aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-02-02 23:50:33 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-02-02 23:50:33 +0000
commit64c2294aa2c703b1878a4855cf2b2a85a2833b52 (patch)
tree954b79658f98919373377437244ebdf1eb589ed3
parentff8f8d19acae9d45aa4ae92a1c99728acf54e2e4 (diff)
parent57b848e746336c2f4d4be8f3ddad0ad3132ccc74 (diff)
downloadokio-simpleperf-release.tar.gz
Snap for 11400057 from 57b848e746336c2f4d4be8f3ddad0ad3132ccc74 to simpleperf-releasesimpleperf-release
Change-Id: I45059b139f47c33e26ee7009d77ec7b8169249b7
-rwxr-xr-x.buildscript/prepare_mkdocs.sh2
-rwxr-xr-x[-rw-r--r--].buildscript/restore_v1_docs.sh36
-rw-r--r--.editorconfig16
-rw-r--r--.gitattributes4
-rw-r--r--.github/workflows/build.yml151
-rw-r--r--.gitignore2
-rw-r--r--Android.bp2
-rw-r--r--CHANGELOG.md337
-rw-r--r--METADATA4
-rw-r--r--OWNERS2
-rw-r--r--android-test/build.gradle73
-rw-r--r--android-test/build.gradle.kts69
-rw-r--r--build-support/build.gradle.kts26
-rw-r--r--build-support/settings.gradle.kts1
-rw-r--r--build-support/src/main/kotlin/BuildSupport.kt23
-rw-r--r--build-support/src/main/kotlin/bom.kt70
-rw-r--r--build-support/src/main/kotlin/jvm.kt17
-rw-r--r--build-support/src/main/kotlin/kmp.kt23
-rw-r--r--build-support/src/main/kotlin/platforms.kt157
-rw-r--r--build.gradle120
-rw-r--r--build.gradle.kts248
-rw-r--r--docs/css/app.css2
-rw-r--r--docs/css/dokka-logo.css5
-rw-r--r--docs/file_system.md125
-rw-r--r--docs/index.md1052
-rw-r--r--docs/java_io_recipes.md98
-rw-r--r--docs/multiplatform.md26
-rw-r--r--docs/recipes.md942
-rw-r--r--docs/releasing.md83
-rw-r--r--docs/security.md1
-rw-r--r--gradle.properties30
-rw-r--r--gradle/gradle-mvn-mpp-push.gradle137
-rw-r--r--gradle/libs.versions.toml24
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin59203 -> 63721 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties4
-rwxr-xr-xgradlew282
-rw-r--r--gradlew.bat181
-rw-r--r--kotlin-js-store/yarn.lock1931
-rw-r--r--mkdocs.yml31
-rw-r--r--okio-assetfilesystem/README.md5
-rw-r--r--okio-assetfilesystem/api/okio-assetfilesystem.api4
-rw-r--r--okio-assetfilesystem/build.gradle.kts36
-rw-r--r--okio-assetfilesystem/src/androidTest/assets/dir/nested.txt1
-rw-r--r--okio-assetfilesystem/src/androidTest/assets/file.txt1
-rw-r--r--okio-assetfilesystem/src/androidTest/assets/moby10b.txt23244
-rw-r--r--okio-assetfilesystem/src/androidTest/kotlin/okio/assetfilesystem/AssetFileSystemTest.kt303
-rw-r--r--okio-assetfilesystem/src/main/kotlin/okio/assetfilesystem/AssetFileSystem.kt240
-rw-r--r--okio-bom/build.gradle.kts12
-rw-r--r--okio-fakefilesystem/README.md4
-rw-r--r--okio-fakefilesystem/api/okio-fakefilesystem.api41
-rw-r--r--okio-fakefilesystem/build.gradle.kts79
-rw-r--r--okio-fakefilesystem/src/commonMain/kotlin/okio/fakefilesystem/FakeFileSystem.kt768
-rw-r--r--okio-fakefilesystem/src/commonMain/kotlin/okio/fakefilesystem/FileMetadataCommon.kt47
-rw-r--r--okio-nodefilesystem/build.gradle.kts63
-rw-r--r--okio-nodefilesystem/src/main/kotlin/okio/FileSink.kt47
-rw-r--r--okio-nodefilesystem/src/main/kotlin/okio/FileSource.kt53
-rw-r--r--okio-nodefilesystem/src/main/kotlin/okio/FsJs.kt184
-rw-r--r--okio-nodefilesystem/src/main/kotlin/okio/NodeJsFileHandle.kt75
-rw-r--r--okio-nodefilesystem/src/main/kotlin/okio/NodeJsFileSystem.kt244
-rw-r--r--okio-nodefilesystem/src/test/kotlin/okio/NodeJsFileSystemTest.kt27
-rw-r--r--okio-testing-support/README.md5
-rw-r--r--okio-testing-support/build.gradle.kts61
-rw-r--r--okio-testing-support/src/commonMain/kotlin/okio/AbstractFileSystemTest.kt2651
-rw-r--r--okio-testing-support/src/commonMain/kotlin/okio/FakeClock.kt (renamed from okio/src/commonTest/kotlin/okio/FakeClock.kt)8
-rw-r--r--okio-testing-support/src/commonMain/kotlin/okio/TestingCommon.kt92
-rw-r--r--okio-testing-support/src/jsMain/kotlin/okio/TestingJs.kt22
-rw-r--r--okio-testing-support/src/jvmMain/kotlin/okio/TestingExecutors.kt46
-rw-r--r--okio-testing-support/src/jvmMain/kotlin/okio/TestingJvm.kt20
-rw-r--r--okio-testing-support/src/nativeMain/kotlin/okio/TestingNative.kt20
-rw-r--r--okio-testing-support/src/nonWasmMain/kotlin/okio/TestingNonWasm.kt55
-rw-r--r--okio-testing-support/src/wasmMain/kotlin/okio/TestingWasm.kt61
-rw-r--r--okio-testing-support/src/wasmWasiMain/kotlin/okio/WasiClock.kt39
-rw-r--r--okio-testing-support/src/wasmWasiMain/kotlin/okio/internal/preview1/Clockid.kt30
-rw-r--r--okio-testing-support/src/wasmWasiMain/kotlin/okio/internal/preview1/Preview1Clock.kt19
-rw-r--r--okio-wasifilesystem/README.md12
-rw-r--r--okio-wasifilesystem/build.gradle.kts111
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/FileSink.kt89
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/FileSource.kt92
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/WasiFileHandle.kt121
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/WasiFileSystem.kt512
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/ErrnoException.kt26
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/Wasi.kt89
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Errno.kt238
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Fdflags.kt26
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Filetype.kt35
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/LookupFlags.kt17
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/OFlags.kt23
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Preview1.kt336
-rw-r--r--okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Rights.kt130
-rw-r--r--okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiFileSystemPreopensTest.kt94
-rw-r--r--okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiFileSystemTest.kt27
-rw-r--r--okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiTest.kt385
-rw-r--r--okio/api/okio.api809
-rw-r--r--okio/build.gradle193
-rw-r--r--okio/build.gradle.kts197
-rw-r--r--okio/gradle.properties2
-rw-r--r--okio/jvm/japicmp/build.gradle56
-rw-r--r--okio/jvm/jmh/build.gradle36
-rw-r--r--okio/jvm/jmh/build.gradle.kts12
-rw-r--r--okio/jvm/jvm.gradle26
-rw-r--r--okio/src/appleMain/kotlin/okio/ApplePosixVariant.kt46
-rw-r--r--okio/src/appleMain/kotlin/okio/ByteString.kt209
-rw-r--r--okio/src/appleMain/kotlin/okio/SegmentedByteString.kt (renamed from okio/src/nonJvmMain/kotlin/okio/SegmentedByteString.kt)16
-rw-r--r--okio/src/appleTest/kotlin/okio/AppleByteStringTest.kt22
-rw-r--r--okio/src/commonMain/kotlin/okio/Base64.kt (renamed from okio/src/commonMain/kotlin/okio/-Base64.kt)20
-rw-r--r--okio/src/commonMain/kotlin/okio/Buffer.kt26
-rw-r--r--okio/src/commonMain/kotlin/okio/BufferedSink.kt2
-rw-r--r--okio/src/commonMain/kotlin/okio/BufferedSource.kt2
-rw-r--r--okio/src/commonMain/kotlin/okio/ByteString.kt58
-rw-r--r--okio/src/commonMain/kotlin/okio/CommonPlatform.kt (renamed from okio/src/commonMain/kotlin/okio/-Platform.kt)20
-rw-r--r--okio/src/commonMain/kotlin/okio/ExperimentalFileSystem.kt11
-rw-r--r--okio/src/commonMain/kotlin/okio/FileHandle.kt443
-rw-r--r--okio/src/commonMain/kotlin/okio/FileMetadata.kt156
-rw-r--r--okio/src/commonMain/kotlin/okio/FileSystem.kt395
-rw-r--r--okio/src/commonMain/kotlin/okio/ForwardingFileSystem.kt242
-rw-r--r--okio/src/commonMain/kotlin/okio/ForwardingSource.kt37
-rw-r--r--okio/src/commonMain/kotlin/okio/HashingSink.kt12
-rw-r--r--okio/src/commonMain/kotlin/okio/HashingSource.kt12
-rw-r--r--okio/src/commonMain/kotlin/okio/Okio.kt17
-rw-r--r--okio/src/commonMain/kotlin/okio/Options.kt8
-rw-r--r--okio/src/commonMain/kotlin/okio/Path.kt309
-rw-r--r--okio/src/commonMain/kotlin/okio/PeekSource.kt4
-rw-r--r--okio/src/commonMain/kotlin/okio/RealBufferedSink.kt2
-rw-r--r--okio/src/commonMain/kotlin/okio/RealBufferedSource.kt2
-rw-r--r--okio/src/commonMain/kotlin/okio/Segment.kt6
-rw-r--r--okio/src/commonMain/kotlin/okio/SegmentedByteString.kt2
-rw-r--r--okio/src/commonMain/kotlin/okio/Utf8.kt41
-rw-r--r--okio/src/commonMain/kotlin/okio/Util.kt (renamed from okio/src/commonMain/kotlin/okio/-Util.kt)37
-rw-r--r--okio/src/commonMain/kotlin/okio/internal/-Utf8.kt6
-rw-r--r--okio/src/commonMain/kotlin/okio/internal/Buffer.kt171
-rw-r--r--okio/src/commonMain/kotlin/okio/internal/ByteString.kt36
-rw-r--r--okio/src/commonMain/kotlin/okio/internal/FileSystem.kt154
-rw-r--r--okio/src/commonMain/kotlin/okio/internal/Path.kt405
-rw-r--r--okio/src/commonMain/kotlin/okio/internal/RealBufferedSink.kt9
-rw-r--r--okio/src/commonMain/kotlin/okio/internal/RealBufferedSource.kt25
-rw-r--r--okio/src/commonMain/kotlin/okio/internal/SegmentedByteString.kt35
-rw-r--r--okio/src/commonTest/kotlin/okio/AbstractBufferedSinkTest.kt67
-rw-r--r--okio/src/commonTest/kotlin/okio/AbstractBufferedSourceTest.kt305
-rw-r--r--okio/src/commonTest/kotlin/okio/BufferCommonTest.kt2
-rw-r--r--okio/src/commonTest/kotlin/okio/BufferedSourceFactory.kt16
-rw-r--r--okio/src/commonTest/kotlin/okio/ByteStringMoreTests.kt29
-rw-r--r--okio/src/commonTest/kotlin/okio/ByteStringTest.kt144
-rw-r--r--okio/src/commonTest/kotlin/okio/CommonBufferTest.kt61
-rw-r--r--okio/src/commonTest/kotlin/okio/CommonOptionsTest.kt205
-rw-r--r--okio/src/commonTest/kotlin/okio/CommonRealBufferedSinkTest.kt20
-rw-r--r--okio/src/commonTest/kotlin/okio/CommonRealBufferedSourceTest.kt6
-rw-r--r--okio/src/commonTest/kotlin/okio/ForwardingSourceTest.kt53
-rw-r--r--okio/src/commonTest/kotlin/okio/HashingSinkTest.kt4
-rw-r--r--okio/src/commonTest/kotlin/okio/HashingSourceTest.kt12
-rw-r--r--okio/src/commonTest/kotlin/okio/HashingTest.kt4
-rw-r--r--okio/src/commonTest/kotlin/okio/OkioTesting.kt (renamed from okio/src/commonTest/kotlin/okio/util.kt)37
-rw-r--r--okio/src/commonTest/kotlin/okio/PathTest.kt777
-rw-r--r--okio/src/commonTest/kotlin/okio/UnsafeCursorTest.kt11
-rw-r--r--okio/src/commonTest/kotlin/okio/Utf8KotlinTest.kt16
-rw-r--r--okio/src/hashFunctions/kotlin/okio/internal/HashFunction.kt2
-rw-r--r--okio/src/hashFunctions/kotlin/okio/internal/Hmac.kt6
-rw-r--r--okio/src/hashFunctions/kotlin/okio/internal/Md5.kt6
-rw-r--r--okio/src/hashFunctions/kotlin/okio/internal/Sha1.kt2
-rw-r--r--okio/src/hashFunctions/kotlin/okio/internal/Sha256.kt6
-rw-r--r--okio/src/hashFunctions/kotlin/okio/internal/Sha512.kt4
-rw-r--r--okio/src/jsMain/kotlin/okio/FileSystem.kt90
-rw-r--r--okio/src/jsMain/kotlin/okio/JsPlatform.kt52
-rw-r--r--okio/src/jvmMain/kotlin/okio/-DeprecatedOkio.kt54
-rw-r--r--okio/src/jvmMain/kotlin/okio/-DeprecatedUtf8.kt8
-rw-r--r--okio/src/jvmMain/kotlin/okio/-JvmPlatform.kt (renamed from okio/src/jvmMain/kotlin/okio/-Platform.kt)16
-rw-r--r--okio/src/jvmMain/kotlin/okio/AsyncTimeout.kt197
-rw-r--r--okio/src/jvmMain/kotlin/okio/Buffer.kt84
-rw-r--r--okio/src/jvmMain/kotlin/okio/BufferedSink.kt4
-rw-r--r--okio/src/jvmMain/kotlin/okio/BufferedSource.kt4
-rw-r--r--okio/src/jvmMain/kotlin/okio/ByteString.kt75
-rw-r--r--okio/src/jvmMain/kotlin/okio/CipherSink.kt22
-rw-r--r--okio/src/jvmMain/kotlin/okio/CipherSource.kt14
-rw-r--r--okio/src/jvmMain/kotlin/okio/DeflaterSink.kt15
-rw-r--r--okio/src/jvmMain/kotlin/okio/DeprecatedUpgrade.kt (renamed from okio/src/jvmMain/kotlin/okio/-DeprecatedUpgrade.kt)1
-rw-r--r--okio/src/jvmMain/kotlin/okio/FileSystem.kt164
-rw-r--r--okio/src/jvmMain/kotlin/okio/ForwardingSink.kt4
-rw-r--r--okio/src/jvmMain/kotlin/okio/ForwardingSource.kt16
-rw-r--r--okio/src/jvmMain/kotlin/okio/ForwardingTimeout.kt11
-rw-r--r--okio/src/jvmMain/kotlin/okio/GzipSink.kt2
-rw-r--r--okio/src/jvmMain/kotlin/okio/GzipSource.kt2
-rw-r--r--okio/src/jvmMain/kotlin/okio/HashingSink.kt16
-rw-r--r--okio/src/jvmMain/kotlin/okio/HashingSource.kt16
-rw-r--r--okio/src/jvmMain/kotlin/okio/InflaterSource.kt2
-rw-r--r--okio/src/jvmMain/kotlin/okio/JvmFileHandle.kt81
-rw-r--r--okio/src/jvmMain/kotlin/okio/JvmOkio.kt27
-rw-r--r--okio/src/jvmMain/kotlin/okio/JvmSystemFileSystem.kt157
-rw-r--r--okio/src/jvmMain/kotlin/okio/NioFileSystemFileHandle.kt84
-rw-r--r--okio/src/jvmMain/kotlin/okio/NioFileSystemWrappingFileSystem.kt191
-rw-r--r--okio/src/jvmMain/kotlin/okio/NioSystemFileSystem.kt91
-rw-r--r--okio/src/jvmMain/kotlin/okio/Path.kt131
-rw-r--r--okio/src/jvmMain/kotlin/okio/Pipe.kt51
-rw-r--r--okio/src/jvmMain/kotlin/okio/RealBufferedSink.kt13
-rw-r--r--okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt19
-rw-r--r--okio/src/jvmMain/kotlin/okio/SegmentPool.kt20
-rw-r--r--okio/src/jvmMain/kotlin/okio/SegmentedByteString.kt32
-rw-r--r--okio/src/jvmMain/kotlin/okio/Throttler.kt25
-rw-r--r--okio/src/jvmMain/kotlin/okio/Timeout.kt152
-rw-r--r--okio/src/jvmMain/kotlin/okio/ZipFileSystem.kt174
-rw-r--r--okio/src/jvmMain/kotlin/okio/internal/FixedLengthSource.kt77
-rw-r--r--okio/src/jvmMain/kotlin/okio/internal/ResourceFileSystem.kt209
-rw-r--r--okio/src/jvmMain/kotlin/okio/internal/ZipEntry.kt51
-rw-r--r--okio/src/jvmMain/kotlin/okio/internal/ZipFiles.kt458
-rw-r--r--okio/src/jvmMain/resources/META-INF/proguard/okio.pro2
l---------okio/src/jvmTest/hashFunctions1
-rw-r--r--okio/src/jvmTest/java/okio/AsyncTimeoutTest.java392
-rw-r--r--okio/src/jvmTest/java/okio/BufferCursorTest.java468
-rw-r--r--okio/src/jvmTest/java/okio/BufferTest.java580
-rw-r--r--okio/src/jvmTest/java/okio/BufferedSinkJavaTest.java245
-rw-r--r--okio/src/jvmTest/java/okio/BufferedSinkTest.java380
-rw-r--r--okio/src/jvmTest/java/okio/BufferedSourceJavaTest.java232
-rw-r--r--okio/src/jvmTest/java/okio/BufferedSourceTest.java1492
-rw-r--r--okio/src/jvmTest/java/okio/ByteStringJavaTest.java633
-rw-r--r--okio/src/jvmTest/java/okio/DeflaterSinkTest.java149
-rw-r--r--okio/src/jvmTest/java/okio/ForwardingTimeoutTest.java46
-rw-r--r--okio/src/jvmTest/java/okio/GzipSinkTest.java61
-rw-r--r--okio/src/jvmTest/java/okio/GzipSourceTest.java233
-rw-r--r--okio/src/jvmTest/java/okio/InflaterSourceTest.java210
-rw-r--r--okio/src/jvmTest/java/okio/LargeStreamsTest.java118
-rw-r--r--okio/src/jvmTest/java/okio/NioTest.java147
-rw-r--r--okio/src/jvmTest/java/okio/OkioTest.java169
-rw-r--r--okio/src/jvmTest/java/okio/PipeTest.java376
-rw-r--r--okio/src/jvmTest/java/okio/ReadUtf8LineTest.java212
-rw-r--r--okio/src/jvmTest/java/okio/SocketTimeoutTest.java138
-rw-r--r--okio/src/jvmTest/java/okio/Utf8Test.java282
-rw-r--r--okio/src/jvmTest/java/okio/WaitUntilNotifiedTest.java172
-rw-r--r--okio/src/jvmTest/kotlin/okio/AsyncTimeoutTest.kt500
-rw-r--r--okio/src/jvmTest/kotlin/okio/AwaitSignalTest.kt224
-rw-r--r--okio/src/jvmTest/kotlin/okio/BufferCursorKotlinTest.kt12
-rw-r--r--okio/src/jvmTest/kotlin/okio/BufferCursorTest.kt459
-rw-r--r--okio/src/jvmTest/kotlin/okio/BufferFactory.kt5
-rw-r--r--okio/src/jvmTest/kotlin/okio/BufferKotlinTest.kt8
-rw-r--r--okio/src/jvmTest/kotlin/okio/BufferTest.kt612
-rw-r--r--okio/src/jvmTest/kotlin/okio/BufferedSinkJavaTest.kt250
-rw-r--r--okio/src/jvmTest/kotlin/okio/BufferedSinkTest.kt389
-rw-r--r--okio/src/jvmTest/kotlin/okio/BufferedSourceJavaTest.kt225
-rw-r--r--okio/src/jvmTest/kotlin/okio/BufferedSourceTest.kt1503
-rw-r--r--okio/src/jvmTest/kotlin/okio/ByteStringJavaTest.kt278
-rw-r--r--okio/src/jvmTest/kotlin/okio/ByteStringKotlinTest.kt8
-rw-r--r--okio/src/jvmTest/kotlin/okio/CipherAlgorithm.kt4
-rw-r--r--okio/src/jvmTest/kotlin/okio/CipherFactory.kt2
-rw-r--r--okio/src/jvmTest/kotlin/okio/CipherSinkTest.kt4
-rw-r--r--okio/src/jvmTest/kotlin/okio/CipherSourceTest.kt2
-rw-r--r--okio/src/jvmTest/kotlin/okio/DeflateKotlinTest.kt4
-rw-r--r--okio/src/jvmTest/kotlin/okio/DeflaterSinkTest.kt171
-rw-r--r--okio/src/jvmTest/kotlin/okio/FileHandleFileSystemTest.kt90
-rw-r--r--okio/src/jvmTest/kotlin/okio/FileLeakTest.kt111
-rw-r--r--okio/src/jvmTest/kotlin/okio/FileSystemJavaTest.kt122
-rw-r--r--okio/src/jvmTest/kotlin/okio/FixedLengthSourceTest.kt165
-rw-r--r--okio/src/jvmTest/kotlin/okio/ForwardingTimeoutKotlinTest.kt2
-rw-r--r--okio/src/jvmTest/kotlin/okio/ForwardingTimeoutTest.kt44
-rw-r--r--okio/src/jvmTest/kotlin/okio/GzipKotlinTest.kt24
-rw-r--r--okio/src/jvmTest/kotlin/okio/GzipSinkTest.kt61
-rw-r--r--okio/src/jvmTest/kotlin/okio/GzipSourceTest.kt232
-rw-r--r--okio/src/jvmTest/kotlin/okio/InflaterSourceTest.kt215
-rw-r--r--okio/src/jvmTest/kotlin/okio/JimfsOkioRoundTripTest.kt70
-rw-r--r--okio/src/jvmTest/kotlin/okio/JvmSystemFileSystemTest.kt86
-rw-r--r--okio/src/jvmTest/kotlin/okio/JvmTest.kt49
-rw-r--r--okio/src/jvmTest/kotlin/okio/JvmTesting.kt54
-rw-r--r--okio/src/jvmTest/kotlin/okio/LargeStreamsTest.kt112
-rw-r--r--okio/src/jvmTest/kotlin/okio/MessageDigestConsistencyTest.kt (renamed from okio/src/jvmTest/java/okio/MessageDigestConsistencyTest.kt)14
-rw-r--r--okio/src/jvmTest/kotlin/okio/NioTest.kt142
-rw-r--r--okio/src/jvmTest/kotlin/okio/OkioKotlinTest.kt13
-rw-r--r--okio/src/jvmTest/kotlin/okio/OkioTest.kt147
-rw-r--r--okio/src/jvmTest/kotlin/okio/PipeKotlinTest.kt34
-rw-r--r--okio/src/jvmTest/kotlin/okio/PipeTest.kt358
-rw-r--r--okio/src/jvmTest/kotlin/okio/ReadUtf8LineTest.kt217
-rw-r--r--okio/src/jvmTest/kotlin/okio/SegmentSharingTest.kt18
-rw-r--r--okio/src/jvmTest/kotlin/okio/SocketTimeoutTest.kt141
-rw-r--r--okio/src/jvmTest/kotlin/okio/TestUtil.kt9
-rw-r--r--okio/src/jvmTest/kotlin/okio/ThrottlerTakeTest.kt2
-rw-r--r--okio/src/jvmTest/kotlin/okio/ThrottlerTest.kt5
-rw-r--r--okio/src/jvmTest/kotlin/okio/TimeoutFactory.kt33
-rw-r--r--okio/src/jvmTest/kotlin/okio/TimeoutTest.kt127
-rw-r--r--okio/src/jvmTest/kotlin/okio/Utf8Test.kt307
-rw-r--r--okio/src/jvmTest/kotlin/okio/WaitUntilNotifiedTest.kt236
-rw-r--r--okio/src/jvmTest/kotlin/okio/ZipBuilder.kt158
-rw-r--r--okio/src/jvmTest/kotlin/okio/ZipFileSystemJavaTest.kt43
-rw-r--r--okio/src/jvmTest/kotlin/okio/ZipFileSystemTest.kt502
-rw-r--r--okio/src/jvmTest/kotlin/okio/internal/HmacTest.kt (renamed from okio/src/jvmTest/java/okio/internal/HmacTest.kt)18
-rw-r--r--okio/src/jvmTest/kotlin/okio/internal/ResourceFileSystemTest.kt505
-rw-r--r--okio/src/jvmTest/resources/okio/resourcefilesystem/a.txt1
-rw-r--r--okio/src/jvmTest/resources/okio/resourcefilesystem/b/b.txt1
-rw-r--r--okio/src/jvmTest/resources/okio/resourcefilesystem/non-ascii/ギリシア神話/Ἰλιάς1
-rw-r--r--okio/src/linuxMain/kotlin/okio/LinuxPosixVariant.kt46
-rw-r--r--okio/src/mingwX64Main/kotlin/okio/Windows.kt54
-rw-r--r--okio/src/mingwX64Main/kotlin/okio/WindowsFileHandle.kt167
-rw-r--r--okio/src/mingwX64Main/kotlin/okio/WindowsPosixVariant.kt206
-rw-r--r--okio/src/nativeMain/kotlin/okio/Cinterop.kt68
-rw-r--r--okio/src/nativeMain/kotlin/okio/FileSink.kt81
-rw-r--r--okio/src/nativeMain/kotlin/okio/FileSource.kt76
-rw-r--r--okio/src/nativeMain/kotlin/okio/FileSystem.kt114
-rw-r--r--okio/src/nativeMain/kotlin/okio/PosixFileSystem.kt119
-rw-r--r--okio/src/nativeMain/kotlin/okio/PosixVariant.kt44
-rw-r--r--okio/src/nativeMain/kotlin/okio/SizetVariant.kt39
-rw-r--r--okio/src/nativeTest/kotlin/okio/NativeSystemFileSystemTest.kt27
-rw-r--r--okio/src/nonAppleMain/kotlin/okio/ByteString.kt (renamed from okio/src/nonJvmMain/kotlin/okio/ByteString.kt)15
-rw-r--r--okio/src/nonAppleMain/kotlin/okio/SegmentedByteString.kt103
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/Buffer.kt10
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/BufferedSink.kt2
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/BufferedSource.kt2
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/ForwardingSource.kt32
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/HashingSink.kt2
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/HashingSource.kt2
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/NonJvmPlatform.kt (renamed from okio/src/nonJvmMain/kotlin/okio/-Platform.kt)23
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/Path.kt102
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/RealBufferedSink.kt2
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/RealBufferedSource.kt10
-rw-r--r--okio/src/nonJvmTest/kotlin/okio/NonJvmTesting.kt38
-rw-r--r--okio/src/nonWasmTest/kotlin/okio/FakeFileSystemTest.kt495
-rw-r--r--okio/src/nonWasmTest/kotlin/okio/ForwardingFileSystemTest.kt172
-rw-r--r--okio/src/nonWasmTest/kotlin/okio/UseTest.kt28
-rw-r--r--okio/src/unixMain/kotlin/okio/UnixFileHandle.kt98
-rw-r--r--okio/src/unixMain/kotlin/okio/UnixPosixVariant.kt206
-rw-r--r--okio/src/wasmMain/kotlin/okio/FileSystem.kt90
-rw-r--r--okio/src/wasmMain/kotlin/okio/WasmPlatform.kt19
-rw-r--r--renovate.json7
-rw-r--r--samples/build.gradle23
-rw-r--r--samples/build.gradle.kts27
-rw-r--r--samples/src/jvmMain/java/okio/samples/BitmapEncoder.java9
-rw-r--r--samples/src/jvmMain/java/okio/samples/Hashing.java21
-rw-r--r--samples/src/jvmMain/java/okio/samples/ReadFileLineByLine.java9
-rw-r--r--samples/src/jvmMain/java/okio/samples/ReadFileLineByLine.kt40
-rw-r--r--samples/src/jvmMain/java/okio/samples/ReadJavaIoFileLineByLine.java48
-rw-r--r--samples/src/jvmMain/java/okio/samples/WriteFile.java9
-rw-r--r--samples/src/jvmMain/java/okio/samples/WriteJavaIoFile.java47
-rw-r--r--samples/src/jvmMain/kotlin/okio/samples/BitmapEncoder.kt18
-rw-r--r--samples/src/jvmMain/kotlin/okio/samples/ExploreCharsets.kt2
-rw-r--r--samples/src/jvmMain/kotlin/okio/samples/GoldenValue.kt8
-rw-r--r--samples/src/jvmMain/kotlin/okio/samples/Hashing.kt29
-rw-r--r--samples/src/jvmMain/kotlin/okio/samples/ReadJavaIoFileLineByLine.kt (renamed from samples/src/jvmMain/kotlin/okio/samples/ReadFileLineByLine.kt)4
-rw-r--r--samples/src/jvmMain/kotlin/okio/samples/SocksProxyServer.kt16
-rw-r--r--samples/src/jvmMain/kotlin/okio/samples/WriteFile.kt13
-rw-r--r--samples/src/jvmMain/kotlin/okio/samples/WriteJavaIoFile.kt37
-rw-r--r--samples/src/jvmTest/java/okio/samples/ChannelsTest.java5
-rw-r--r--settings.gradle14
-rw-r--r--settings.gradle.kts25
336 files changed, 55173 insertions, 9940 deletions
diff --git a/.buildscript/prepare_mkdocs.sh b/.buildscript/prepare_mkdocs.sh
index 5dcb42cd..b9bbebc2 100755
--- a/.buildscript/prepare_mkdocs.sh
+++ b/.buildscript/prepare_mkdocs.sh
@@ -9,7 +9,7 @@
set -ex
# Generate the API docs
-./gradlew okio:dokkaHtml
+./gradlew dokkaHtml
# Copy in special files that GitHub wants in the project root.
cp CHANGELOG.md docs/changelog.md
diff --git a/.buildscript/restore_v1_docs.sh b/.buildscript/restore_v1_docs.sh
index 220fed82..e0fae1e7 100644..100755
--- a/.buildscript/restore_v1_docs.sh
+++ b/.buildscript/restore_v1_docs.sh
@@ -1,30 +1,34 @@
#!/bin/bash
# Commit b3205fa199a19d6fbf13ee5c8e0c3d6d2b15b05f contains
-# Javadoc for Okio 1.x. Those should be present on
-# gh-pages and published along with the other website
-# content, but if for some reason they have to be re-added
+# Javadoc for Okio 1.x. Those should be present on
+# gh-pages and published along with the other website
+# content, but if for some reason they have to be re-added
# to gh-pages - run this script locally.
set -ex
-REPO="git@github.com:square/okio.git"
-DIR=temp-clone
+DIR=temp-clone
-# Delete any existing temporary website clone
-rm -rf $DIR
+# Delete any existing temporary website clone
+rm -rf $DIR
-# Clone the current repo into temp folder
-git clone $REPO $DIR
+# Clone the current repo into temp folder
+git clone . $DIR
-# Move working directory into temp folder
+# Move working directory into temp folder
cd $DIR
-# Restore Javadocs from 1.x
-git checkout gh-pages
-git cherry-pick b3205fa199a19d6fbf13ee5c8e0c3d6d2b15b05f
-git push
+# Restore docs from 1.x
+git checkout b3205fa199a19d6fbf13ee5c8e0c3d6d2b15b05f
+mkdir -p ../site
+mv ./1.x ../site/1.x
+
+# Restore docs from 2.x
+git checkout 9235ff8faca96082aa8784e789448b5f4893af69
+mkdir -p ../site
+mv ./2.x ../site/2.x
-# Delete our temp folder
-cd ..
+# Delete our temp folder
+cd ..
rm -rf $DIR
diff --git a/.editorconfig b/.editorconfig
index 15d8bd6e..e4cef88f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,5 +1,13 @@
-[*.kt]
-indent_size = 2
+root = true
-[*.gradle]
-indent_size = 2
+[*]
+insert_final_newline=true
+end_of_line=lf
+charset=utf-8
+indent_size=2
+trim_trailing_whitespace=true
+
+[*.{kt,kts}]
+ij_kotlin_allow_trailing_comma=true
+ij_kotlin_allow_trailing_comma_on_call_site=true
+ij_kotlin_imports_layout=*
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..27fc7c29
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+* text=auto eol=lf
+
+*.bat text eol=crlf
+*.jar binary \ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 26b26894..a4fba896 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,9 +1,16 @@
name: build
-on: [push, pull_request]
+on:
+ pull_request: {}
+ workflow_dispatch: {}
+ push:
+ branches:
+ - 'master'
+ tags-ignore:
+ - '**'
env:
- GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"
+ GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx2g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"
jobs:
jvm:
@@ -13,134 +20,147 @@ jobs:
fail-fast: false
matrix:
java-version:
- - 1.8
- - 9
- - 10
+ - 8
- 11
- - 12
- - 13
- - 14
- - 15
+ - 17
+ - 19
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Configure JDK
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v4
with:
- java-version: ${{ matrix.java-version }}
+ distribution: 'zulu'
+ java-version: 19
- name: Test
run: |
- ./gradlew -Dkjs=false -Dknative=false build
+ ./gradlew -Dkjs=false -Dknative=false -Dkwasm=false -Dtest.java.version=${{ matrix.java-version }} build --stacktrace
- - name: Upload Japicmp report
- if: failure()
- uses: actions/upload-artifact@master
+ emulator:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4.0.0
with:
- name: japicmp-report
- path: okio/jvm/japicmp/build/reports/japi.txt
+ distribution: 'zulu'
+ java-version: 19
+
+ - uses: gradle/gradle-build-action@v2
+
+ - uses: reactivecircus/android-emulator-runner@v2
+ with:
+ api-level: 24
+ script: ./gradlew :okio-assetfilesystem:connectedCheck
+
+ loom:
+ runs-on: ubuntu-latest
- multiplatform:
- runs-on: macOS-latest
+ strategy:
+ fail-fast: false
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Configure JDK
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v4
with:
- java-version: 14
+ distribution: 'zulu'
+ java-version: 19
- name: Test
run: |
- ./gradlew build
+ ./gradlew -DloomEnabled=true build
+
+ all-platforms:
+ runs-on: ${{ matrix.os }}
- windows:
- runs-on: windows-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ macos-11, ubuntu-latest, windows-latest ]
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Configure JDK
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v4
with:
- java-version: 1.8
+ distribution: 'zulu'
+ java-version: 19
- name: Test
+ if: matrix.os != 'windows-latest'
run: |
./gradlew build
- publish:
- runs-on: macOS-latest
- if: github.ref == 'refs/heads/master'
- needs: [jvm, multiplatform, windows]
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
+ - name: Test (No WASM)
+ if: matrix.os == 'windows-latest'
+ run: |
+ ./gradlew build -Dkwasm=false
- - name: Configure JDK
- uses: actions/setup-java@v1
+ - name: Save Test Reports
+ if: failure()
+ uses: actions/upload-artifact@v4
with:
- java-version: 14
+ name: test-reports
+ path: '**/build/reports'
- - name: Upload Artifacts
- run: |
- ./gradlew clean publish
- env:
- ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
- ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
-
- publish-windows:
- runs-on: windows-latest
- if: github.ref == 'refs/heads/master'
- needs: [jvm, multiplatform, windows]
+ publish:
+ runs-on: macos-13
+ if: github.repository == 'square/okio' && github.ref == 'refs/heads/master'
+ needs: [jvm, all-platforms, emulator]
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Configure JDK
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v4
with:
- java-version: 1.8
+ distribution: 'zulu'
+ java-version: 19
- name: Upload Artifacts
run: |
- ./gradlew clean publishMingwX64PublicationToMavenRepository
+ ./gradlew clean publish --stacktrace
env:
- ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
- ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
+ ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
+ ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
+ ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ARTIFACT_SIGNING_PRIVATE_KEY }}
publish-website:
runs-on: ubuntu-latest
- if: github.ref == 'refs/heads/master'
- needs: [jvm, multiplatform]
+ if: github.repository == 'square/okio' && github.ref == 'refs/heads/master'
+ needs: [jvm, all-platforms, emulator]
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
- name: Configure JDK
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v4
with:
- java-version: 14
+ distribution: 'zulu'
+ java-version: 19
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: 3.8
@@ -149,9 +169,12 @@ jobs:
- name: Build mkdocs
run: |
- pip3 install mkdocs-macros-plugin
+ pip3 install mkdocs-material mkdocs-macros-plugin
mkdocs build
+ - name: Restore 1.x docs
+ run: .buildscript/restore_v1_docs.sh
+
- name: Deploy docs
if: success()
uses: JamesIves/github-pages-deploy-action@releases/v3
diff --git a/.gitignore b/.gitignore
index 451ec21b..977efb87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,6 @@ obj
node_modules
# Special Mkdocs files
-docs/2.x
+docs/3.x
docs/changelog.md
docs/contributing.md
diff --git a/Android.bp b/Android.bp
index d1c46b25..8ac33877 100644
--- a/Android.bp
+++ b/Android.bp
@@ -20,6 +20,8 @@ java_library {
host_supported: true,
srcs: [
"okio/src/jvmMain/**/*.kt",
+ ],
+ common_srcs: [
"okio/src/commonMain/**/*.kt",
],
apex_available: [
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48028d0a..ad2eaaa3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,309 @@
Change Log
==========
+## Version 3.7.0
+
+_2023-12-16_
+
+ * New: `Timeout.cancel()` prevents a timeout from firing.
+ * Breaking: Drop the `watchosX86` Kotlin/Native target. From [the Kotlin blog][watchosX86],
+ _‘This is an obsolete simulator for Intel Macs. Use the watchosX64 target instead.’_
+ * New: Add the `watchosDeviceArm64` Kotlin/Native target.
+ * New: `Timeout` APIs that accept `kotlin.time.Duration`.
+ * Upgrade: [Kotlin 1.9.21][kotlin_1_9_21].
+
+
+## Version 3.6.0
+
+_2023-10-01_
+
+ * Fix: Don't leak file handles when using `metadata` functions on `ZipFileSystem`. We had a bug
+ where we were closing the `.zip` file, but not a stream inside of it. We would have prevented
+ this bug if only we’d used `FakeFileSystem.checkNoOpenFiles()` in our tests!
+ * Fix: Don't build an index of a class loader's resources in `ResourceFileSystem.read()`. This
+ operation doesn't need this index, and building it is potentially expensive.
+ * New: Experimentally support Linux on ARM64 for Kotlin/Native targets (`linuxArm64`). Note that
+ we haven't yet added CI test coverage for this platform.
+ * Upgrade: [Kotlin 1.9.10][kotlin_1_9_10].
+
+
+## Version 1.17.6
+
+_2023-10-01_
+
+ * Fix: Don't crash decoding GZIP files when the optional extra data (`XLEN`) is 32 KiB or larger.
+
+
+## Version 3.5.0
+
+_2023-08-02_
+
+ * New: Support the WebAssembly (WASM) platform. Okio's support for WASM is experimental, but
+ improving, just like Kotlin's own support for WASM.
+ * New: Adapt WebAssembly System Interface (WASI) API's as an Okio FileSystem using
+ `WasiFileSystem`. This is in the new `okio-wasifilesystem` module. It requires the [preview1]
+ WASI API. We’ll make backwards-incompatible upgrades to new WASI API versions as they become
+ available.
+ * Fix: Return relative paths in the NIO adapter FileSystem when required. `FileSystem.list()`
+ had always returned absolute paths, even when the target directory was supplied as a relative
+ path.
+ * Fix: Don't crash when reading into an empty array using `FileHandle` on Kotlin/Native.
+ * Upgrade: [Kotlin 1.9.0][kotlin_1_9_0].
+
+
+## Version 3.4.0
+
+_2023-07-07_
+
+ * New: Adapt a Java NIO FileSystem (`java.nio.file.FileSystem`) as an Okio FileSystem using
+ `fileSystem.asOkioFileSystem()`.
+ * New: Adapt Android’s `AssetManager` as an Okio FileSystem using `AssetFileSystem`. This is in the
+ new `okio-assetfilesystem` module. Android applications should prefer this over
+ `FileSystem.RESOURCES` as it’s faster to load.
+ * Fix: Don't crash decoding GZIP files when the optional extra data (`XLEN`) is 32 KiB or larger.
+ * Fix: Resolve symlinks in `FakeFileSystem.canonicalize()`.
+ * Fix: Report the correct `createdAtMillis` in `NodeJsFileSystem` file metadata. We were
+ incorrectly using `ctimeMs`, where `c` means _changed_, not _created_.
+ * Fix: `UnsafeCursor` is now `Closeable`.
+
+
+## Version 3.3.0
+
+_2023-01-07_
+
+ * Fix: Don't leak resources when `use {}` is used with a non-local return. We introduced this
+ performance and stability bug by not considering that non-local returns execute neither the
+ `return` nor `catch` control flows.
+ * Fix: Use a sealed interface for `BufferedSink` and `BufferedSource`. These were never intended
+ for end-users to implement, and we're happy that Kotlin now allows us to express that in our API.
+ * New: Change internal locks from `synchronized` to `ReentrantLock` and `Condition`. We expect this
+ to improve help when using Okio with Java virtual threads ([Project Loom][loom]).
+ * Upgrade: [Kotlin 1.8.0][kotlin_1_8_0].
+
+
+## Version 3.2.0
+
+_2022-06-26_
+
+ * Fix: Configure the multiplatform artifact (`com.squareup.okio:okio:3.x.x`) to depend on the
+ JVM artifact (`com.squareup.okio:okio-jvm:3.x.x`) for Maven builds. This should work-around an
+ issue where Maven doesn't interpret Gradle metadata.
+ * Fix: Change `CipherSource` and `CipherSink` to recover if the cipher doesn't support streaming.
+ This should work around a crash with AES/GCM ciphers on Android.
+ * New: Enable compatibility with non-hierarchical projects.
+
+
+## Version 3.1.0
+
+_2022-04-19_
+
+ * Upgrade: [Kotlin 1.6.20][kotlin_1_6_20].
+ * New: Support [Hierarchical project structure][hierarchical_projects]. If you're using Okio in a
+ multiplatform project please upgrade your project to Kotlin 1.6.20 (or newer) to take advantage
+ of this. With hierarchical projects it's easier to use properties like `FileSystem.SYSTEM` that
+ are available on most Okio platforms but not all of them.
+ * New: `ForwardingSource` is now available on all platforms.
+ * New: The `watchosX64` platform is now supported.
+ * Fix: Don't crash in `NSData.toByteString()' when the input is empty.
+ * Fix: Support empty ZIP files in `FileSystem.openZip()`.
+ * Fix: Throw in `canonicalize()` of ZIP file systems if the path doesn't exist.
+ * Fix: Don't require ZIP files start with a local file header.
+ * New: `okio.ProtocolException` is a new exception type for multiplatform users. (It is aliased to
+ `java.net.ProtocolException` on JVM platforms).
+
+
+## Version 3.0.0
+
+_2021-10-28_
+
+This is the first stable release of Okio 3.x. This release is strongly backwards-compatible with
+Okio 2.x, and the new major version signifies new capabilities more than it does backwards
+incompatibility.
+
+Most users should be able to upgrade from 2.x by just changing the version. If you're using Okio
+in a Kotlin Multiplatform project, you'll need to drop the `-multiplatform` suffix in your Gradle
+dependencies.
+
+ * New: Remove `@ExperimentalFileSystem`. This annotation is no longer necessary as the file system
+ is no longer experimental!
+ * New: Path no longer aggressively normalizes `..` segments. Use `Path.normalize()` to apply these
+ based on the content of the path, or `FileSystem.canonicalize()` to do it honoring any symlinks
+ on a particular file system.
+ * New: Publish a [bill of materials (BOM)][bom] for Okio. Depend on this from Gradle or Maven to
+ keep all of your Okio artifacts on the same version, even if they're declared via transitive
+ dependencies. You can even omit versions when declaring other Okio dependencies.
+
+ ```kotlin
+ dependencies {
+ api(platform("com.squareup.okio:okio-bom:3.0.0"))
+ api("com.squareup.okio:okio") // No version!
+ api("com.squareup.okio:okio-fakefilesystem") // No version!
+ }
+ ```
+
+ * New: `FileSystem.delete()` silently succeeds when deleting a file that doesn't exist. Use
+ the new `mustExist` parameter to trigger an exception instead.
+ * New: `FileSystem.createDirectories()` silently succeeds when creating a directory that already
+ exists. Use the new `mustCreate` parameter to trigger an exception instead.
+ * New: `FileSystem` offers Java-language overloads where appropriate. Previously functions that
+ had default parameters were potentially awkward to invoke from Java.
+ * New: `Timeout.intersectWith()` returns a value instead of `Unit`. This is a binary-incompatible
+ change. We expect that this public API is very rarely used outside of Okio itself.
+ * Fix: Change `BufferedSource.readDecimalLong()` to fail if the input value is just `-`. Previously
+ Okio incorrectly returned `0` for this.
+
+
+## Version 3.0.0-alpha.11
+
+_2021-10-23_
+
+ * Upgrade: [Kotlin 1.5.31][kotlin_1_5_31].
+ * Upgrade: [kotlinx-datetime 0.3.0][datetime_0_3_0]. (This is a dependency of `okio-fakefilesystem`
+ only.)
+ * New: Support creating and accessing symlinks. We were reluctant to include symlinks in our API
+ (to keep it small!) but decided that supporting them was essential to properly implement
+ recursive traversal.
+ * New: `FileMetadata.extras` can track metadata for custom `FileSystem` implementations.
+ * New: Support Apple Silicon Kotlin/Native targets (`macosArm64`, `iosSimulatorArm64`,
+ `tvosSimulatorArm64`, and `watchosSimulatorArm64`).
+ * New: `FileSystem.listRecursively()` returns a `Sequence` that includes all of a directory's
+ children, and all of their children recursively. The implementation does a lazy, depth-first
+ traversal.
+ * New: `Path.relativeTo()` computes how to get from one path to another.
+ * New: `Path.root` and `Path.segments`. These APIs decompose a path into its component parts.
+ * New: `FileSystem.listOrNull()` returns a directory's children, or null if the path doesn't
+ reference a readable directory.
+ * New: Option to fail if the file being updated doesn't already exist: `mustExist`. Use this to
+ avoid creating a new file when your intention is to update an existing file.
+ * New: Option to fail if a file being created already exists: `mustCreate`. Use this to avoid
+ updating an existing file when your intention is to create a new file.
+ * Fix: Restore support for Kotlin/JS on browser platforms. We were relying on NodeJS-only features
+ to fetch the local directory separator (`/` or `\`) and temporary directory.
+ * Fix: Don't ignore the caller's specified write offset running Okio on Kotlin/Native on Linux.
+ (`FileHandle.write()` was broken and always appended to the end of the file.)
+
+
+## Version 3.0.0-alpha.10
+
+_2021-09-09_
+
+This release drops the `-multiplatform` suffix on Kotlin Multiplatform artifacts. All artifacts now
+share the same name (like `com.squareup.okio:okio:3.0.0-alpha.10`) for both Kotlin/JVM and Kotlin
+Multiplatform.
+
+ * Fix: Don't crash in `ResourceFileSystem` when classpath `.jar` files have special characters in
+ their paths.
+
+
+## Version 3.0.0-alpha.9
+
+_2021-08-01_
+
+ * New: `ByteString.copyInto()` saves an allocation when extracting data from a `ByteString`.
+ * Fix: Create `FileHandle.protectedSize()` to match other abstract functions.
+ * Fix: Open files in binary mode on Windows. Without this, files that contain `0x1a` will be
+ truncated prematurely.
+
+
+## Version 3.0.0-alpha.8
+
+_2021-07-13_
+
+ * Fix: Don't crash on duplicate entries in a .zip file.
+ * Fix: Change `FileSystem.RESOURCES` to initialize itself lazily.
+
+
+## Version 3.0.0-alpha.7
+
+_2021-07-12_
+
+ * Fix: Change `ResourceFileSystem` to load roots eagerly. We had a bug where `list()` on the root
+ returned an empty list even if resources were present.
+ * New: `FileHandle.reposition()` can seek on a source or sink returned by that `FileHandle`.
+ * New: Move the system resources instance to `FileSystem.RESOURCES`.
+ * Upgrade: [Kotlin 1.5.20][kotlin_1_5_20].
+
+
+## Version 3.0.0-alpha.6
+
+_2021-06-01_
+
+ * New: `FileHandle` supports random access reads, writes, and resizes on files. Create an instance
+ with `FileSystem.openReadOnly()` or `FileSystem.openReadWrite()`.
+ * New: Remove `Cursor` which is obsoleted by `FileHandle`. (`UnsafeCursor` is still around!)
+ * New: Add support for the new intermediate representation (IR) artifacts in Kotlin/JS. We still
+ support the legacy artifact format.
+ * New: Support tvOS (tvosArm64, tvosX64) in multiplatform.
+ * New: Change `ResourceFileSystem` to omit `.class` files when indexing `.zip` files. We expect
+ this to lower the memory footprint of `ResourceFileSystem`.
+ * Fix: Don't crash on background thread access in Kotlin/Native. We had to apply `@SharedImmutable`
+ and run our test suite on a background thread.
+
+
+## Version 3.0.0-alpha.5
+
+_2021-04-27_
+
+ * New: Promote the `ZipFileSystem` and `ResourceFileSystem` to the main Okio module. These are
+ currently JVM-only. The `okio-zipfilesystem` module is no longer published.
+
+
+## Version 3.0.0-alpha.4
+
+_2021-04-14_
+
+ * Fix: Rename internal classes to avoid name collisions. We were seeing problems due to having
+ multiple files named `-Platform.kt`.
+
+
+## Version 3.0.0-alpha.3
+
+_2021-04-06_
+
+ * New: Move `NodeJsFileSystem` into its own module. Having it built-in prevented Okio from working
+ in a browser where there's no synchronous file system API. This is in the `okio-nodefilesystem`
+ artifact.
+
+
+## Version 3.0.0-alpha.2
+
+_2021-03-24_
+
+ * New: Require Java 8+ for Okio 3.x.
+ * New: `Cursor` supports random access reads on a `Source`.
+ * New: `FileSystem.openZip(path)` returns a file system backed by a `.zip` file. This is in the
+ `okio-zipfilesystem` artifact.
+
+
+## Version 3.0.0-alpha.1
+
+_2021-01-07_
+
+* New: Experimental file system API. The `Path`, `FileMetadata`, `FileSystem` and
+ `ForwardingFileSystem` types are subject to API changes in a future release.
+* New: Experimental `okio-fakefilesystem` artifact.
+
+
+## Version 2.10.0
+
+_2021-01-07_
+
+* New: Support Windows (mingwX64) in multiplatform.
+* New: Support watchOS (watchosArm32, watchosArm64, watchosX86) in multiplatform.
+* New: Support `HashingSource`, `HashingSink`, buffer hash functions, and `UnsafeCursor` on non-JVM
+ platforms. Previously these were all JVM-only.
+* New: Implement `Closeable` on `Sink` and `Source` on non-JVM platforms. Okio now includes a
+ multiplatform `okio.Closeable` interface and corresponding `use {}` extension. Closing resources
+ when you're done with them shouldn't be JVM-only!
+* New: `Sink.hashingSink` and `Source.hashingSource` functions that accept
+ `java.security.MessageDigest` and `javax.crypto.Mac` instances. Use these when your hash function
+ isn't built-in.
+* Fix: Don't crash with a `ShortBufferException` in `CipherSink` and `CipherSource` on Android.
+ (Android may throw a `ShortBufferException` even if the buffer is not too short. We now
+ avoid this problem!)
+* Upgrade: [Kotlin 1.4.20][kotlin_1_4_20].
+
+
## Version 2.9.0
_2020-10-04_
@@ -34,10 +337,10 @@ _2020-07-07_
* New: `Pipe.cancel()` causes in-progress and future reads and writes on the pipe to immediately
fail with an `IOException`. The streams may still be canceled normally.
-
+
* New: Enlarge Okio's internal segment pool from a fixed 64 KiB total to 64 KiB per processor. For
- example, on an Intel i9 8-core/16-thread machine the segment pool now uses up to 1 MiB of memory.
-
+ example, on an Intel i9 8-core/16-thread machine the segment pool now uses up to 1 MiB of memory.
+
* New: Migrate from `synchronized` to lock-free when accessing the segment pool. Combined with the
change above we saw throughput increase 3x on a synthetic benchmark designed to create
contention.
@@ -83,20 +386,20 @@ _2019-12-11_
in a crash when subsequent reads encountered an unexpected empty segment.
-### Version 2.4.1
+## Version 2.4.1
_2019-10-04_
* Fix: Don't cache hash code and UTF-8 string in `ByteString` on Kotlin/Native which prevented freezing.
-### Version 2.4.0
+## Version 2.4.0
_2019-08-26_
* New: Upgrade to Kotlin 1.3.50.
-### Version 2.3.0
+## Version 2.3.0
_2019-07-29_
@@ -577,7 +880,21 @@ _2014-04-08_
* Imported from OkHttp.
- [gradle_metadata]: https://blog.gradle.org/gradle-metadata-1.0
- [kotlin_1_4_10]: https://github.com/JetBrains/kotlin/releases/tag/v1.4.10
- [maven_provided]: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html
- [xor_utf8]: https://github.com/square/okio/blob/bbb29c459e5ccf0f286e0b17ccdcacd7ac4bc2a9/okio/src/main/kotlin/okio/Utf8.kt#L302
+[bom]: https://docs.gradle.org/6.2/userguide/platforms.html#sub:bom_import
+[datetime_0_3_0]: https://github.com/Kotlin/kotlinx-datetime/releases/tag/v0.3.0
+[gradle_metadata]: https://blog.gradle.org/gradle-metadata-1.0
+[hierarchical_projects]: https://kotlinlang.org/docs/multiplatform-hierarchy.html
+[kotlin_1_4_10]: https://github.com/JetBrains/kotlin/releases/tag/v1.4.10
+[kotlin_1_4_20]: https://github.com/JetBrains/kotlin/releases/tag/v1.4.20
+[kotlin_1_5_20]: https://github.com/JetBrains/kotlin/releases/tag/v1.5.20
+[kotlin_1_5_31]: https://github.com/JetBrains/kotlin/releases/tag/v1.5.31
+[kotlin_1_6_20]: https://blog.jetbrains.com/kotlin/2022/04/kotlin-1-6-20-released/
+[kotlin_1_8_0]: https://kotlinlang.org/docs/whatsnew18.html
+[kotlin_1_9_0]: https://kotlinlang.org/docs/whatsnew19.html
+[kotlin_1_9_10]: https://github.com/JetBrains/kotlin/releases/tag/v1.9.10
+[kotlin_1_9_21]: https://github.com/JetBrains/kotlin/releases/tag/v1.9.21
+[loom]: https://wiki.openjdk.org/display/loom/Getting+started
+[maven_provided]: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html
+[preview1]: https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
+[watchosX86]: https://blog.jetbrains.com/kotlin/2023/02/update-regarding-kotlin-native-targets/
+[xor_utf8]: https://github.com/square/okio/blob/bbb29c459e5ccf0f286e0b17ccdcacd7ac4bc2a9/okio/src/main/kotlin/okio/Utf8.kt#L302
diff --git a/METADATA b/METADATA
index 659dfff2..c45ecd35 100644
--- a/METADATA
+++ b/METADATA
@@ -14,7 +14,7 @@ third_party {
type: GIT
value: "https://github.com/square/okio/"
}
- version: "47fb0ddcd0bcf768a897dff723a1699341eea10f"
- last_upgrade_date { year: 2021 month: 4 day: 6 }
+ version: "3.7.0"
+ last_upgrade_date { year: 2024 month: 1 day: 3 }
license_type: NOTICE
}
diff --git a/OWNERS b/OWNERS
index 0f11294b..f8765892 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,3 @@
-file:platform/frameworks/base:/core/java/android/app/admin/WorkDeviceExperience_OWNERS
+file:platform/external/lottie:OWNERS
kirit@google.com
olegsh@google.com
diff --git a/android-test/build.gradle b/android-test/build.gradle
deleted file mode 100644
index 56fcda76..00000000
--- a/android-test/build.gradle
+++ /dev/null
@@ -1,73 +0,0 @@
-apply plugin: 'com.android.library'
-apply plugin: 'org.jetbrains.kotlin.android'
-
-buildscript {
- repositories {
- mavenCentral()
- gradlePluginPortal()
- google()
- }
-}
-
-def isIDE = properties.containsKey('android.injected.invoked.from.ide') ||
- (System.getenv("XPC_SERVICE_NAME") ?: "").contains("intellij") ||
- System.getenv("IDEA_INITIAL_DIRECTORY") != null
-
-android {
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- coreLibraryDesugaringEnabled true
- }
-
- kotlinOptions {
- freeCompilerArgs += "-Xmulti-platform"
- }
-
- compileSdkVersion 30
-
- defaultConfig {
- minSdkVersion 15
- targetSdkVersion 30
- versionCode 1
- versionName "1.0"
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-
- // AndroidJUnitRunner wasn't finding tests in multidex artifacts when running on Android 4.0.3.
- // Work around by adding all Okio classes to the keep list. That way they'll be in the main
- // .dx file where TestRequestBuilder will find them.
- multiDexEnabled true
- multiDexKeepProguard file('multidex-config.pro')
- }
-
- if (!isIDE) {
- sourceSets {
- named("androidTest") {
- it.java.srcDirs += [
- project.file("../okio/src/commonMain/kotlin"),
- project.file("../okio/src/commonTest/java"),
- project.file("../okio/src/commonTest/kotlin"),
- project.file("../okio/src/hashFunctions/kotlin"),
- project.file("../okio/src/jvmMain/kotlin"),
- project.file("../okio/src/jvmTest/java"),
- project.file("../okio/src/jvmTest/kotlin"),
- ]
- }
- }
- }
-}
-
-
-dependencies {
- coreLibraryDesugaring deps.android.desugarJdkLibs
- androidTestImplementation deps.androidx.testExtJunit
- androidTestImplementation deps.androidx.testRunner
- androidTestImplementation deps.animalSniffer.annotations
- androidTestImplementation deps.kotlin.stdLib.common
- androidTestImplementation deps.kotlin.test.annotations
- androidTestImplementation deps.kotlin.test.common
- androidTestImplementation deps.kotlin.test.jdk
- androidTestImplementation deps.kotlin.time
- androidTestImplementation deps.test.assertj
- androidTestImplementation deps.test.junit
-}
diff --git a/android-test/build.gradle.kts b/android-test/build.gradle.kts
new file mode 100644
index 00000000..5f9957ba
--- /dev/null
+++ b/android-test/build.gradle.kts
@@ -0,0 +1,69 @@
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+buildscript {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ google()
+ }
+}
+
+val isIDE = properties.containsKey("android.injected.invoked.from.ide") ||
+ (System.getenv("XPC_SERVICE_NAME") ?: "").contains("intellij") ||
+ System.getenv("IDEA_INITIAL_DIRECTORY") != null
+
+android {
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ isCoreLibraryDesugaringEnabled = true
+ }
+
+ kotlinOptions {
+ freeCompilerArgs += "-Xmulti-platform"
+ }
+
+ compileSdkVersion(33)
+
+ defaultConfig {
+ minSdkVersion(15)
+ targetSdkVersion(33)
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ // AndroidJUnitRunner wasn't finding tests in multidex artifacts when running on Android 4.0.3.
+ // Work around by adding all Okio classes to the keep list. That way they'll be in the main
+ // .dx file where TestRequestBuilder will find them.
+ multiDexEnabled = true
+ multiDexKeepProguard = file("multidex-config.pro")
+ }
+
+ if (!isIDE) {
+ sourceSets {
+ named("androidTest") {
+ java.srcDirs(
+ project.file("../okio-fakefilesystem/src/commonMain/kotlin"),
+ project.file("../okio/src/commonMain/kotlin"),
+ project.file("../okio/src/commonTest/java"),
+ project.file("../okio/src/commonTest/kotlin"),
+ project.file("../okio/src/hashFunctions/kotlin"),
+ project.file("../okio/src/jvmMain/kotlin"),
+ project.file("../okio/src/jvmTest/java"),
+ project.file("../okio/src/jvmTest/kotlin")
+ )
+ }
+ }
+ }
+}
+
+dependencies {
+ coreLibraryDesugaring(libs.android.desugar.jdk.libs)
+ androidTestImplementation(libs.androidx.test.ext.junit)
+ androidTestImplementation(libs.androidx.test.runner)
+ androidTestImplementation(libs.kotlin.test)
+ androidTestImplementation(libs.kotlin.time)
+ androidTestImplementation(libs.test.assertj)
+ androidTestImplementation(libs.test.junit)
+}
diff --git a/build-support/build.gradle.kts b/build-support/build.gradle.kts
new file mode 100644
index 00000000..cd0c3df4
--- /dev/null
+++ b/build-support/build.gradle.kts
@@ -0,0 +1,26 @@
+plugins {
+ `kotlin-dsl`
+ `java-gradle-plugin`
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ add("compileOnly", kotlin("gradle-plugin"))
+ add("compileOnly", kotlin("gradle-plugin-api"))
+}
+
+gradlePlugin {
+ plugins {
+ create("build-support") {
+ id = "build-support"
+ implementationClass = "BuildSupport"
+ }
+ }
+}
+
+dependencies {
+ implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21")
+}
diff --git a/build-support/settings.gradle.kts b/build-support/settings.gradle.kts
new file mode 100644
index 00000000..2fcdac38
--- /dev/null
+++ b/build-support/settings.gradle.kts
@@ -0,0 +1 @@
+// empty.
diff --git a/build-support/src/main/kotlin/BuildSupport.kt b/build-support/src/main/kotlin/BuildSupport.kt
new file mode 100644
index 00000000..ad7ad8e4
--- /dev/null
+++ b/build-support/src/main/kotlin/BuildSupport.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class BuildSupport : Plugin<Project> {
+ override fun apply(project: Project) {
+ // Do nothing.
+ }
+}
diff --git a/build-support/src/main/kotlin/bom.kt b/build-support/src/main/kotlin/bom.kt
new file mode 100644
index 00000000..b647714c
--- /dev/null
+++ b/build-support/src/main/kotlin/bom.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import java.util.Locale
+import org.gradle.api.Project
+import org.gradle.api.artifacts.dsl.DependencyConstraintHandler
+import org.gradle.kotlin.dsl.getByType
+import org.gradle.kotlin.dsl.withType
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
+import org.jetbrains.kotlin.gradle.plugin.KotlinJsPluginWrapper
+import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
+import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
+import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget
+import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget
+
+/**
+ * Collect all the root project's multiplatform targets and add them to the BOM.
+ *
+ * Only published subprojects are included.
+ *
+ * This supports Kotlin/Multiplatform and Kotlin/JS subprojects.
+ */
+fun Project.collectBomConstraints() {
+ val bomConstraints: DependencyConstraintHandler = dependencies.constraints
+ rootProject.subprojects {
+ val subproject = this
+
+ subproject.plugins.withId("com.vanniktech.maven.publish.base") {
+ subproject.plugins.withType<KotlinAndroidPluginWrapper> {
+ bomConstraints.api(subproject)
+ }
+
+ subproject.plugins.withType<KotlinJsPluginWrapper> {
+ bomConstraints.api(subproject)
+ }
+
+ subproject.plugins.withType<KotlinMultiplatformPluginWrapper> {
+ subproject.extensions.getByType<KotlinMultiplatformExtension>().targets.all {
+ bomConstraints.api(dependencyConstraint(this))
+ }
+ }
+ }
+ }
+}
+
+/** Returns a string like "com.squareup.okio:okio-iosarm64:3.4.0" for this target. */
+private fun Project.dependencyConstraint(target: KotlinTarget): String {
+ val artifactId = when (target) {
+ is KotlinMetadataTarget -> name
+ is KotlinJsTarget -> "$name-js"
+ else -> "$name-${target.targetName.toLowerCase(Locale.ROOT)}"
+ }
+ return "$group:$artifactId:$version"
+}
+
+private fun DependencyConstraintHandler.api(constraintNotation: Any) =
+ add("api", constraintNotation)
diff --git a/build-support/src/main/kotlin/jvm.kt b/build-support/src/main/kotlin/jvm.kt
new file mode 100644
index 00000000..3e409ef2
--- /dev/null
+++ b/build-support/src/main/kotlin/jvm.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// If true - tests should run for a loom environment.
+val loomEnabled = System.getProperty("loomEnabled", "false").toBoolean()
diff --git a/build-support/src/main/kotlin/kmp.kt b/build-support/src/main/kotlin/kmp.kt
new file mode 100644
index 00000000..50737df7
--- /dev/null
+++ b/build-support/src/main/kotlin/kmp.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// If false - JS targets will not be configured in multiplatform projects.
+val kmpJsEnabled = System.getProperty("kjs", "true").toBoolean()
+
+// If false - Native targets will not be configured in multiplatform projects.
+val kmpNativeEnabled = System.getProperty("knative", "true").toBoolean()
+
+// If false - WASM targets will not be configured in multiplatform projects.
+val kmpWasmEnabled = System.getProperty("kwasm", "true").toBoolean()
diff --git a/build-support/src/main/kotlin/platforms.kt b/build-support/src/main/kotlin/platforms.kt
new file mode 100644
index 00000000..4cfb5ae5
--- /dev/null
+++ b/build-support/src/main/kotlin/platforms.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.get
+import org.gradle.kotlin.dsl.withType
+import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
+import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+fun KotlinMultiplatformExtension.configureOrCreateOkioPlatforms() {
+ jvm {
+ }
+ if (kmpJsEnabled) {
+ configureOrCreateJsPlatforms()
+ }
+ if (kmpNativeEnabled) {
+ configureOrCreateNativePlatforms()
+ }
+ if (kmpWasmEnabled) {
+ configureOrCreateWasmPlatform()
+ }
+}
+
+fun KotlinMultiplatformExtension.configureOrCreateNativePlatforms() {
+ iosX64()
+ iosArm64()
+ iosSimulatorArm64()
+ tvosX64()
+ tvosArm64()
+ tvosSimulatorArm64()
+ watchosArm32()
+ watchosArm64()
+ watchosDeviceArm64()
+ watchosX64()
+ watchosSimulatorArm64()
+ // Required to generate tests tasks: https://youtrack.jetbrains.com/issue/KT-26547
+ linuxX64()
+ linuxArm64()
+ macosX64()
+ macosArm64()
+ mingwX64()
+}
+
+val appleTargets = listOf(
+ "iosArm64",
+ "iosX64",
+ "iosSimulatorArm64",
+ "macosX64",
+ "macosArm64",
+ "tvosArm64",
+ "tvosX64",
+ "tvosSimulatorArm64",
+ "watchosArm32",
+ "watchosArm64",
+ "watchosDeviceArm64",
+ "watchosX64",
+ "watchosSimulatorArm64",
+)
+
+val mingwTargets = listOf(
+ "mingwX64",
+)
+
+val linuxTargets = listOf(
+ "linuxX64",
+ "linuxArm64",
+)
+
+val nativeTargets = appleTargets + linuxTargets + mingwTargets
+
+val wasmTargets = listOf(
+ "wasmJs",
+ "wasmWasi",
+)
+
+/**
+ * Creates a source set for a directory that isn't already a built-in platform. Use this to create
+ * custom shared directories like `nonJvmMain` or `unixMain`.
+ */
+fun NamedDomainObjectContainer<KotlinSourceSet>.createSourceSet(
+ name: String,
+ parent: KotlinSourceSet? = null,
+ children: List<String> = listOf()
+): KotlinSourceSet {
+ val result = create(name)
+
+ if (parent != null) {
+ result.dependsOn(parent)
+ }
+
+ val suffix = when {
+ name.endsWith("Main") -> "Main"
+ name.endsWith("Test") -> "Test"
+ else -> error("unexpected source set name: ${name}")
+ }
+
+ for (childTarget in children) {
+ val childSourceSet = get("${childTarget}$suffix")
+ childSourceSet.dependsOn(result)
+ }
+
+ return result
+}
+
+fun KotlinMultiplatformExtension.configureOrCreateJsPlatforms() {
+ js {
+ compilations.all {
+ kotlinOptions {
+ moduleKind = "umd"
+ sourceMap = true
+ metaInfo = true
+ }
+ }
+ nodejs {
+ testTask {
+ useMocha {
+ timeout = "30s"
+ }
+ }
+ }
+ browser {
+ }
+ }
+}
+
+fun KotlinMultiplatformExtension.configureOrCreateWasmPlatform(
+ js: Boolean = true,
+ wasi: Boolean = true,
+) {
+ if (js) {
+ wasmJs {
+ nodejs()
+ }
+ }
+ if (wasi) {
+ wasmWasi {
+ nodejs()
+ }
+ }
+}
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 4923686e..00000000
--- a/build.gradle
+++ /dev/null
@@ -1,120 +0,0 @@
-buildscript {
- // If false - JS targets will not be configured in multiplatform projects.
- ext.kmpJsEnabled = Boolean.parseBoolean(System.getProperty('kjs', 'true'))
-
- // If false - Native targets will not be configured in multiplatform projects.
- ext.kmpNativeEnabled = Boolean.parseBoolean(System.getProperty('knative', 'true'))
-
- ext.versions = [
- 'kotlin': '1.4.20',
- 'jmhPlugin': '0.5.0',
- 'animalSnifferPlugin': '1.5.0',
- 'dokka': '1.4.20',
- 'jmh': '1.23',
- 'animalSniffer': '1.16',
- 'junit': '4.12',
- 'assertj': '1.7.0',
- 'shadowPlugin': '5.2.0',
- 'spotless': '5.8.2',
- 'ktlint': '0.40.0',
- 'bndPlugin': '5.1.2'
- ]
-
- ext.deps = [
- 'android': [
- 'gradlePlugin': "com.android.tools.build:gradle:4.1.1",
- 'desugarJdkLibs': "com.android.tools:desugar_jdk_libs:1.1.1",
- ],
- 'androidx': [
- 'testExtJunit': "androidx.test.ext:junit:1.1.2",
- 'testRunner': "androidx.test:runner:1.3.0",
- ],
- 'kotlin': [
- 'gradlePlugin': "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}",
- 'stdLib': [
- 'common': "org.jetbrains.kotlin:kotlin-stdlib-common",
- 'jdk8': "org.jetbrains.kotlin:kotlin-stdlib-jdk8",
- 'jdk7': "org.jetbrains.kotlin:kotlin-stdlib-jdk7",
- 'jdk6': "org.jetbrains.kotlin:kotlin-stdlib",
- 'js': "org.jetbrains.kotlin:kotlin-stdlib-js",
- ],
- 'test': [
- 'common': "org.jetbrains.kotlin:kotlin-test-common",
- 'annotations': "org.jetbrains.kotlin:kotlin-test-annotations-common",
- 'jdk': "org.jetbrains.kotlin:kotlin-test-junit",
- 'js': "org.jetbrains.kotlin:kotlin-test-js",
- ],
- 'time': 'org.jetbrains.kotlinx:kotlinx-datetime:0.1.1',
- ],
- 'jmh': [
- 'gradlePlugin': "me.champeau.gradle:jmh-gradle-plugin:${versions.jmhPlugin}",
- 'core': "org.openjdk.jmh:jmh-core:${versions.jmh}",
- 'generator': "org.openjdk.jmh:jmh-generator-annprocess:${versions.jmh}",
- ],
- 'animalSniffer': [
- 'gradlePlugin': "ru.vyarus:gradle-animalsniffer-plugin:${versions.animalSnifferPlugin}",
- 'annotations': "org.codehaus.mojo:animal-sniffer-annotations:${versions.animalSniffer}",
- ],
- 'japicmp': 'me.champeau.gradle:japicmp-gradle-plugin:0.2.8',
- 'dokka': "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}",
- 'shadow': "com.github.jengelman.gradle.plugins:shadow:${versions.shadowPlugin}",
- 'spotless': "com.diffplug.spotless:spotless-plugin-gradle:${versions.spotless}",
- 'bnd': "biz.aQute.bnd:biz.aQute.bnd.gradle:${versions.bndPlugin}",
- 'test': [
- 'junit': "junit:junit:${versions.junit}",
- 'assertj': "org.assertj:assertj-core:${versions.assertj}",
- ]
- ]
-
- dependencies {
- classpath deps.android.gradlePlugin
- classpath deps.kotlin.gradlePlugin
- classpath deps.animalSniffer.gradlePlugin
- classpath deps.japicmp
- classpath deps.dokka
- classpath deps.shadow
- classpath deps.jmh.gradlePlugin
- classpath deps.spotless
- classpath deps.bnd
- // https://github.com/melix/japicmp-gradle-plugin/issues/36
- classpath 'com.google.guava:guava:28.2-jre'
- }
-
- repositories {
- mavenCentral()
- gradlePluginPortal()
- jcenter()
- google()
- maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
- maven { url 'https://kotlin.bintray.com/kotlinx/' }
- }
-}
-
-// when scripts are applied the buildscript classes are not accessible directly therefore we save the class here to make it accessible
-ext.bndBundleTaskConventionClass = aQute.bnd.gradle.BundleTaskConvention
-
-allprojects {
- group = GROUP
- version = VERSION_NAME
-}
-
-subprojects {
- repositories {
- mavenCentral()
- jcenter()
- google()
- maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
- maven { url 'https://kotlin.bintray.com/kotlinx/' }
- }
-
- apply plugin: "com.diffplug.spotless"
-
- spotless {
- kotlin {
- target("**/*.kt")
- ktlint(versions.ktlint).userData([indent_size: '2'])
- trimTrailingWhitespace()
- endWithNewline()
- }
- }
-}
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 00000000..f564d88a
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,248 @@
+import aQute.bnd.gradle.BundleTaskConvention
+import com.diffplug.gradle.spotless.SpotlessExtension
+import com.vanniktech.maven.publish.MavenPublishBaseExtension
+import com.vanniktech.maven.publish.SonatypeHost
+import groovy.util.Node
+import groovy.util.NodeList
+import java.nio.charset.StandardCharsets
+import org.gradle.api.tasks.testing.logging.TestExceptionFormat
+import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED
+import org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED
+import org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED
+import org.gradle.api.tasks.testing.logging.TestLogEvent.STARTED
+import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
+import org.jetbrains.dokka.gradle.DokkaTask
+import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
+import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
+import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ id("build-support").apply(false)
+}
+
+buildscript {
+ dependencies {
+ classpath(libs.android.gradle.plugin)
+ classpath(libs.dokka)
+ classpath(libs.jmh.gradle.plugin)
+ classpath(libs.binaryCompatibilityValidator)
+ classpath(libs.spotless)
+ classpath(libs.bnd)
+ classpath(libs.vanniktech.publish.plugin)
+ }
+
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ google()
+ }
+}
+
+apply(plugin = "com.vanniktech.maven.publish.base")
+
+// When scripts are applied the buildscript classes are not accessible directly therefore we save
+// the class here to make it accessible.
+ext.set("bndBundleTaskConventionClass", BundleTaskConvention::class.java)
+
+allprojects {
+ group = project.property("GROUP") as String
+ version = project.property("VERSION_NAME") as String
+
+ repositories {
+ mavenCentral()
+ google()
+ }
+
+ tasks.withType<DokkaTask>().configureEach {
+ dokkaSourceSets.configureEach {
+ reportUndocumented.set(false)
+ skipDeprecated.set(true)
+ jdkVersion.set(8)
+ perPackageOption {
+ matchingRegex.set("com\\.squareup.okio.*")
+ suppress.set(true)
+ }
+ perPackageOption {
+ matchingRegex.set("okio\\.internal.*")
+ suppress.set(true)
+ }
+ }
+
+ if (name == "dokkaHtml") {
+ outputDirectory.set(file("${rootDir}/docs/3.x/${project.name}"))
+ pluginsMapConfiguration.set(
+ mapOf(
+ "org.jetbrains.dokka.base.DokkaBase" to """
+ {
+ "customStyleSheets": [
+ "${rootDir.toString().replace('\\', '/')}/docs/css/dokka-logo.css"
+ ],
+ "customAssets" : [
+ "${rootDir.toString().replace('\\', '/')}/docs/images/icon-square.png"
+ ]
+ }
+ """.trimIndent()
+ )
+ )
+ }
+ }
+
+ plugins.withId("com.vanniktech.maven.publish.base") {
+ configure<PublishingExtension> {
+ repositories {
+ /**
+ * Want to push to an internal repository for testing? Set the following properties in
+ * `~/.gradle/gradle.properties`.
+ *
+ * internalMavenUrl=YOUR_INTERNAL_MAVEN_REPOSITORY_URL
+ * internalMavenUsername=YOUR_USERNAME
+ * internalMavenPassword=YOUR_PASSWORD
+ */
+ val internalUrl = providers.gradleProperty("internalUrl")
+ if (internalUrl.isPresent) {
+ maven {
+ name = "internal"
+ setUrl(internalUrl)
+ credentials(PasswordCredentials::class)
+ }
+ }
+ }
+ }
+ val publishingExtension = extensions.getByType(PublishingExtension::class.java)
+ configure<MavenPublishBaseExtension> {
+ publishToMavenCentral(SonatypeHost.S01, automaticRelease = true)
+ signAllPublications()
+ pom {
+ description.set("A modern I/O library for Android, Java, and Kotlin Multiplatform.")
+ name.set(project.name)
+ url.set("https://github.com/square/okio/")
+ licenses {
+ license {
+ name.set("The Apache Software License, Version 2.0")
+ url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
+ distribution.set("repo")
+ }
+ }
+ scm {
+ url.set("https://github.com/square/okio/")
+ connection.set("scm:git:git://github.com/square/okio.git")
+ developerConnection.set("scm:git:ssh://git@github.com/square/okio.git")
+ }
+ developers {
+ developer {
+ id.set("square")
+ name.set("Square, Inc.")
+ }
+ }
+ }
+
+ // Configure the kotlinMultiplatform artifact to depend on the JVM artifact in pom.xml only.
+ // This hack allows Maven users to continue using our original Okio artifact names (like
+ // com.squareup.okio:okio:3.x.y) even though we changed that artifact from JVM-only to Kotlin
+ // Multiplatform. Note that module.json doesn't need this hack.
+ val mavenPublications = publishingExtension.publications.withType<MavenPublication>()
+ mavenPublications.configureEach {
+ if (name != "jvm") return@configureEach
+ val jvmPublication = this
+ val kmpPublication = mavenPublications.getByName("kotlinMultiplatform")
+ kmpPublication.pom.withXml {
+ val root = asNode()
+ val dependencies = (root["dependencies"] as NodeList).firstOrNull() as Node?
+ ?: root.appendNode("dependencies")
+ for (child in dependencies.children().toList()) {
+ dependencies.remove(child as Node)
+ }
+ dependencies.appendNode("dependency").apply {
+ appendNode("groupId", jvmPublication.groupId)
+ appendNode("artifactId", jvmPublication.artifactId)
+ appendNode("version", jvmPublication.version)
+ appendNode("scope", "compile")
+ }
+ }
+ }
+ }
+ }
+}
+
+subprojects {
+ apply(plugin = "com.diffplug.spotless")
+ configure<SpotlessExtension> {
+ kotlin {
+ target("**/*.kt")
+ ktlint(libs.versions.ktlint.get())
+ }
+ }
+
+ tasks.withType<KotlinCompile>().configureEach {
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_1_8.toString()
+ @Suppress("SuspiciousCollectionReassignment")
+ freeCompilerArgs += "-Xjvm-default=all"
+ }
+ }
+
+ tasks.withType<JavaCompile> {
+ options.encoding = StandardCharsets.UTF_8.toString()
+ sourceCompatibility = JavaVersion.VERSION_1_8.toString()
+ targetCompatibility = JavaVersion.VERSION_1_8.toString()
+ }
+
+ val testJavaVersion = System.getProperty("test.java.version", "19").toInt()
+ tasks.withType<Test> {
+ val javaToolchains = project.extensions.getByType<JavaToolchainService>()
+ javaLauncher.set(javaToolchains.launcherFor {
+ languageVersion.set(JavaLanguageVersion.of(testJavaVersion))
+ })
+
+ testLogging {
+ events(STARTED, PASSED, SKIPPED, FAILED)
+ exceptionFormat = TestExceptionFormat.FULL
+ showStandardStreams = false
+ }
+
+ if (loomEnabled) {
+ jvmArgs = jvmArgs!! + listOf(
+ "-Djdk.tracePinnedThread=full",
+ "--enable-preview",
+ "-DloomEnabled=true"
+ )
+ }
+ }
+
+ tasks.withType<AbstractArchiveTask>().configureEach {
+ isPreserveFileTimestamps = false
+ isReproducibleFileOrder = true
+ }
+
+ normalization {
+ runtimeClasspath {
+ metaInf {
+ ignoreAttribute("Bnd-LastModified")
+ }
+ }
+ }
+}
+
+/**
+ * Select a NodeJS version with WASI and WASM GC.
+ * https://github.com/Kotlin/kotlin-wasm-examples/blob/main/wasi-example/build.gradle.kts
+ */
+plugins.withType<NodeJsRootPlugin> {
+ extensions.getByType<NodeJsRootExtension>().apply {
+ if (DefaultNativePlatform.getCurrentOperatingSystem().isWindows) {
+ // We're waiting for a Windows build of NodeJS that can do WASM GC + WASI.
+ nodeVersion = "21.4.0"
+ } else {
+ nodeVersion = "21.0.0-v8-canary202309143a48826a08"
+ nodeDownloadBaseUrl = "https://nodejs.org/download/v8-canary"
+ }
+ }
+ // Suppress an error because yarn doesn't like our Node version string.
+ // warning You are using Node "21.0.0-v8-canary202309143a48826a08" which is not supported and
+ // may encounter bugs or unexpected behavior.
+ // error typescript@5.0.4: The engine "node" is incompatible with this module.
+ tasks.withType<KotlinNpmInstallTask>().all {
+ args += "--ignore-engines"
+ }
+}
diff --git a/docs/css/app.css b/docs/css/app.css
index 48136b7e..2667de1e 100644
--- a/docs/css/app.css
+++ b/docs/css/app.css
@@ -27,7 +27,7 @@ body, input {
font-family: cash-market,"Helvetica Neue",helvetica,sans-serif;
line-height: normal;
font-weight: bold;
- color: #353535;
+ color: var(--md-default-fg-color);
}
button.dl {
diff --git a/docs/css/dokka-logo.css b/docs/css/dokka-logo.css
index ae3a99e6..d05d9701 100644
--- a/docs/css/dokka-logo.css
+++ b/docs/css/dokka-logo.css
@@ -1,6 +1,3 @@
#logo {
- background-image: url(../images/logo-square.png);
- background-size: auto;
- padding-top: unset;
- height: 60px;
+ display: none;
}
diff --git a/docs/file_system.md b/docs/file_system.md
new file mode 100644
index 00000000..49207d9f
--- /dev/null
+++ b/docs/file_system.md
@@ -0,0 +1,125 @@
+File System
+===========
+
+Okio's file system is designed to be easy, testable, multiplatform, and efficient.
+
+### Easy
+
+Reading and writing files is concise yet flexible.
+
+```kotlin
+val path = "README.md".toPath()
+
+val readmeContent = FileSystem.SYSTEM.read(path) {
+ readUtf8()
+}
+
+val updatedContent = readmeContent.replace("red", "blue")
+
+FileSystem.SYSTEM.write(path) {
+ writeUtf8(updatedContent)
+}
+```
+
+
+### Testable
+
+It's easy to swap out the real file system with a fake. This makes tests run faster and more
+reliably.
+
+```kotlin
+val fileSystem = FakeFileSystem()
+val userHome = "/Users/sandy".toPath()
+val gitConfig = userHome / ".gitconfig"
+
+fileSystem.createDirectories(userHome)
+val original = """
+ |[user]
+ | email = sandy@example.com
+ |""".trimMargin()
+fileSystem.write(gitConfig) { writeUtf8(original) }
+
+GitConfigFixer(fileSystem).fix(userHome)
+
+val expected = """
+ |[user]
+ | email = sandy@example.com
+ |[diff]
+ | renames = true
+ | indentHeuristic = on
+ """.trimIndent()
+assertEquals(expected, fileSystem.read(gitConfig) { readUtf8() })
+```
+
+With `ForwardingFileSystem` you can easily inject faults to confirm your program is graceful even
+when the user's disk fills up.
+
+
+### Multiplatform
+
+Okio’s `Path` class supports Windows-style (like `C:\autoexec.bat`) and UNIX-style paths
+(like `/etc/passwd`). It supports manipulating Windows paths on UNIX, and UNIX paths on Windows.
+
+The system `FileSystem` abstracts over these platform APIs:
+
+ * Android API levels <26: [java.io.File](https://developer.android.com/reference/java/io/File)
+ * Java and Android API level 26+: [java.nio.file](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/file/FileSystem.html)
+ * Linux: [man pages](https://www.kernel.org/doc/man-pages/)
+ * UNIX: [stdio.h](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html)
+ * Windows: [fileapi.h](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/)
+ * Node.js: [file system](https://nodejs.org/api/fs.html)
+
+
+### Efficient
+
+Read and write operations integrate with Okio buffers to reduce the number of system calls.
+
+It exposes high-level operations like `atomicMove()` and `metadata` to get the OS to do all the work
+when appropriate.
+
+
+## Known Issues
+
+
+Okio's implementation is constrained by the capabilities its underlying APIs. This page is an
+overview of these limitations.
+
+
+### All Platforms
+
+ * There are no APIs for file permissions, watches, volume management, memory mapping, or locking.
+ * Paths that cannot be represented as UTF-8 strings are unsupported. The underlying APIs that Okio
+ calls through, including `java.io.File`, all treat paths as strings.
+
+
+### Kotlin/JVM
+
+#### On Android, API level less than 26:
+
+ * Creating and accessing symlinks is unsupported.
+
+
+#### On Windows:
+
+ * `FileSystem.atomicMove()` fails if the target file already exists.
+
+
+### Kotlin/Native
+
+ * FakeFileSystem does not support concurrent use. We are [holding off on this][fake_fs_concurrency]
+ until the upcoming memory model is released.
+
+#### On Windows:
+
+ * Creating and accessing symlinks is unsupported.
+
+
+### Kotlin/JS
+
+ * NodeJsFileSystem's `source()` and `sink()` cannot access UNIX pipes.
+ * Instead of returning null, `NodeJsFileSystem.metadataOrNull()` throws `IOException` if the path
+ is invalid. (In the Node.js API there's no mechanism to differentiate between a failure to read
+ a valid path and a rejection of an invalid path.)
+
+
+[fake_fs_concurrency]: https://github.com/square/okio/issues/950
diff --git a/docs/index.md b/docs/index.md
index d69c409d..4fb96f5e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -77,1035 +77,44 @@ works and how Okio does it.
[Ok Multiplatform!][ok_multiplatform_talk] ([slides][ok_multiplatform_slides]): How we changed
Okio’s implementation language from Java to Kotlin.
+[Nerding Out On Okio][apis_talk]: The story of the Okio APIs, their design and tradeoffs, as well
+as implementation notes with animated marbles diagrams.
+
Requirements
------------
-Okio supports Android 4.0.3+ (API level 15+) and Java 7+.
+Okio 2.x supports Android 4.0.3+ (API level 15+) and Java 7+.
+
+Okio 3.x supports Android 4.0.3+ (API level 15+) and Java 8+.
Okio depends on the [Kotlin standard library][kotlin]. It is a small library with strong
backward-compatibility.
-Recipes
--------
-
-We've written some recipes that demonstrate how to solve common problems with
-Okio. Read through them to learn about how everything works together.
-Cut-and-paste these examples freely; that's what they're for.
-
-### Read a text file line-by-line ([Java][ReadFileLineByLine]/[Kotlin][ReadFileLineByLineKt])
-
-Use `Okio.source(File)` to open a source stream to read a file. The returned
-`Source` interface is very small and has limited uses. Instead we wrap the
-source with a buffer. This has two benefits:
-
- * **It makes the API more powerful.** Instead of the basic methods offered by
- `Source`, `BufferedSource` has dozens of methods to address most common
- problems concisely.
-
- * **It makes your program run faster.** Buffering allows Okio to get more done
- with fewer I/O operations.
-
-Each `Source` that is opened needs to be closed. The code that opens the stream
-is responsible for making sure it is closed.
-
-=== "Java"
-
- Here we use Java's `try` blocks to close our sources automatically.
-
- ```java
- public void readLines(File file) throws IOException {
- try (Source fileSource = Okio.source(file);
- BufferedSource bufferedSource = Okio.buffer(fileSource)) {
-
- while (true) {
- String line = bufferedSource.readUtf8Line();
- if (line == null) break;
-
- if (line.contains("square")) {
- System.out.println(line);
- }
- }
-
- }
- }
- ```
-
-=== "Kotlin"
-
- Note that static `Okio` methods become extension functions (`Okio.source(file)` =>
- `file.source()`), and `use` is used to automatically close the streams:
-
- ```kotlin
- @Throws(IOException::class)
- fun readLines(file: File) {
- file.source().use { fileSource ->
- fileSource.buffer().use { bufferedFileSource ->
- while (true) {
- val line = bufferedFileSource.readUtf8Line() ?: break
- if ("square" in line) {
- println(line)
- }
- }
- }
- }
- }
- ```
-
-The `readUtf8Line()` API reads all of the data until the next line delimiter –
-either `\n`, `\r\n`, or the end of the file. It returns that data as a string,
-omitting the delimiter at the end. When it encounters empty lines the method
-will return an empty string. If there isn’t any more data to read it will
-return null.
-
-The above program can be written more compactly by inlining the `fileSource`
-variable and by using a fancy `for` loop instead of a `while`:
-
-```java
-public void readLines(File file) throws IOException {
- try (BufferedSource source = Okio.buffer(Okio.source(file))) {
- for (String line; (line = source.readUtf8Line()) != null; ) {
- if (line.contains("square")) {
- System.out.println(line);
- }
- }
- }
-}
-```
-
-In Kotlin, we can wrap invocations of `source.readUtf8Line()` into the `generateSequence` builder to
-create a sequence of lines that will end once null is returned. Plus, transforming streams is easy
-thanks to the extension functions:
-
-```kotlin
-@Throws(IOException::class)
-fun readLines(file: File) {
- file.source().buffer().use { source ->
- generateSequence { source.readUtf8Line() }
- .filter { line -> "square" in line }
- .forEach(::println)
- }
-}
-```
-
-The `readUtf8Line()` method is suitable for parsing most files. For certain
-use-cases you may also consider `readUtf8LineStrict()`. It is similar but it
-requires that each line is terminated by `\n` or `\r\n`. If it encounters the
-end of the file before that it will throw an `EOFException`. The strict variant
-also permits a byte limit to defend against malformed input.
-
-```java
-public void readLines(File file) throws IOException {
- try (BufferedSource source = Okio.buffer(Okio.source(file))) {
- while (!source.exhausted()) {
- String line = source.readUtf8LineStrict(1024L);
- if (line.contains("square")) {
- System.out.println(line);
- }
- }
- }
-}
-```
-
-Here's a similar example written in Kotlin:
-
-```kotlin
-@Throws(IOException::class)
-fun readLines(file: File) {
- file.source().buffer().use { source ->
- while (!source.exhausted()) {
- val line = source.readUtf8LineStrict(1024)
- if ("square" in line) {
- println(line)
- }
- }
- }
-}
-```
-
-### Write a text file ([Java][WriteFile]/[Kotlin][WriteFileKt])
-
-Above we used a `Source` and a `BufferedSource` to read a file. To write, we use
-a `Sink` and a `BufferedSink`. The advantages of buffering are the same: a more
-capable API and better performance.
-
-```java
-public void writeEnv(File file) throws IOException {
- try (Sink fileSink = Okio.sink(file);
- BufferedSink bufferedSink = Okio.buffer(fileSink)) {
-
- for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
- bufferedSink.writeUtf8(entry.getKey());
- bufferedSink.writeUtf8("=");
- bufferedSink.writeUtf8(entry.getValue());
- bufferedSink.writeUtf8("\n");
- }
-
- }
-}
-```
-
-There isn’t an API to write a line of input; instead we manually insert our own
-newline character. Most programs should hardcode `"\n"` as the newline
-character. In rare situations you may use `System.lineSeparator()` instead of
-`"\n"`: it returns `"\r\n"` on Windows and `"\n"` everywhere else.
-
-We can write the above program more compactly by inlining the `fileSink`
-variable and by taking advantage of method chaining:
-
-=== "Java"
-
- ```Java
- public void writeEnv(File file) throws IOException {
- try (BufferedSink sink = Okio.buffer(Okio.sink(file))) {
- for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
- sink.writeUtf8(entry.getKey())
- .writeUtf8("=")
- .writeUtf8(entry.getValue())
- .writeUtf8("\n");
- }
- }
- }
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- @Throws(IOException::class)
- fun writeEnv(file: File) {
- file.sink().buffer().use { sink ->
- for ((key, value) in System.getenv()) {
- sink.writeUtf8(key)
- sink.writeUtf8("=")
- sink.writeUtf8(value)
- sink.writeUtf8("\n")
- }
- }
- }
- ```
-
-In the above code we make four calls to `writeUtf8()`. Making four calls is
-more efficient than the code below because the VM doesn’t have to create and
-garbage collect a temporary string.
-
-```java
-sink.writeUtf8(entry.getKey() + "=" + entry.getValue() + "\n"); // Slower!
-```
-
-### UTF-8 ([Java][ExploreCharsets]/[Kotlin][ExploreCharsetsKt])
-
-In the above APIs you can see that Okio really likes UTF-8. Early computer
-systems suffered many incompatible character encodings: ISO-8859-1, ShiftJIS,
-ASCII, EBCDIC, etc. Writing software to support multiple character sets was
-awful and we didn’t even have emoji! Today we're lucky that the world has
-standardized on UTF-8 everywhere, with some rare uses of other charsets in
-legacy systems.
-
-If you need another character set, `readString()` and `writeString()` are there
-for you. These methods require that you specify a character set. Otherwise you
-may accidentally create data that is only readable by the local computer. Most
-programs should use the UTF-8 methods only.
-
-When encoding strings you need to be mindful of the different ways that strings
-are represented and encoded. When a glyph has an accent or another adornment
-it may be represented as a single complex code point (`é`) or as a simple code
-point (`e`) followed by its modifiers (`´`). When the entire glyph is a single
-code point that’s called [NFC][nfc]; when it’s multiple it’s [NFD][nfd].
-
-Though we use UTF-8 whenever we read or write strings in I/O, when they are in
-memory Java Strings use an obsolete character encoding called UTF-16. It is a
-bad encoding because it uses a 16-bit `char` for most characters, but some don’t
-fit. In particular, most emoji use two Java chars. This is problematic because
-`String.length()` returns a surprising result: the number of UTF-16 chars and
-not the natural number of glyphs.
-
-| | Café 🍩 | Café 🍩 |
-| --------------------: | :---------------------------| :------------------------------|
-| Form | [NFC][nfc] | [NFD][nfd] |
-| Code Points | `c  a  f  é    ␣   🍩     ` | `c  a  f  e  ´    ␣   🍩     ` |
-| UTF-8 bytes | `43 61 66 c3a9 20 f09f8da9` | `43 61 66 65 cc81 20 f09f8da9` |
-| String.codePointCount | 6 | 7 |
-| String.length | 7 | 8 |
-| Utf8.size | 10 | 11 |
-
-For the most part Okio lets you ignore these problems and focus on your data.
-But when you need them, there are convenient APIs for dealing with low-level
-UTF-8 strings.
-
-Use `Utf8.size()` to count the number of bytes required to encode a string as
-UTF-8 without actually encoding it. This is handy in length-prefixed encodings
-like protocol buffers.
-
-Use `BufferedSource.readUtf8CodePoint()` to read a single variable-length code
-point, and `BufferedSink.writeUtf8CodePoint()` to write one.
-
-### Golden Values ([Java][GoldenValue]/[Kotlin][GoldenValueKt])
-
-Okio likes testing. The library itself is heavily tested, and it has features
-that are often helpful when testing application code. One pattern we’ve found to
-be quite useful is “golden value” testing. The goal of such tests is to confirm
-that data encoded with earlier versions of a program can safely be decoded by
-the current program.
-
-We’ll illustrate this by encoding a value using Java Serialization. Though we
-must disclaim that Java Serialization is an awful encoding system and most
-programs should prefer other formats like JSON or protobuf! In any case, here’s
-a method that takes an object, serializes it, and returns the result as a
-`ByteString`:
-
-=== "Java"
-
- ```Java
- private ByteString serialize(Object o) throws IOException {
- Buffer buffer = new Buffer();
- try (ObjectOutputStream objectOut = new ObjectOutputStream(buffer.outputStream())) {
- objectOut.writeObject(o);
- }
- return buffer.readByteString();
- }
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- @Throws(IOException::class)
- private fun serialize(o: Any?): ByteString {
- val buffer = Buffer()
- ObjectOutputStream(buffer.outputStream()).use { objectOut ->
- objectOut.writeObject(o)
- }
- return buffer.readByteString()
- }
- ```
-
-There’s a lot going on here.
-
-1. We create a buffer as a holding space for our serialized data. It’s a convenient
- replacement for `ByteArrayOutputStream`.
-
-2. We ask the buffer for its output stream. Writes to a buffer or its output stream
- always append data to the end of the buffer.
-
-3. We create an `ObjectOutputStream` (the encoding API for Java serialization) and
- write our object. The try block takes care of closing the stream for us. Note
- that closing a buffer has no effect.
-
-4. Finally we read a byte string from the buffer. The `readByteString()` method
- allows us to specify how many bytes to read; here we don’t specify a count in
- order to read the entire thing. Reads from a buffer always consume data from
- the front of the buffer.
-
-With our `serialize()` method handy we are ready to compute and print a golden
-value.
-
-=== "Java"
-
- ```Java
- Point point = new Point(8.0, 15.0);
- ByteString pointBytes = serialize(point);
- System.out.println(pointBytes.base64());
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- val point = Point(8.0, 15.0)
- val pointBytes = serialize(point)
- println(pointBytes.base64())
- ```
-
-We print the `ByteString` as [base64][base64] because it’s a compact format
-that’s suitable for embedding in a test case. The program prints this:
-
-```
-rO0ABXNyAB5va2lvLnNhbXBsZXMuR29sZGVuVmFsdWUkUG9pbnTdUW8rMji1IwIAAkQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA
-```
-
-That’s our golden value! We can embed it in our test case using base64 again
-to convert it back into a `ByteString`:
-
-=== "Java"
-
- ```Java
- ByteString goldenBytes = ByteString.decodeBase64("rO0ABXNyAB5va2lvLnNhbXBsZ"
- + "XMuR29sZGVuVmFsdWUkUG9pbnTdUW8rMji1IwIAAkQAAXhEAAF5eHBAIAAAAAAAAEAuA"
- + "AAAAAAA");
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- val goldenBytes = ("rO0ABXNyACRva2lvLnNhbXBsZXMuS290bGluR29sZGVuVmFsdWUkUG9pbnRF9yaY7cJ9EwIAA" +
- "kQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA").decodeBase64()
- ```
-
-The next step is to deserialize the `ByteString` back into our value class. This
-method reverses the `serialize()` method above: we append a byte string to a
-buffer then consume it using an `ObjectInputStream`:
-
-=== "Java"
-
- ```Java
- private Object deserialize(ByteString byteString) throws IOException, ClassNotFoundException {
- Buffer buffer = new Buffer();
- buffer.write(byteString);
- try (ObjectInputStream objectIn = new ObjectInputStream(buffer.inputStream())) {
- return objectIn.readObject();
- }
- }
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- @Throws(IOException::class, ClassNotFoundException::class)
- private fun deserialize(byteString: ByteString): Any? {
- val buffer = Buffer()
- buffer.write(byteString)
- ObjectInputStream(buffer.inputStream()).use { objectIn ->
- return objectIn.readObject()
- }
- }
- ```
-
-Now we can test the decoder against the golden value:
-
-=== "Java"
-
- ```Java
- ByteString goldenBytes = ByteString.decodeBase64("rO0ABXNyAB5va2lvLnNhbXBsZ"
- + "XMuR29sZGVuVmFsdWUkUG9pbnTdUW8rMji1IwIAAkQAAXhEAAF5eHBAIAAAAAAAAEAuA"
- + "AAAAAAA");
- Point decoded = (Point) deserialize(goldenBytes);
- assertEquals(new Point(8.0, 15.0), decoded);
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- val goldenBytes = ("rO0ABXNyACRva2lvLnNhbXBsZXMuS290bGluR29sZGVuVmFsdWUkUG9pbnRF9yaY7cJ9EwIAA" +
- "kQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA").decodeBase64()!!
- val decoded = deserialize(goldenBytes) as Point
- assertEquals(point, decoded)
- ```
-
-With this test we can change the serialization of the `Point` class without
-breaking compatibility.
-
-
-### Write a binary file ([Java][BitmapEncoder]/[Kotlin][BitmapEncoderKt])
-
-Encoding a binary file is not unlike encoding a text file. Okio uses the same
-`BufferedSink` and `BufferedSource` bytes for both. This is handy for binary
-formats that include both byte and character data.
-
-Writing binary data is more hazardous than text because if you make a mistake it
-is often quite difficult to diagnose. Avoid such mistakes by being careful
-around these traps:
-
- * **The width of each field.** This is the number of bytes used. Okio doesn't
- include a mechanism to emit partial bytes. If you need that, you’ll need to
- do your own bit shifting and masking before writing.
-
- * **The endianness of each field.** All fields that have more than one byte
- have _endianness_: whether the bytes are ordered most-significant to least
- (big endian) or least-significant to most (little endian). Okio uses the `Le`
- suffix for little-endian methods; methods without a suffix are big-endian.
-
- * **Signed vs. Unsigned.** Java doesn’t have unsigned primitive types (except
- for `char`!) so coping with this is often something that happens at the
- application layer. To make this a little easier Okio accepts `int` types for
- `writeByte()` and `writeShort()`. You can pass an “unsigned” byte like 255
- and Okio will do the right thing.
-
-| Method | Width | Endianness | Value | Encoded Value |
-| :----------- | ----: | :--------- | --------------: | :------------------------ |
-| writeByte | 1 | | 3 | `03` |
-| writeShort | 2 | big | 3 | `00 03` |
-| writeInt | 4 | big | 3 | `00 00 00 03` |
-| writeLong | 8 | big | 3 | `00 00 00 00 00 00 00 03` |
-| writeShortLe | 2 | little | 3 | `03 00` |
-| writeIntLe | 4 | little | 3 | `03 00 00 00` |
-| writeLongLe | 8 | little | 3 | `03 00 00 00 00 00 00 00` |
-| writeByte | 1 | | Byte.MAX_VALUE | `7f` |
-| writeShort | 2 | big | Short.MAX_VALUE | `7f ff` |
-| writeInt | 4 | big | Int.MAX_VALUE | `7f ff ff ff` |
-| writeLong | 8 | big | Long.MAX_VALUE | `7f ff ff ff ff ff ff ff` |
-| writeShortLe | 2 | little | Short.MAX_VALUE | `ff 7f` |
-| writeIntLe | 4 | little | Int.MAX_VALUE | `ff ff ff 7f` |
-| writeLongLe | 8 | little | Long.MAX_VALUE | `ff ff ff ff ff ff ff 7f` |
-
-This code encodes a bitmap following the [BMP file format][bmp].
-
-=== "Java"
-
- ```Java
- void encode(Bitmap bitmap, BufferedSink sink) throws IOException {
- int height = bitmap.height();
- int width = bitmap.width();
-
- int bytesPerPixel = 3;
- int rowByteCountWithoutPadding = (bytesPerPixel * width);
- int rowByteCount = ((rowByteCountWithoutPadding + 3) / 4) * 4;
- int pixelDataSize = rowByteCount * height;
- int bmpHeaderSize = 14;
- int dibHeaderSize = 40;
-
- // BMP Header
- sink.writeUtf8("BM"); // ID.
- sink.writeIntLe(bmpHeaderSize + dibHeaderSize + pixelDataSize); // File size.
- sink.writeShortLe(0); // Unused.
- sink.writeShortLe(0); // Unused.
- sink.writeIntLe(bmpHeaderSize + dibHeaderSize); // Offset of pixel data.
-
- // DIB Header
- sink.writeIntLe(dibHeaderSize);
- sink.writeIntLe(width);
- sink.writeIntLe(height);
- sink.writeShortLe(1); // Color plane count.
- sink.writeShortLe(bytesPerPixel * Byte.SIZE);
- sink.writeIntLe(0); // No compression.
- sink.writeIntLe(16); // Size of bitmap data including padding.
- sink.writeIntLe(2835); // Horizontal print resolution in pixels/meter. (72 dpi).
- sink.writeIntLe(2835); // Vertical print resolution in pixels/meter. (72 dpi).
- sink.writeIntLe(0); // Palette color count.
- sink.writeIntLe(0); // 0 important colors.
-
- // Pixel data.
- for (int y = height - 1; y >= 0; y--) {
- for (int x = 0; x < width; x++) {
- sink.writeByte(bitmap.blue(x, y));
- sink.writeByte(bitmap.green(x, y));
- sink.writeByte(bitmap.red(x, y));
- }
-
- // Padding for 4-byte alignment.
- for (int p = rowByteCountWithoutPadding; p < rowByteCount; p++) {
- sink.writeByte(0);
- }
- }
- }
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- @Throws(IOException::class)
- fun encode(bitmap: Bitmap, sink: BufferedSink) {
- val height = bitmap.height
- val width = bitmap.width
- val bytesPerPixel = 3
- val rowByteCountWithoutPadding = bytesPerPixel * width
- val rowByteCount = (rowByteCountWithoutPadding + 3) / 4 * 4
- val pixelDataSize = rowByteCount * height
- val bmpHeaderSize = 14
- val dibHeaderSize = 40
-
- // BMP Header
- sink.writeUtf8("BM") // ID.
- sink.writeIntLe(bmpHeaderSize + dibHeaderSize + pixelDataSize) // File size.
- sink.writeShortLe(0) // Unused.
- sink.writeShortLe(0) // Unused.
- sink.writeIntLe(bmpHeaderSize + dibHeaderSize) // Offset of pixel data.
-
- // DIB Header
- sink.writeIntLe(dibHeaderSize)
- sink.writeIntLe(width)
- sink.writeIntLe(height)
- sink.writeShortLe(1) // Color plane count.
- sink.writeShortLe(bytesPerPixel * Byte.SIZE_BITS)
- sink.writeIntLe(0) // No compression.
- sink.writeIntLe(16) // Size of bitmap data including padding.
- sink.writeIntLe(2835) // Horizontal print resolution in pixels/meter. (72 dpi).
- sink.writeIntLe(2835) // Vertical print resolution in pixels/meter. (72 dpi).
- sink.writeIntLe(0) // Palette color count.
- sink.writeIntLe(0) // 0 important colors.
-
- // Pixel data.
- for (y in height - 1 downTo 0) {
- for (x in 0 until width) {
- sink.writeByte(bitmap.blue(x, y))
- sink.writeByte(bitmap.green(x, y))
- sink.writeByte(bitmap.red(x, y))
- }
-
- // Padding for 4-byte alignment.
- for (p in rowByteCountWithoutPadding until rowByteCount) {
- sink.writeByte(0)
- }
- }
- }
- ```
-
-The trickiest part of this program is the format’s required padding. The BMP
-format expects each row to begin on a 4-byte boundary so it is necessary to add
-zeros to maintain the alignment.
-
-Encoding other binary formats is usually quite similar. Some tips:
-
- * Write tests with golden values! Confirming that your program emits the
- expected result can make debugging easier.
- * Use `Utf8.size()` to compute the number of bytes of an encoded string. This
- is essential for length-prefixed formats.
- * Use `Float.floatToIntBits()` and `Double.doubleToLongBits()` to encode
- floating point values.
-
-
-### Communicate on a Socket ([Java][SocksProxyServer]/[Kotlin][SocksProxyServerKt])
-
-Sending and receiving data over the network is a bit like writing and reading
-files. We use `BufferedSink` to encode output and `BufferedSource` to decode
-input. Like files, network protocols can be text, binary, or a mix of both. But
-there are also some substantial differences between the network and the
-file system.
-
-With a file you’re either reading or writing but with the network you can do
-both! Some protocols handle this by taking turns: write a request, read a
-response, repeat. You can implement this kind of protocol with a single thread.
-In other protocols you may read and write simultaneously. Typically you’ll want
-one dedicated thread for reading. For writing you can use either a dedicated
-thread or use `synchronized` so that multiple threads can share a sink. Okio’s
-streams are not safe for concurrent use.
-
-Sinks buffer outbound data to minimize I/O operations. This is efficient but it
-means you must manually call `flush()` to transmit data. Typically
-message-oriented protocols flush after each message. Note that Okio will
-automatically flush when the buffered data exceeds some threshold. This is
-intended to save memory and you shouldn’t rely on it for interactive protocols.
-
-Okio builds on `java.io.Socket` for connectivity. Create your socket as a server
-or as a client, then use `Okio.source(Socket)` to read and `Okio.sink(Socket)`
-to write. These APIs also work with `SSLSocket`. You should use SSL unless you
-have a very good reason not to!
-
-Cancel a socket from any thread by calling `Socket.close()`; this will cause its
-sources and sinks to immediately fail with an `IOException`. You can also
-configure timeouts for all socket operations. You don’t need a reference to the
-socket to adjust timeouts: `Source` and `Sink` expose timeouts directly. This
-API works even if the streams are decorated.
-
-As a complete example of networking with Okio we wrote a [basic SOCKS
-proxy][SocksProxyServer] server. Some highlights:
-
-=== "Java"
-
- ```Java
- Socket fromSocket = ...
- BufferedSource fromSource = Okio.buffer(Okio.source(fromSocket));
- BufferedSink fromSink = Okio.buffer(Okio.sink(fromSocket));
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- val fromSocket: Socket = ...
- val fromSource = fromSocket.source().buffer()
- val fromSink = fromSocket.sink().buffer()
- ```
-
-Creating sources and sinks for sockets is the same as creating them for files.
-Once you create a `Source` or `Sink` for a socket you must not use its
-`InputStream` or `OutputStream`, respectively.
-
-=== "Java"
-
- ```Java
- Buffer buffer = new Buffer();
- for (long byteCount; (byteCount = source.read(buffer, 8192L)) != -1; ) {
- sink.write(buffer, byteCount);
- sink.flush();
- }
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- val buffer = Buffer()
- var byteCount: Long
- while (source.read(buffer, 8192L).also { byteCount = it } != -1L) {
- sink.write(buffer, byteCount)
- sink.flush()
- }
- ```
-
-The above loop copies data from the source to the sink, flushing after each
-read. If we didn’t need the flushing we could replace this loop with a single
-call to `BufferedSink.writeAll(Source)`.
-
-The `8192` argument to `read()` is the maximum number of bytes to read before
-returning. We could have passed any value here, but we like 8 KiB because that’s
-the largest value Okio can do in a single system call. Most of the time
-application code doesn’t need to deal with such limits!
-
-=== "Java"
-
- ```Java
- int addressType = fromSource.readByte() & 0xff;
- int port = fromSource.readShort() & 0xffff;
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- val addressType = fromSource.readByte().toInt() and 0xff
- val port = fromSource.readShort().toInt() and 0xffff
- ```
-
-Okio uses signed types like `byte` and `short`, but often protocols want
-unsigned values. The bitwise `&` operator is Java’s preferred idiom to convert
-a signed value into an unsigned value. Here’s a cheat sheet for bytes, shorts,
-and ints:
-
-| Type | Signed Range | Unsigned Range | Signed to Unsigned |
-| :---- | :---------------------------: | :--------------- | :-------------------------- |
-| byte | -128..127 | 0..255 | `int u = s & 0xff;` |
-| short | -32,768..32,767 | 0..65,535 | `int u = s & 0xffff;` |
-| int | -2,147,483,648..2,147,483,647 | 0..4,294,967,295 | `long u = s & 0xffffffffL;` |
-
-Java has no primitive type that can represent unsigned longs.
-
-
-### Hashing ([Java][Hashing]/[Kotlin][HashingKt])
-
-We’re bombarded by hashing in our lives as Java programmers. Early on we're introduced to the
-`hashCode()` method, something we know we need to override otherwise unforeseen bad things happen.
-Later we’re shown `LinkedHashMap` and its friends. These build on that `hashCode()` method to
-organize data for fast retrieval.
-
-Elsewhere we have cryptographic hash functions. These get used all over the place. HTTPS
-certificates, Git commits, BitTorrent integrity checking, and Blockchain blocks all use
-cryptographic hashes. Good use of hashes can improve the performance, privacy, security, and
-simplicity of an application.
-
-Each cryptographic hash function accepts a variable-length stream of input bytes and produces a
-fixed-length byte string value called the “hash”. Hash functions have these important qualities:
-
- * Deterministic: each input always produces the same output.
- * Uniform: each output byte string is equally likely. It is very difficult to find or create pairs
- of different inputs that yield the same output. This is called a “collision”.
- * Non-reversible: knowing an output doesn't help you to find the input. Note that if you know some
- possible inputs you can hash them to see if their hashes match.
- * Well-known: the hash is implemented everywhere and rigorously understood.
-
-Good hash functions are very cheap to compute (dozens of microseconds) and expensive to reverse
-(quintillions of millenia). Steady advances in computing and mathematics have caused once-great hash
-functions to become inexpensive to reverse. When choosing a hash function, beware that not all are
-created equal! Okio supports these well-known cryptographic hash functions:
-
- * **MD5**: a 128-bit (16 byte) cryptographic hash. It is both insecure and obsolete because it is
- inexpensive to reverse! This hash is offered because it is popular and convenient for use in
- legacy systems that are not security-sensitive.
- * **SHA-1**: a 160-bit (20 byte) cryptographic hash. It was recently demonstrated that it is
- feasible to create SHA-1 collisions. Consider upgrading from SHA-1 to SHA-256.
- * **SHA-256**: a 256-bit (32 byte) cryptographic hash. SHA-256 is widely understood and expensive
- to reverse. This is the hash most systems should use.
- * **SHA-512**: a 512-bit (64 byte) cryptographic hash. It is expensive to reverse.
-
-Each hash creates a `ByteString` of the specified length. Use `hex()` to get the conventional
-human-readable form. Or leave it as a `ByteString` because that’s a convenient model type!
-
-Okio can produce cryptographic hashes from byte strings:
-
-=== "Java"
-
- ```Java
- ByteString byteString = readByteString(new File("README.md"));
- System.out.println(" md5: " + byteString.md5().hex());
- System.out.println(" sha1: " + byteString.sha1().hex());
- System.out.println("sha256: " + byteString.sha256().hex());
- System.out.println("sha512: " + byteString.sha512().hex());
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- val byteString = readByteString(File("README.md"))
- println(" md5: " + byteString.md5().hex())
- println(" sha1: " + byteString.sha1().hex())
- println(" sha256: " + byteString.sha256().hex())
- println(" sha512: " + byteString.sha512().hex())
- ```
-
-From buffers:
-
-=== "Java"
-
- ```Java
- Buffer buffer = readBuffer(new File("README.md"));
- System.out.println(" md5: " + buffer.md5().hex());
- System.out.println(" sha1: " + buffer.sha1().hex());
- System.out.println("sha256: " + buffer.sha256().hex());
- System.out.println("sha512: " + buffer.sha512().hex());
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- val buffer = readBuffer(File("README.md"))
- println(" md5: " + buffer.md5().hex())
- println(" sha1: " + buffer.sha1().hex())
- println(" sha256: " + buffer.sha256().hex())
- println(" sha512: " + buffer.sha512().hex())
- ```
-
-While streaming from a source:
-
-=== "Java"
-
- ```Java
- try (HashingSink hashingSink = HashingSink.sha256(Okio.blackhole());
- BufferedSource source = Okio.buffer(Okio.source(file))) {
- source.readAll(hashingSink);
- System.out.println("sha256: " + hashingSink.hash().hex());
- }
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- sha256(blackholeSink()).use { hashingSink ->
- file.source().buffer().use { source ->
- source.readAll(hashingSink)
- println(" sha256: " + hashingSink.hash.hex())
- }
- }
- ```
-
-While streaming to a sink:
-
-=== "Java"
-
- ```Java
- try (HashingSink hashingSink = HashingSink.sha256(Okio.blackhole());
- BufferedSink sink = Okio.buffer(hashingSink);
- Source source = Okio.source(file)) {
- sink.writeAll(source);
- sink.close(); // Emit anything buffered.
- System.out.println("sha256: " + hashingSink.hash().hex());
- }
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- sha256(blackholeSink()).use { hashingSink ->
- hashingSink.buffer().use { sink ->
- file.source().use { source ->
- sink.writeAll(source)
- sink.close() // Emit anything buffered.
- println(" sha256: " + hashingSink.hash.hex())
- }
- }
- }
- ```
-
-Okio also supports HMAC (Hash Message Authentication Code) which combines a secret and a hash.
-Applications use HMAC for data integrity and authentication.
-
-=== "Java"
-
- ```Java
- ByteString secret = ByteString.decodeHex("7065616e7574627574746572");
- System.out.println("hmacSha256: " + byteString.hmacSha256(secret).hex());
- ```
-
-=== "Kotlin"
-
- ```Kotlin
- val secret = "7065616e7574627574746572".decodeHex()
- println("hmacSha256: " + byteString.hmacSha256(secret).hex())
- ```
-
-As with hashing, you can generate an HMAC from a `ByteString`, `Buffer`, `HashingSource`, and
-`HashingSink`. Note that Okio doesn’t implement HMAC for MD5. Okio uses Java’s
-`java.security.MessageDigest` for cryptographic hashes and `javax.crypto.Mac` for HMAC.
-
-### Encryption and Decryption
-
-Use `Okio.cipherSink(Sink, Cipher)` or `Okio.cipherSource(Source, Cipher)` to encrypt or decrypt a
-stream using a block cipher.
-
-Callers are responsible for the initialization of the encryption or decryption cipher with the
-chosen algorithm, the key, and algorithm-specific additional parameters like the initialization
-vector. The following example shows a typical usage with AES encryption, in which `key` and `iv`
-parameters should both be 16 bytes long.
-
-```java
-void encryptAes(ByteString bytes, File file, byte[] key, byte[] iv)
- throws GeneralSecurityException, IOException {
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
- cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
- try (BufferedSink sink = Okio.buffer(Okio.cipherSink(Okio.sink(file), cipher))) {
- sink.write(bytes);
- }
-}
-
-ByteString decryptAesToByteString(File file, byte[] key, byte[] iv)
- throws GeneralSecurityException, IOException {
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
- cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
- try (BufferedSource source = Okio.buffer(Okio.cipherSource(Okio.source(file), cipher))) {
- return source.readByteString();
- }
-}
-```
-
-In Kotlin, these encryption and decryption methods are extensions on `Cipher`:
-
-```kotlin
-fun encryptAes(bytes: ByteString, file: File, key: ByteArray, iv: ByteArray) {
- val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
- cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
- val cipherSink = file.sink().cipherSink(cipher)
- cipherSink.buffer().use {
- it.write(bytes)
- }
-}
-
-fun decryptAesToByteString(file: File, key: ByteArray, iv: ByteArray): ByteString {
- val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
- cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
- val cipherSource = file.source().cipherSource(cipher)
- return cipherSource.buffer().use {
- it.readByteString()
- }
-}
-```
-
-File System Examples
---------------------
-
-Okio's recently gained a multiplatform file system API. These examples work on JVM, native, and
-Node.js platforms. In the examples below `fileSystem` is an instance of [FileSystem] such as
-`FileSystem.SYSTEM` or `FakeFileSystem`.
-
-Read all of `readme.md` as a string:
-
-```
-val path = "readme.md".toPath()
-val entireFileString = fileSystem.read(path) {
- readUtf8()
-}
-```
-
-Read all of `thumbnail.png` as a [ByteString][3]:
-
-```
-val path = "thumbnail.png".toPath()
-val entireFileByteString = fileSystem.read(path) {
- readByteString()
-}
-```
-
-Read all lines of `/etc/hosts` into a `List<String>`:
-
-```
-val path = "/etc/hosts".toPath()
-val allLines = fileSystem.read(path) {
- generateSequence { readUtf8Line() }.toList()
-}
-```
-
-Read the prefix of `index.html` that precedes the first `<html>` substring:
-
-```
-val path = "index.html".toPath()
-val untilHtmlTag = fileSystem.read(path) {
- val htmlTag = indexOf("<html>".encodeUtf8())
- if (htmlTag != -1L) readUtf8(htmlTag) else null
-}
-```
-
-Write `readme.md` as a string:
-
-```
-val path = "readme.md".toPath()
-fileSystem.write(path) {
- writeUtf8(
- """
- |Hello, World
- |------------
- |
- |This is a sample file.
- |""".trimMargin()
- )
-}
-```
-
-Write `data.bin` as a [ByteString][3]:
-
-```
-val path = "data.bin".toPath()
-fileSystem.write(path) {
- val byteString = "68656c6c6f20776f726c640a".decodeHex()
- write(byteString)
-}
-```
-
-Write `readme.md` from a `List<String>`:
-
-```
-val path = "readme.md".toPath()
-val lines = listOf(
- "Hello, World",
- "------------",
- "",
- "This is a sample file.",
- ""
-)
-fileSystem.write(path) {
- for (line in lines) {
- writeUtf8(line)
- writeUtf8("\n")
- }
-}
-```
-
-Generate `binary.txt` programmatically:
-
-```
-val path = "binary.txt".toPath()
-fileSystem.write(path) {
- for (i in 1 until 100) {
- writeUtf8("$i ${i.toString(2)}")
- writeUtf8("\n")
- }
-}
-```
-
-
Releases
--------
Our [change log][changelog] has release history.
```kotlin
-implementation("com.squareup.okio:okio:2.10.0")
+implementation("com.squareup.okio:okio:3.7.0")
```
<details>
<summary>Snapshot builds are also available</summary>
-
+
```kotlin
repositories {
- maven {
- url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
- }
+ maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}
-
+
dependencies {
- implementation("com.squareup.okio:okio:2.10.0")
+ implementation("com.squareup.okio:okio:3.7.0")
}
-```
-
-</details>
-
-
-R8 / ProGuard
---------
+```
-If you are using R8 or ProGuard add the options from [this file][proguard].
+</details>
License
@@ -1124,20 +133,16 @@ License
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
[1]: https://github.com/square/okhttp
- [3]: https://square.github.io/okio/2.x/okio/okio/-byte-string/index.html
- [4]: https://square.github.io/okio/2.x/okio/okio/-buffer/index.html
- [5]: https://square.github.io/okio/2.x/okio/okio/-source/index.html
- [6]: https://square.github.io/okio/2.x/okio/okio/-sink/index.html
- [7]: https://square.github.io/okio/2.x/okio/okio/-buffered-source/index.html
- [8]: https://square.github.io/okio/2.x/okio/okio/-buffered-sink/index.html
+ [3]: https://square.github.io/okio/3.x/okio/okio/okio/-byte-string/index.html
+ [4]: https://square.github.io/okio/3.x/okio/okio/okio/-buffer/index.html
+ [5]: https://square.github.io/okio/3.x/okio/okio/okio/-source/index.html
+ [6]: https://square.github.io/okio/3.x/okio/okio/okio/-sink/index.html
+ [7]: https://square.github.io/okio/3.x/okio/okio/okio/-buffered-source/index.html
+ [8]: https://square.github.io/okio/3.x/okio/okio/okio/-buffered-sink/index.html
[changelog]: http://square.github.io/okio/changelog/
[javadoc]: https://square.github.io/okio/2.x/okio/okio/index.html
- [nfd]: https://docs.oracle.com/javase/7/docs/api/java/text/Normalizer.Form.html#NFD
- [nfc]: https://docs.oracle.com/javase/7/docs/api/java/text/Normalizer.Form.html#NFC
- [base64]: https://tools.ietf.org/html/rfc4648#section-4
- [bmp]: https://en.wikipedia.org/wiki/BMP_file_format
[kotlin]: https://kotlinlang.org/
[ok_libraries_talk]: https://www.youtube.com/watch?v=WvyScM_S88c
[ok_libraries_slides]: https://speakerdeck.com/jakewharton/a-few-ok-libraries-droidcon-mtl-2015
@@ -1145,19 +150,4 @@ License
[encoding_slides]: https://speakerdeck.com/swankjesse/decoding-the-secrets-of-binary-data-droidcon-nyc-2016
[ok_multiplatform_talk]: https://www.youtube.com/watch?v=Q8B4eDirgk0
[ok_multiplatform_slides]: https://speakerdeck.com/swankjesse/ok-multiplatform
- [ReadFileLineByLine]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/ReadFileLineByLine.java
- [ReadFileLineByLineKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/ReadFileLineByLine.kt
- [WriteFile]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/WriteFile.java
- [WriteFileKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/WriteFile.kt
- [ExploreCharsets]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/ExploreCharsets.java
- [ExploreCharsetsKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/ExploreCharsets.kt
- [FileSystem]: https://square.github.io/okio/2.x/okio/okio/-file-system/index.html
- [GoldenValue]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/GoldenValue.java
- [GoldenValueKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/GoldenValue.kt
- [BitmapEncoder]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/BitmapEncoder.java
- [BitmapEncoderKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/BitmapEncoder.kt
- [SocksProxyServer]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/SocksProxyServer.java
- [SocksProxyServerKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/SocksProxyServer.kt
- [Hashing]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/Hashing.java
- [HashingKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/Hashing.kt
- [proguard]: https://github.com/square/okio/blob/master/okio/src/jvmMain/resources/META-INF/proguard/okio.pro
+ [apis_talk]: https://www.youtube.com/watch?v=Du7YXPAV1M8
diff --git a/docs/java_io_recipes.md b/docs/java_io_recipes.md
new file mode 100644
index 00000000..4c44d9dc
--- /dev/null
+++ b/docs/java_io_recipes.md
@@ -0,0 +1,98 @@
+java.io Recipes
+===============
+
+These recipes use Okio with `java.io.File` instead of Okio's own `Path` and `FileSystem` types.
+
+
+Read a text file line-by-line ([Java][ReadJavaIoFileLineByLine]/[Kotlin][ReadJavaIoFileLineByLineKt])
+-----------------------------------------------------------------------------------------------------
+
+This is similar to the other [line-by-line example](recipes.md#read-a-text-file-line-by-line-javakotlin), but it uses `java.io.File`
+instead of `okio.Path` and `okio.FileSystem`.
+
+=== "Java"
+
+ ```java
+ public void readLines(File file) throws IOException {
+ try (Source fileSource = Okio.source(file);
+ BufferedSource bufferedFileSource = Okio.buffer(fileSource)) {
+
+ while (true) {
+ String line = bufferedFileSource.readUtf8Line();
+ if (line == null) break;
+
+ if (line.contains("square")) {
+ System.out.println(line);
+ }
+ }
+
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ Note that static `Okio` methods become extension functions (`Okio.source(file)` =>
+ `file.source()`).
+
+ ```kotlin
+ @Throws(IOException::class)
+ fun readLines(file: File) {
+ file.source().use { fileSource ->
+ fileSource.buffer().use { bufferedFileSource ->
+ while (true) {
+ val line = bufferedFileSource.readUtf8Line() ?: break
+ if ("square" in line) {
+ println(line)
+ }
+ }
+ }
+ }
+ }
+ ```
+
+Write a text file ([Java][WriteJavaIoFile]/[Kotlin][WriteJavaIoFileKt])
+-----------------------------------------------------------------------------------------------------
+
+This is similar to the other [write example](recipes.md#write-a-text-file-javakotlin), but it uses
+`java.io.File` instead of `okio.Path` and `okio.FileSystem`.
+
+=== "Java"
+
+ ```java
+ public void writeEnv(File file) throws IOException {
+ try (Sink fileSink = Okio.sink(file);
+ BufferedSink bufferedSink = Okio.buffer(fileSink)) {
+
+ for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
+ bufferedSink.writeUtf8(entry.getKey());
+ bufferedSink.writeUtf8("=");
+ bufferedSink.writeUtf8(entry.getValue());
+ bufferedSink.writeUtf8("\n");
+ }
+
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin
+ @Throws(IOException::class)
+ fun writeEnv(file: File) {
+ file.sink().buffer().use { sink ->
+ for ((key, value) in System.getenv()) {
+ sink.writeUtf8(key)
+ sink.writeUtf8("=")
+ sink.writeUtf8(value)
+ sink.writeUtf8("\n")
+ }
+ }
+ }
+ ```
+
+
+[ReadJavaIoFileLineByLineKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/ReadJavaIoFileLineByLine.kt
+[ReadJavaIoFileLineByLine]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/ReadJavaIoFileLineByLine.java
+[WriteJavaIoFileKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/WriteJavaIoFile.kt
+[WriteJavaIoFile]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/WriteJavaIoFile.java
diff --git a/docs/multiplatform.md b/docs/multiplatform.md
index cad68636..8943099a 100644
--- a/docs/multiplatform.md
+++ b/docs/multiplatform.md
@@ -38,3 +38,29 @@ Okio uses the built-in implementations of these functions on the JVM.
[kotlin_multiplatform]: https://kotlinlang.org/docs/reference/multiplatform.html
[mingw]: http://www.mingw.org/
[node_js]: https://nodejs.org/api/fs.html
+
+## Gradle configuration
+
+```kotlin
+// build.gradle.kts
+kotlin {
+ sourceSets {
+ val okioVersion = "3.XXX"
+ val commonMain by getting {
+ dependencies {
+ implementation("com.squareup.okio:okio:$okioVersion")
+ }
+ }
+ val jsMain by getting {
+ dependencies {
+ implementation("com.squareup.okio:okio-nodefilesystem:$okioVersion")
+ }
+ }
+ val commonTest by getting {
+ dependencies {
+ implementation("com.squareup.okio:okio-fakefilesystem:$okioVersion")
+ }
+ }
+ }
+}
+```
diff --git a/docs/recipes.md b/docs/recipes.md
new file mode 100644
index 00000000..acc5dcc7
--- /dev/null
+++ b/docs/recipes.md
@@ -0,0 +1,942 @@
+Recipes
+=======
+
+We've written some recipes that demonstrate how to solve common problems with Okio. Read through
+them to learn about how everything works together. Cut-and-paste these examples freely; that's what
+they're for.
+
+These recipes work on all platforms: Java, Android, Kotlin/Native, and Kotlin/JS. See
+[java.io Recipes](java_io_recipes.md) for samples that integrate Java APIs.
+
+
+Read a text file line-by-line ([Java][ReadFileLineByLine]/[Kotlin][ReadFileLineByLineKt])
+-----------------------------------------------------------------------------------------
+
+Use `FileSystem.source(Path)` to open a source stream to read a file. The returned `Source`
+interface is very small and has limited uses. Instead we wrap the source with a buffer. This has two
+benefits:
+
+ * **It makes the API more powerful.** Instead of the basic methods offered by `Source`,
+ `BufferedSource` has dozens of methods to address most common problems concisely.
+
+ * **It makes your program run faster.** Buffering allows Okio to get more done with fewer I/O
+ operations.
+
+Each `Source` that is opened needs to be closed. The code that opens the stream is responsible for
+making sure it is closed.
+
+=== "Java"
+
+ Here we use Java's `try` blocks to close our sources automatically.
+
+ ```java
+ public void readLines(Path path) throws IOException {
+ try (Source fileSource = FileSystem.SYSTEM.source(path);
+ BufferedSource bufferedFileSource = Okio.buffer(fileSource)) {
+
+ while (true) {
+ String line = bufferedFileSource.readUtf8Line();
+ if (line == null) break;
+
+ if (line.contains("square")) {
+ System.out.println(line);
+ }
+ }
+
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ This uses `use` to automatically close the streams. This prevents resource leaks, even if an
+ exception is thrown.
+
+ ```kotlin
+ fun readLines(path: Path) {
+ FileSystem.SYSTEM.source(path).use { fileSource ->
+ fileSource.buffer().use { bufferedFileSource ->
+ while (true) {
+ val line = bufferedFileSource.readUtf8Line() ?: break
+ if ("square" in line) {
+ println(line)
+ }
+ }
+ }
+ }
+ }
+ ```
+
+
+The `readUtf8Line()` API reads all of the data until the next line delimiter – either `\n`, `\r\n`,
+or the end of the file. It returns that data as a string, omitting the delimiter at the end. When it
+encounters empty lines the method will return an empty string. If there isn’t any more data to read
+it will return null.
+
+
+=== "Java"
+
+ The above Java program can be written more compactly by inlining the `fileSource` variable and
+ by using a fancy `for` loop instead of a `while`:
+
+ ```java
+ public void readLines(Path path) throws IOException {
+ try (BufferedSource source = Okio.buffer(FileSystem.SYSTEM.source(path))) {
+ for (String line; (line = source.readUtf8Line()) != null; ) {
+ if (line.contains("square")) {
+ System.out.println(line);
+ }
+ }
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ In Kotlin, we can use `FileSystem.read()` to buffer the source before our block and close the
+ source afterwards. In the body of the block, `this` is a `BufferedSource`.
+
+ ```kotlin
+ @Throws(IOException::class)
+ fun readLines(path: Path) {
+ FileSystem.SYSTEM.read(path) {
+ while (true) {
+ val line = readUtf8Line() ?: break
+ if ("square" in line) {
+ println(line)
+ }
+ }
+ }
+ }
+ ```
+
+The `readUtf8Line()` method is suitable for parsing most files. For certain use-cases you may also
+consider `readUtf8LineStrict()`. It is similar but it requires that each line is terminated by `\n`
+or `\r\n`. If it encounters the end of the file before that it will throw an `EOFException`. The
+strict variant also permits a byte limit to defend against malformed input.
+
+=== "Java"
+
+ ```java
+ public void readLines(Path path) throws IOException {
+ try (BufferedSource source = Okio.buffer(FileSystem.SYSTEM.source(path))) {
+ while (!source.exhausted()) {
+ String line = source.readUtf8LineStrict(1024L);
+ if (line.contains("square")) {
+ System.out.println(line);
+ }
+ }
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin
+ @Throws(IOException::class)
+ fun readLines(path: Path) {
+ FileSystem.SYSTEM.read(path) {
+ while (!source.exhausted()) {
+ val line = source.readUtf8LineStrict(1024)
+ if ("square" in line) {
+ println(line)
+ }
+ }
+ }
+ }
+ ```
+
+
+Write a text file ([Java][WriteFile]/[Kotlin][WriteFileKt])
+-----------------------------------------------------------
+
+Above we used a `Source` and a `BufferedSource` to read a file. To write, we use a `Sink` and a
+`BufferedSink`. The advantages of buffering are the same: a more capable API and better performance.
+
+```java
+public void writeEnv(Path path) throws IOException {
+ try (Sink fileSink = FileSystem.SYSTEM.sink(path);
+ BufferedSink bufferedSink = Okio.buffer(fileSink)) {
+
+ for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
+ bufferedSink.writeUtf8(entry.getKey());
+ bufferedSink.writeUtf8("=");
+ bufferedSink.writeUtf8(entry.getValue());
+ bufferedSink.writeUtf8("\n");
+ }
+
+ }
+}
+```
+
+There isn’t an API to write a line of input; instead we manually insert our own newline character.
+Most programs should hardcode `"\n"` as the newline character. In rare situations you may use
+`System.lineSeparator()` instead of `"\n"`: it returns `"\r\n"` on Windows and `"\n"` everywhere
+else.
+
+=== "Java"
+
+ We can write the above program more compactly by inlining the `fileSink` variable and by taking
+ advantage of method chaining:
+
+ ```java
+ public void writeEnv(Path path) throws IOException {
+ try (BufferedSink sink = Okio.buffer(FileSystem.SYSTEM.sink(path))) {
+ for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
+ sink.writeUtf8(entry.getKey())
+ .writeUtf8("=")
+ .writeUtf8(entry.getValue())
+ .writeUtf8("\n");
+ }
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ In Kotlin, we can use `FileSystem.write()` to buffer the sink before our block and close the
+ sink afterwards. In the body of the block, `this` is a `BufferedSink`.
+
+ ```kotlin
+ @Throws(IOException::class)
+ fun writeEnv(path: Path) {
+ FileSystem.SYSTEM.write(path) {
+ for ((key, value) in System.getenv()) {
+ writeUtf8(key)
+ writeUtf8("=")
+ writeUtf8(value)
+ writeUtf8("\n")
+ }
+ }
+ }
+ ```
+
+In the above code we make four calls to `writeUtf8()`. Making four calls is more efficient than the
+code below because the VM doesn’t have to create and garbage collect a temporary string.
+
+```java
+sink.writeUtf8(entry.getKey() + "=" + entry.getValue() + "\n"); // Slower!
+```
+
+
+UTF-8 ([Java][ExploreCharsets]/[Kotlin][ExploreCharsetsKt])
+-----------------------------------------------------------
+
+In the above APIs you can see that Okio really likes UTF-8. Early computer systems suffered many
+incompatible character encodings: ISO-8859-1, ShiftJIS, ASCII, EBCDIC, etc. Writing software to
+support multiple character sets was awful and we didn’t even have emoji! Today we're lucky that the
+world has standardized on UTF-8 everywhere, with some rare uses of other charsets in legacy systems.
+
+If you need another character set, `readString()` and `writeString()` are there for you. These
+ methods require that you specify a character set. Otherwise you may accidentally create data that
+ is only readable by the local computer. Most programs should use the UTF-8 methods only.
+
+When encoding strings you need to be mindful of the different ways that strings are represented and
+encoded. When a glyph has an accent or another adornment it may be represented as a single complex
+ code point (`é`) or as a simple code point (`e`) followed by its modifiers (`´`). When the entire
+ glyph is a single code point that’s called [NFC][nfc]; when it’s multiple it’s [NFD][nfd].
+
+Though we use UTF-8 whenever we read or write strings in I/O, when they are in memory Java Strings
+use an obsolete character encoding called UTF-16. It is a bad encoding because it uses a 16-bit
+`char` for most characters, but some don’t fit. In particular, most emoji use two Java chars. This
+is problematic because `String.length()` returns a surprising result: the number of UTF-16 chars and
+not the natural number of glyphs.
+
+| | Café 🍩 | Café 🍩 |
+| --------------------: | :---------------------------| :------------------------------|
+| Form | [NFC][nfc] | [NFD][nfd] |
+| Code Points | `c  a  f  é    ␣   🍩     ` | `c  a  f  e  ´    ␣   🍩     ` |
+| UTF-8 bytes | `43 61 66 c3a9 20 f09f8da9` | `43 61 66 65 cc81 20 f09f8da9` |
+| String.codePointCount | 6 | 7 |
+| String.length | 7 | 8 |
+| Utf8.size | 10 | 11 |
+
+For the most part Okio lets you ignore these problems and focus on your data. But when you need
+them, there are convenient APIs for dealing with low-level UTF-8 strings.
+
+Use `Utf8.size()` to count the number of bytes required to encode a string as UTF-8 without actually
+encoding it. This is handy in length-prefixed encodings like protocol buffers.
+
+Use `BufferedSource.readUtf8CodePoint()` to read a single variable-length code point, and
+`BufferedSink.writeUtf8CodePoint()` to write one.
+
+
+=== "Java"
+
+ ```java
+ public void dumpStringData(String s) throws IOException {
+ System.out.println(" " + s);
+ System.out.println(" String.length: " + s.length());
+ System.out.println("String.codePointCount: " + s.codePointCount(0, s.length()));
+ System.out.println(" Utf8.size: " + Utf8.size(s));
+ System.out.println(" UTF-8 bytes: " + ByteString.encodeUtf8(s).hex());
+ System.out.println();
+ }
+ ```
+
+=== "Kotlin"
+
+ ```kotlin
+ fun dumpStringData(s: String) {
+ println(" " + s)
+ println(" String.length: " + s.length)
+ println("String.codePointCount: " + s.codePointCount(0, s.length))
+ println(" Utf8.size: " + s.utf8Size())
+ println(" UTF-8 bytes: " + s.encodeUtf8().hex())
+ println()
+ }
+ ```
+
+Golden Values ([Java][GoldenValue]/[Kotlin][GoldenValueKt])
+-----------------------------------------------------------
+
+Okio likes testing. The library itself is heavily tested, and it has features that are often helpful
+when testing application code. One pattern we’ve found to be quite useful is “golden value” testing.
+The goal of such tests is to confirm that data encoded with earlier versions of a program can safely
+be decoded by the current program.
+
+We’ll illustrate this by encoding a value using Java Serialization. Though we must disclaim that
+Java Serialization is an awful encoding system and most programs should prefer other formats like
+JSON or protobuf! In any case, here’s a method that takes an object, serializes it, and returns the
+result as a `ByteString`:
+
+=== "Java"
+
+ ```Java
+ private ByteString serialize(Object o) throws IOException {
+ Buffer buffer = new Buffer();
+ try (ObjectOutputStream objectOut = new ObjectOutputStream(buffer.outputStream())) {
+ objectOut.writeObject(o);
+ }
+ return buffer.readByteString();
+ }
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ @Throws(IOException::class)
+ private fun serialize(o: Any?): ByteString {
+ val buffer = Buffer()
+ ObjectOutputStream(buffer.outputStream()).use { objectOut ->
+ objectOut.writeObject(o)
+ }
+ return buffer.readByteString()
+ }
+ ```
+
+There’s a lot going on here.
+
+1. We create a buffer as a holding space for our serialized data. It’s a convenient replacement for
+ `ByteArrayOutputStream`.
+
+2. We ask the buffer for its output stream. Writes to a buffer or its output stream always append
+ data to the end of the buffer.
+
+3. We create an `ObjectOutputStream` (the encoding API for Java serialization) and write our object.
+ The try block takes care of closing the stream for us. Note that closing a buffer has no effect.
+
+4. Finally we read a byte string from the buffer. The `readByteString()` method allows us to specify
+ how many bytes to read; here we don’t specify a count in order to read the entire thing. Reads
+ from a buffer always consume data from the front of the buffer.
+
+With our `serialize()` method handy we are ready to compute and print a golden value.
+
+=== "Java"
+
+ ```Java
+ Point point = new Point(8.0, 15.0);
+ ByteString pointBytes = serialize(point);
+ System.out.println(pointBytes.base64());
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ val point = Point(8.0, 15.0)
+ val pointBytes = serialize(point)
+ println(pointBytes.base64())
+ ```
+
+We print the `ByteString` as [base64][base64] because it’s a compact format that’s suitable for
+embedding in a test case. The program prints this:
+
+```
+rO0ABXNyAB5va2lvLnNhbXBsZXMuR29sZGVuVmFsdWUkUG9pbnTdUW8rMji1IwIAAkQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA
+```
+
+That’s our golden value! We can embed it in our test case using base64 again to convert it back into
+a `ByteString`:
+
+=== "Java"
+
+ ```Java
+ ByteString goldenBytes = ByteString.decodeBase64("rO0ABXNyAB5va2lvLnNhbXBsZ"
+ + "XMuR29sZGVuVmFsdWUkUG9pbnTdUW8rMji1IwIAAkQAAXhEAAF5eHBAIAAAAAAAAEAuA"
+ + "AAAAAAA");
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ val goldenBytes = ("rO0ABXNyACRva2lvLnNhbXBsZXMuS290bGluR29sZGVuVmFsdWUkUG9pbnRF9yaY7cJ9EwIAA" +
+ "kQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA").decodeBase64()
+ ```
+
+The next step is to deserialize the `ByteString` back into our value class. This method reverses the
+`serialize()` method above: we append a byte string to a buffer then consume it using an
+`ObjectInputStream`:
+
+=== "Java"
+
+ ```Java
+ private Object deserialize(ByteString byteString) throws IOException, ClassNotFoundException {
+ Buffer buffer = new Buffer();
+ buffer.write(byteString);
+ try (ObjectInputStream objectIn = new ObjectInputStream(buffer.inputStream())) {
+ return objectIn.readObject();
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ @Throws(IOException::class, ClassNotFoundException::class)
+ private fun deserialize(byteString: ByteString): Any? {
+ val buffer = Buffer()
+ buffer.write(byteString)
+ ObjectInputStream(buffer.inputStream()).use { objectIn ->
+ return objectIn.readObject()
+ }
+ }
+ ```
+
+Now we can test the decoder against the golden value:
+
+=== "Java"
+
+ ```Java
+ ByteString goldenBytes = ByteString.decodeBase64("rO0ABXNyAB5va2lvLnNhbXBsZ"
+ + "XMuR29sZGVuVmFsdWUkUG9pbnTdUW8rMji1IwIAAkQAAXhEAAF5eHBAIAAAAAAAAEAuA"
+ + "AAAAAAA");
+ Point decoded = (Point) deserialize(goldenBytes);
+ assertEquals(new Point(8.0, 15.0), decoded);
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ val goldenBytes = ("rO0ABXNyACRva2lvLnNhbXBsZXMuS290bGluR29sZGVuVmFsdWUkUG9pbnRF9yaY7cJ9EwIAA" +
+ "kQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA").decodeBase64()!!
+ val decoded = deserialize(goldenBytes) as Point
+ assertEquals(point, decoded)
+ ```
+
+With this test we can change the serialization of the `Point` class without
+breaking compatibility.
+
+
+Write a binary file ([Java][BitmapEncoder]/[Kotlin][BitmapEncoderKt])
+---------------------------------------------------------------------
+
+Encoding a binary file is not unlike encoding a text file. Okio uses the same `BufferedSink` and
+`BufferedSource` bytes for both. This is handy for binary formats that include both byte and
+character data.
+
+Writing binary data is more hazardous than text because if you make a mistake it is often quite
+difficult to diagnose. Avoid such mistakes by being careful around these traps:
+
+ * **The width of each field.** This is the number of bytes used. Okio doesn't include a mechanism
+ to emit partial bytes. If you need that, you’ll need to do your own bit shifting and masking
+ before writing.
+
+ * **The endianness of each field.** All fields that have more than one byte have _endianness_:
+ whether the bytes are ordered most-significant to least (big endian) or least-significant to most
+ (little endian). Okio uses the `Le` suffix for little-endian methods; methods without a suffix
+ are big-endian.
+
+ * **Signed vs. Unsigned.** Java doesn’t have unsigned primitive types (except for `char`!) so
+ coping with this is often something that happens at the application layer. To make this a little
+ easier Okio accepts `int` types for `writeByte()` and `writeShort()`. You can pass an “unsigned”
+ byte like 255 and Okio will do the right thing.
+
+| Method | Width | Endianness | Value | Encoded Value |
+| :----------- | ----: | :--------- | --------------: | :------------------------ |
+| writeByte | 1 | | 3 | `03` |
+| writeShort | 2 | big | 3 | `00 03` |
+| writeInt | 4 | big | 3 | `00 00 00 03` |
+| writeLong | 8 | big | 3 | `00 00 00 00 00 00 00 03` |
+| writeShortLe | 2 | little | 3 | `03 00` |
+| writeIntLe | 4 | little | 3 | `03 00 00 00` |
+| writeLongLe | 8 | little | 3 | `03 00 00 00 00 00 00 00` |
+| writeByte | 1 | | Byte.MAX_VALUE | `7f` |
+| writeShort | 2 | big | Short.MAX_VALUE | `7f ff` |
+| writeInt | 4 | big | Int.MAX_VALUE | `7f ff ff ff` |
+| writeLong | 8 | big | Long.MAX_VALUE | `7f ff ff ff ff ff ff ff` |
+| writeShortLe | 2 | little | Short.MAX_VALUE | `ff 7f` |
+| writeIntLe | 4 | little | Int.MAX_VALUE | `ff ff ff 7f` |
+| writeLongLe | 8 | little | Long.MAX_VALUE | `ff ff ff ff ff ff ff 7f` |
+
+This code encodes a bitmap following the [BMP file format][bmp].
+
+=== "Java"
+
+ ```Java
+ void encode(Bitmap bitmap, BufferedSink sink) throws IOException {
+ int height = bitmap.height();
+ int width = bitmap.width();
+
+ int bytesPerPixel = 3;
+ int rowByteCountWithoutPadding = (bytesPerPixel * width);
+ int rowByteCount = ((rowByteCountWithoutPadding + 3) / 4) * 4;
+ int pixelDataSize = rowByteCount * height;
+ int bmpHeaderSize = 14;
+ int dibHeaderSize = 40;
+
+ // BMP Header
+ sink.writeUtf8("BM"); // ID.
+ sink.writeIntLe(bmpHeaderSize + dibHeaderSize + pixelDataSize); // File size.
+ sink.writeShortLe(0); // Unused.
+ sink.writeShortLe(0); // Unused.
+ sink.writeIntLe(bmpHeaderSize + dibHeaderSize); // Offset of pixel data.
+
+ // DIB Header
+ sink.writeIntLe(dibHeaderSize);
+ sink.writeIntLe(width);
+ sink.writeIntLe(height);
+ sink.writeShortLe(1); // Color plane count.
+ sink.writeShortLe(bytesPerPixel * Byte.SIZE);
+ sink.writeIntLe(0); // No compression.
+ sink.writeIntLe(16); // Size of bitmap data including padding.
+ sink.writeIntLe(2835); // Horizontal print resolution in pixels/meter. (72 dpi).
+ sink.writeIntLe(2835); // Vertical print resolution in pixels/meter. (72 dpi).
+ sink.writeIntLe(0); // Palette color count.
+ sink.writeIntLe(0); // 0 important colors.
+
+ // Pixel data.
+ for (int y = height - 1; y >= 0; y--) {
+ for (int x = 0; x < width; x++) {
+ sink.writeByte(bitmap.blue(x, y));
+ sink.writeByte(bitmap.green(x, y));
+ sink.writeByte(bitmap.red(x, y));
+ }
+
+ // Padding for 4-byte alignment.
+ for (int p = rowByteCountWithoutPadding; p < rowByteCount; p++) {
+ sink.writeByte(0);
+ }
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ @Throws(IOException::class)
+ fun encode(bitmap: Bitmap, sink: BufferedSink) {
+ val height = bitmap.height
+ val width = bitmap.width
+ val bytesPerPixel = 3
+ val rowByteCountWithoutPadding = bytesPerPixel * width
+ val rowByteCount = (rowByteCountWithoutPadding + 3) / 4 * 4
+ val pixelDataSize = rowByteCount * height
+ val bmpHeaderSize = 14
+ val dibHeaderSize = 40
+
+ // BMP Header
+ sink.writeUtf8("BM") // ID.
+ sink.writeIntLe(bmpHeaderSize + dibHeaderSize + pixelDataSize) // File size.
+ sink.writeShortLe(0) // Unused.
+ sink.writeShortLe(0) // Unused.
+ sink.writeIntLe(bmpHeaderSize + dibHeaderSize) // Offset of pixel data.
+
+ // DIB Header
+ sink.writeIntLe(dibHeaderSize)
+ sink.writeIntLe(width)
+ sink.writeIntLe(height)
+ sink.writeShortLe(1) // Color plane count.
+ sink.writeShortLe(bytesPerPixel * Byte.SIZE_BITS)
+ sink.writeIntLe(0) // No compression.
+ sink.writeIntLe(16) // Size of bitmap data including padding.
+ sink.writeIntLe(2835) // Horizontal print resolution in pixels/meter. (72 dpi).
+ sink.writeIntLe(2835) // Vertical print resolution in pixels/meter. (72 dpi).
+ sink.writeIntLe(0) // Palette color count.
+ sink.writeIntLe(0) // 0 important colors.
+
+ // Pixel data.
+ for (y in height - 1 downTo 0) {
+ for (x in 0 until width) {
+ sink.writeByte(bitmap.blue(x, y))
+ sink.writeByte(bitmap.green(x, y))
+ sink.writeByte(bitmap.red(x, y))
+ }
+
+ // Padding for 4-byte alignment.
+ for (p in rowByteCountWithoutPadding until rowByteCount) {
+ sink.writeByte(0)
+ }
+ }
+ }
+ ```
+
+The trickiest part of this program is the format’s required padding. The BMP format expects each row
+to begin on a 4-byte boundary so it is necessary to add zeros to maintain the alignment.
+
+Encoding other binary formats is usually quite similar. Some tips:
+
+ * Write tests with golden values! Confirming that your program emits the expected result can make
+ debugging easier.
+ * Use `Utf8.size()` to compute the number of bytes of an encoded string. This is essential for
+ length-prefixed formats.
+ * Use `Float.floatToIntBits()` and `Double.doubleToLongBits()` to encode floating point values.
+
+
+Communicate on a Socket ([Java][SocksProxyServer]/[Kotlin][SocksProxyServerKt])
+-------------------------------------------------------------------------------
+
+Note that Okio doesn't yet support sockets on Kotlin/Native or Kotlin/JS.
+
+Sending and receiving data over the network is a bit like writing and reading files. We use
+`BufferedSink` to encode output and `BufferedSource` to decode input. Like files, network protocols
+can be text, binary, or a mix of both. But there are also some substantial differences between the
+network and the file system.
+
+With a file you’re either reading or writing but with the network you can do both! Some protocols
+handle this by taking turns: write a request, read a response, repeat. You can implement this kind
+of protocol with a single thread. In other protocols you may read and write simultaneously.
+Typically you’ll want one dedicated thread for reading. For writing you can use either a dedicated
+thread or use `synchronized` so that multiple threads can share a sink. Okio’s streams are not safe
+for concurrent use.
+
+Sinks buffer outbound data to minimize I/O operations. This is efficient but it means you must
+manually call `flush()` to transmit data. Typically message-oriented protocols flush after each
+message. Note that Okio will automatically flush when the buffered data exceeds some threshold. This
+is intended to save memory and you shouldn’t rely on it for interactive protocols.
+
+Okio builds on `java.io.Socket` for connectivity. Create your socket as a server or as a client,
+then use `Okio.source(Socket)` to read and `Okio.sink(Socket)` to write. These APIs also work with
+`SSLSocket`. You should use SSL unless you have a very good reason not to!
+
+Cancel a socket from any thread by calling `Socket.close()`; this will cause its sources and sinks
+to immediately fail with an `IOException`. You can also configure timeouts for all socket
+operations. You don’t need a reference to the socket to adjust timeouts: `Source` and `Sink` expose
+timeouts directly. This API works even if the streams are decorated.
+
+As a complete example of networking with Okio we wrote a [basic SOCKS proxy][SocksProxyServer]
+server. Some highlights:
+
+=== "Java"
+
+ ```Java
+ Socket fromSocket = ...
+ BufferedSource fromSource = Okio.buffer(Okio.source(fromSocket));
+ BufferedSink fromSink = Okio.buffer(Okio.sink(fromSocket));
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ val fromSocket: Socket = ...
+ val fromSource = fromSocket.source().buffer()
+ val fromSink = fromSocket.sink().buffer()
+ ```
+
+Creating sources and sinks for sockets is the same as creating them for files. Once you create a
+`Source` or `Sink` for a socket you must not use its `InputStream` or `OutputStream`, respectively.
+
+=== "Java"
+
+ ```Java
+ Buffer buffer = new Buffer();
+ for (long byteCount; (byteCount = source.read(buffer, 8192L)) != -1; ) {
+ sink.write(buffer, byteCount);
+ sink.flush();
+ }
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ val buffer = Buffer()
+ var byteCount: Long
+ while (source.read(buffer, 8192L).also { byteCount = it } != -1L) {
+ sink.write(buffer, byteCount)
+ sink.flush()
+ }
+ ```
+
+The above loop copies data from the source to the sink, flushing after each read. If we didn’t need
+the flushing we could replace this loop with a single call to `BufferedSink.writeAll(Source)`.
+
+The `8192` argument to `read()` is the maximum number of bytes to read before returning. We could
+have passed any value here, but we like 8 KiB because that’s the largest value Okio can do in a
+single system call. Most of the time application code doesn’t need to deal with such limits!
+
+=== "Java"
+
+ ```Java
+ int addressType = fromSource.readByte() & 0xff;
+ int port = fromSource.readShort() & 0xffff;
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ val addressType = fromSource.readByte().toInt() and 0xff
+ val port = fromSource.readShort().toInt() and 0xffff
+ ```
+
+Okio uses signed types like `byte` and `short`, but often protocols want unsigned values. The
+bitwise `&` operator is Java’s preferred idiom to convert a signed value into an unsigned value.
+Here’s a cheat sheet for bytes, shorts, and ints:
+
+| Type | Signed Range | Unsigned Range | Signed to Unsigned |
+| :---- | :---------------------------: | :--------------- | :-------------------------- |
+| byte | -128..127 | 0..255 | `int u = s & 0xff;` |
+| short | -32,768..32,767 | 0..65,535 | `int u = s & 0xffff;` |
+| int | -2,147,483,648..2,147,483,647 | 0..4,294,967,295 | `long u = s & 0xffffffffL;` |
+
+Java has no primitive type that can represent unsigned longs.
+
+
+Hashing ([Java][Hashing]/[Kotlin][HashingKt])
+---------------------------------------------
+
+We’re bombarded by hashing in our lives as Java programmers. Early on we're introduced to the
+`hashCode()` method, something we know we need to override otherwise unforeseen bad things happen.
+Later we’re shown `LinkedHashMap` and its friends. These build on that `hashCode()` method to
+organize data for fast retrieval.
+
+Elsewhere we have cryptographic hash functions. These get used all over the place. HTTPS
+certificates, Git commits, BitTorrent integrity checking, and Blockchain blocks all use
+cryptographic hashes. Good use of hashes can improve the performance, privacy, security, and
+simplicity of an application.
+
+Each cryptographic hash function accepts a variable-length stream of input bytes and produces a
+fixed-length byte string value called the “hash”. Hash functions have these important qualities:
+
+ * Deterministic: each input always produces the same output.
+ * Uniform: each output byte string is equally likely. It is very difficult to find or create pairs
+ of different inputs that yield the same output. This is called a “collision”.
+ * Non-reversible: knowing an output doesn't help you to find the input. Note that if you know some
+ possible inputs you can hash them to see if their hashes match.
+ * Well-known: the hash is implemented everywhere and rigorously understood.
+
+Good hash functions are very cheap to compute (dozens of microseconds) and expensive to reverse
+(quintillions of millenia). Steady advances in computing and mathematics have caused once-great hash
+functions to become inexpensive to reverse. When choosing a hash function, beware that not all are
+created equal! Okio supports these well-known cryptographic hash functions:
+
+ * **MD5**: a 128-bit (16 byte) cryptographic hash. It is both insecure and obsolete because it is
+ inexpensive to reverse! This hash is offered because it is popular and convenient for use in
+ legacy systems that are not security-sensitive.
+ * **SHA-1**: a 160-bit (20 byte) cryptographic hash. It was recently demonstrated that it is
+ feasible to create SHA-1 collisions. Consider upgrading from SHA-1 to SHA-256.
+ * **SHA-256**: a 256-bit (32 byte) cryptographic hash. SHA-256 is widely understood and expensive
+ to reverse. This is the hash most systems should use.
+ * **SHA-512**: a 512-bit (64 byte) cryptographic hash. It is expensive to reverse.
+
+Each hash creates a `ByteString` of the specified length. Use `hex()` to get the conventional
+human-readable form. Or leave it as a `ByteString` because that’s a convenient model type!
+
+Okio can produce cryptographic hashes from byte strings:
+
+=== "Java"
+
+ ```Java
+ ByteString byteString = readByteString(Path.get("README.md"));
+ System.out.println(" md5: " + byteString.md5().hex());
+ System.out.println(" sha1: " + byteString.sha1().hex());
+ System.out.println("sha256: " + byteString.sha256().hex());
+ System.out.println("sha512: " + byteString.sha512().hex());
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ val byteString = readByteString("README.md".toPath())
+ println(" md5: " + byteString.md5().hex())
+ println(" sha1: " + byteString.sha1().hex())
+ println(" sha256: " + byteString.sha256().hex())
+ println(" sha512: " + byteString.sha512().hex())
+ ```
+
+From buffers:
+
+=== "Java"
+
+ ```Java
+ Buffer buffer = readBuffer(Path.get("README.md"));
+ System.out.println(" md5: " + buffer.md5().hex());
+ System.out.println(" sha1: " + buffer.sha1().hex());
+ System.out.println("sha256: " + buffer.sha256().hex());
+ System.out.println("sha512: " + buffer.sha512().hex());
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ val buffer = readBuffer("README.md".toPath())
+ println(" md5: " + buffer.md5().hex())
+ println(" sha1: " + buffer.sha1().hex())
+ println(" sha256: " + buffer.sha256().hex())
+ println(" sha512: " + buffer.sha512().hex())
+ ```
+
+While streaming from a source:
+
+=== "Java"
+
+ ```Java
+ try (HashingSink hashingSink = HashingSink.sha256(Okio.blackhole());
+ BufferedSource source = Okio.buffer(FileSystem.SYSTEM.source(path))) {
+ source.readAll(hashingSink);
+ System.out.println("sha256: " + hashingSink.hash().hex());
+ }
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ sha256(blackholeSink()).use { hashingSink ->
+ FileSystem.SYSTEM.source(path).buffer().use { source ->
+ source.readAll(hashingSink)
+ println(" sha256: " + hashingSink.hash.hex())
+ }
+ }
+ ```
+
+While streaming to a sink:
+
+=== "Java"
+
+ ```Java
+ try (HashingSink hashingSink = HashingSink.sha256(Okio.blackhole());
+ BufferedSink sink = Okio.buffer(hashingSink);
+ Source source = FileSystem.SYSTEM.source(path)) {
+ sink.writeAll(source);
+ sink.close(); // Emit anything buffered.
+ System.out.println("sha256: " + hashingSink.hash().hex());
+ }
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ sha256(blackholeSink()).use { hashingSink ->
+ hashingSink.buffer().use { sink ->
+ FileSystem.SYSTEM.source(path).use { source ->
+ sink.writeAll(source)
+ sink.close() // Emit anything buffered.
+ println(" sha256: " + hashingSink.hash.hex())
+ }
+ }
+ }
+ ```
+
+Okio also supports HMAC (Hash Message Authentication Code) which combines a secret and a hash.
+Applications use HMAC for data integrity and authentication.
+
+=== "Java"
+
+ ```Java
+ ByteString secret = ByteString.decodeHex("7065616e7574627574746572");
+ System.out.println("hmacSha256: " + byteString.hmacSha256(secret).hex());
+ ```
+
+=== "Kotlin"
+
+ ```Kotlin
+ val secret = "7065616e7574627574746572".decodeHex()
+ println("hmacSha256: " + byteString.hmacSha256(secret).hex())
+ ```
+
+As with hashing, you can generate an HMAC from a `ByteString`, `Buffer`, `HashingSource`, and
+`HashingSink`. Note that Okio doesn’t implement HMAC for MD5.
+
+On Android and Java, Okio uses Java’s `java.security.MessageDigest` for cryptographic hashes and
+`javax.crypto.Mac` for HMAC. On other platforms Okio uses its own optimized implementation of
+these algorithms.
+
+
+Encryption and Decryption
+-------------------------
+
+On Android and Java it's easy to encrypt streams.
+
+Callers are responsible for the initialization of the encryption or decryption cipher with the
+chosen algorithm, the key, and algorithm-specific additional parameters like the initialization
+vector. The following example shows a typical usage with AES encryption, in which `key` and `iv`
+parameters should both be 16 bytes long.
+
+=== "Java"
+
+ Use `Okio.cipherSink(Sink, Cipher)` or `Okio.cipherSource(Source, Cipher)` to encrypt or decrypt
+ a stream using a block cipher.
+
+ ```java
+ void encryptAes(ByteString bytes, Path path, byte[] key, byte[] iv)
+ throws GeneralSecurityException, IOException {
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
+ try (BufferedSink sink = Okio.buffer(
+ Okio.cipherSink(FileSystem.SYSTEM.sink(path), cipher))) {
+ sink.write(bytes);
+ }
+ }
+
+ ByteString decryptAesToByteString(Path path, byte[] key, byte[] iv)
+ throws GeneralSecurityException, IOException {
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
+ try (BufferedSource source = Okio.buffer(
+ Okio.cipherSource(FileSystem.SYSTEM.source(path), cipher))) {
+ return source.readByteString();
+ }
+ }
+ ```
+
+=== "Kotlin"
+
+ Encryption and decryption functions are extensions on `Cipher`:
+
+ ```kotlin
+ fun encryptAes(bytes: ByteString, path: Path, key: ByteArray, iv: ByteArray) {
+ val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
+ cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
+ val cipherSink = FileSystem.SYSTEM.sink(path).cipherSink(cipher)
+ cipherSink.buffer().use {
+ it.write(bytes)
+ }
+ }
+
+ fun decryptAesToByteString(path: Path, key: ByteArray, iv: ByteArray): ByteString {
+ val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
+ cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
+ val cipherSource = FileSystem.SYSTEM.source(path).cipherSource(cipher)
+ return cipherSource.buffer().use {
+ it.readByteString()
+ }
+ }
+ ```
+
+
+[base64]: https://tools.ietf.org/html/rfc4648#section-4
+[bmp]: https://en.wikipedia.org/wiki/BMP_file_format
+[nfd]: https://docs.oracle.com/javase/7/docs/api/java/text/Normalizer.Form.html#NFD
+[nfc]: https://docs.oracle.com/javase/7/docs/api/java/text/Normalizer.Form.html#NFC
+[BitmapEncoderKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/BitmapEncoder.kt
+[BitmapEncoder]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/BitmapEncoder.java
+[ExploreCharsetsKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/ExploreCharsets.kt
+[ExploreCharsets]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/ExploreCharsets.java
+[GoldenValueKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/GoldenValue.kt
+[GoldenValue]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/GoldenValue.java
+[HashingKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/Hashing.kt
+[Hashing]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/Hashing.java
+[ReadFileLineByLine]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/ReadFileLineByLine.java
+[ReadFileLineByLineKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/ReadFileLineByLine.kt
+[SocksProxyServerKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/SocksProxyServer.kt
+[SocksProxyServer]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/SocksProxyServer.java
+[WriteFile]: https://github.com/square/okio/blob/master/samples/src/jvmMain/java/okio/samples/WriteFile.java
+[WriteFileKt]: https://github.com/square/okio/blob/master/samples/src/jvmMain/kotlin/okio/samples/WriteFile.kt
diff --git a/docs/releasing.md b/docs/releasing.md
index 94a1a34c..1a32a260 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -1,63 +1,6 @@
Releasing
=========
-### Prerequisite: Sonatype (Maven Central) Account
-
-Create an account on the [Sonatype issues site][sonatype_issues]. Ask an existing publisher to open
-an issue requesting publishing permissions for `com.squareup` projects.
-
-### Prerequisite: GPG Keys
-
-Generate a GPG key (RSA, 4096 bit, 3650 day) expiry, or use an existing one. You should leave the
-password empty for this key.
-
-```
-$ gpg --full-generate-key
-```
-
-Upload the GPG keys to public servers:
-
-```
-$ gpg --list-keys --keyid-format LONG
-/Users/johnbarber/.gnupg/pubring.kbx
-------------------------------
-pub rsa4096/XXXXXXXXXXXXXXXX 2019-07-16 [SC] [expires: 2029-07-13]
- YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
-uid [ultimate] John Barber <jbarber@squareup.com>
-sub rsa4096/ZZZZZZZZZZZZZZZZ 2019-07-16 [E] [expires: 2029-07-13]
-
-$ gpg --send-keys --keyserver keyserver.ubuntu.com XXXXXXXXXXXXXXXX
-```
-
-### Prerequisite: Gradle Properties
-
-Define publishing properties in `~/.gradle/gradle.properties`:
-
-```
-signing.keyId=1A2345F8
-signing.password=
-signing.secretKeyRingFile=/Users/jbarber/.gnupg/secring.gpg
-```
-
-`signing.keyId` is the GPG key's ID. Get it with this:
-
- ```
- $ gpg --list-keys --keyid-format SHORT
- ```
-
-`signing.password` is the password for this key. This might be empty!
-
-`signing.secretKeyRingFile` is the absolute path for `secring.gpg`. You may need to export this
-file manually with the following command where `XXXXXXXX` is the `keyId` above:
-
- ```
- $ gpg --keyring secring.gpg --export-secret-key XXXXXXXX > ~/.gnupg/secring.gpg
- ```
-
-
-Cutting a Release
------------------
-
1. Update `CHANGELOG.md`.
2. Set versions:
@@ -67,14 +10,7 @@ Cutting a Release
export NEXT_VERSION=X.Y.Z-SNAPSHOT
```
-3. Set environment variables with your [Sonatype credentials][sonatype_issues].
-
- ```
- export SONATYPE_NEXUS_USERNAME=johnbarber
- export SONATYPE_NEXUS_PASSWORD=`pbpaste`
- ```
-
-4. Update, build, and upload:
+3. Update versions, tag the release, and prepare for the next release.
```
sed -i "" \
@@ -82,24 +18,19 @@ Cutting a Release
gradle.properties
sed -i "" \
"s/\"com.squareup.okio:\([^\:]*\):[^\"]*\"/\"com.squareup.okio:\1:$RELEASE_VERSION\"/g" \
- `find . -name "README.md"`
- ./gradlew clean publish
- ```
-
-5. Visit [Sonatype Nexus][sonatype_nexus] to promote (close then release) the artifact. Or drop it
- if there is a problem!
+ `find . -name "index.md"`
-6. Tag the release, prepare for the next one, and push to GitHub.
-
- ```
git commit -am "Prepare for release $RELEASE_VERSION."
git tag -a parent-$RELEASE_VERSION -m "Version $RELEASE_VERSION"
+
sed -i "" \
"s/VERSION_NAME=.*/VERSION_NAME=$NEXT_VERSION/g" \
gradle.properties
git commit -am "Prepare next development version."
+
git push && git push --tags
```
- [sonatype_issues]: https://issues.sonatype.org/
- [sonatype_nexus]: https://oss.sonatype.org/
+4. Wait for [GitHub Actions][github_actions] to build and promote the release.
+
+[github_actions]: https://github.com/square/okio/actions
diff --git a/docs/security.md b/docs/security.md
index 1eb81491..03997d30 100644
--- a/docs/security.md
+++ b/docs/security.md
@@ -5,6 +5,7 @@ Security Policy
| Version | Supported |
| ------- | ---------- |
+| 3.x | ✅ |
| 2.x | ✅ |
| 1.x | ✅ |
diff --git a/gradle.properties b/gradle.properties
index 21f92f07..ed1f6d9a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,25 +1,15 @@
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
-android.enableJetifier=true
+
+android.enableJetifier=false
android.useAndroidX=true
+android.defaults.buildfeatures.buildconfig=false
+android.defaults.buildfeatures.aidl=false
+android.defaults.buildfeatures.renderscript=false
+android.defaults.buildfeatures.resvalues=false
+android.defaults.buildfeatures.shaders=false
-# Publishing SHA 256 and 512 hashses of maven-metadata is not supported by Sonatype and Nexus.
-# See https://github.com/gradle/gradle/issues/11308 and
-# https://issues.sonatype.org/browse/NEXUS-21802
-systemProp.org.gradle.internal.publish.checksums.insecure=true
+kotlin.mpp.stability.nowarn=true
GROUP=com.squareup.okio
-VERSION_NAME=2.11.0-SNAPSHOT
-
-POM_DESCRIPTION=A modern I/O API for Java
-
-POM_URL=https://github.com/square/okio/
-POM_SCM_URL=https://github.com/square/okio/
-POM_SCM_CONNECTION=scm:git:git://github.com/square/okio.git
-POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/square/okio.git
-
-POM_LICENCE_NAME=The Apache Software License, Version 2.0
-POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
-POM_LICENCE_DIST=repo
-
-POM_DEVELOPER_ID=square
-POM_DEVELOPER_NAME=Square, Inc.
+VERSION_NAME=3.7.0
+kotlin.mpp.commonizerLogLevel=info
diff --git a/gradle/gradle-mvn-mpp-push.gradle b/gradle/gradle-mvn-mpp-push.gradle
deleted file mode 100644
index 7ec94292..00000000
--- a/gradle/gradle-mvn-mpp-push.gradle
+++ /dev/null
@@ -1,137 +0,0 @@
-apply plugin: 'maven-publish'
-apply plugin: 'signing'
-apply plugin: 'org.jetbrains.dokka'
-
-def dokkaConfiguration = {
- outputDirectory.set(file("$rootDir/docs/2.x"))
-
- dokkaSourceSets {
- configureEach {
- reportUndocumented.set(false)
- skipDeprecated.set(true)
- jdkVersion.set(8)
- perPackageOption {
- matchingRegex.set("com\\.squareup.okio.*")
- suppress.set(true)
- }
- perPackageOption {
- matchingRegex.set("okio\\.internal.*")
- suppress.set(true)
- }
- }
- }
-}
-
-dokkaGfm.configure(dokkaConfiguration)
-dokkaHtml.configure(dokkaConfiguration)
-
-def rootRelativePath(path) {
- return rootProject.file(path).toString().replace('\\', '/')
-}
-
-dokkaHtml.pluginsMapConfiguration.set([
- "org.jetbrains.dokka.base.DokkaBase": """{ "customStyleSheets": ["${rootRelativePath("docs/css/dokka-logo.css")}"], "customAssets" : ["${rootRelativePath("docs/images/logo-square.png")}"]}"""
-])
-
-def isReleaseBuild() {
- return VERSION_NAME.contains("SNAPSHOT") == false
-}
-
-def getReleaseRepositoryUrl() {
- return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL :
- "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
-}
-
-def getSnapshotRepositoryUrl() {
- return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL :
- "https://oss.sonatype.org/content/repositories/snapshots/"
-}
-
-def getRepositoryUsername() {
- return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : ""
-}
-
-def getRepositoryPassword() {
- return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : ""
-}
-
-task emptySourcesJar(type: Jar) {
- classifier = 'sources'
-}
-
-task javadocsJar(type: Jar, dependsOn: dokkaGfm) {
- classifier = 'javadoc'
- from dokkaGfm.outputDirectory
-}
-
-signing {
- required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
- sign(publishing.publications)
-}
-
-publishing {
- publications.all {
- artifact javadocsJar
-
- pom.withXml {
- def root = asNode()
-
- root.children().last() + {
- resolveStrategy = Closure.DELEGATE_FIRST
-
- description POM_DESCRIPTION
- name POM_NAME
- url POM_URL
- licenses {
- license {
- name POM_LICENCE_NAME
- url POM_LICENCE_URL
- distribution POM_LICENCE_DIST
- }
- }
- scm {
- url POM_SCM_URL
- connection POM_SCM_CONNECTION
- developerConnection POM_SCM_DEV_CONNECTION
- }
- developers {
- developer {
- id POM_DEVELOPER_ID
- name POM_DEVELOPER_NAME
- }
- }
- }
- }
- }
-
- // Use default artifact name for the JVM target
- publications {
- kotlinMultiplatform {
- artifactId = POM_ARTIFACT_ID + '-multiplatform'
- }
- jvm {
- artifactId = POM_ARTIFACT_ID
- }
- }
-
- afterEvaluate {
- publications.getByName('kotlinMultiplatform') {
- // Source jars are only created for platforms, not the common artifact.
- artifact emptySourcesJar
- }
- }
-
- repositories {
- maven {
- url isReleaseBuild() ? getReleaseRepositoryUrl() : getSnapshotRepositoryUrl()
- credentials {
- username getRepositoryUsername()
- password getRepositoryPassword()
- }
- }
- maven {
- name 'test'
- url "file://${rootProject.buildDir}/localMaven"
- }
- }
-}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 00000000..ba610822
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,24 @@
+[versions]
+jmh = "1.37"
+ktlint = "0.48.2"
+
+[libraries]
+android-gradle-plugin = { module = "com.android.tools.build:gradle", version = "7.4.2" }
+android-desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs", version = "2.0.4" }
+androidx-test-ext-junit = { module = "androidx.test.ext:junit", version = "1.1.5" }
+androidx-test-runner = { module = "androidx.test:runner", version = "1.5.2" }
+binaryCompatibilityValidator = { module = "org.jetbrains.kotlinx.binary-compatibility-validator:org.jetbrains.kotlinx.binary-compatibility-validator.gradle.plugin", version = "0.13.2" }
+kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test" }
+kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit" }
+kotlin-time = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.5.0" }
+jmh-gradle-plugin = { module = "me.champeau.jmh:jmh-gradle-plugin", version = "0.7.2" }
+jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
+jmh-generator = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
+dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version = "1.9.10" }
+spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version = "6.23.3" }
+bnd = { module = "biz.aQute.bnd:biz.aQute.bnd.gradle", version = "6.4.0" }
+vanniktech-publish-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version = "0.25.3" }
+test-junit = { module = "junit:junit", version = "4.13.2" }
+test-assertj = { module = "org.assertj:assertj-core", version = "3.24.2" }
+test-assertk = "com.willowtreeapps.assertk:assertk:0.28.0"
+test-jimfs = "com.google.jimfs:jimfs:1.3.0"
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e708b1c0..7f93135c 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 4d9ca164..3fa8f862 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 4f906e0c..1aa94a42 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
-#!/usr/bin/env sh
+#!/bin/sh
#
-# Copyright 2015 the original author or authors.
+# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,67 +17,99 @@
#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
warn () {
echo "$*"
-}
+} >&2
die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
fi
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
# Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
fi
- i=`expr $i + 1`
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
fi
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index ac1b06f9..93e3f59f 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,89 +1,92 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock
new file mode 100644
index 00000000..c9f26541
--- /dev/null
+++ b/kotlin-js-store/yarn.lock
@@ -0,0 +1,1931 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@colors/colors@1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
+ integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
+
+"@discoveryjs/json-ext@^0.5.0":
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
+ integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
+
+"@jridgewell/gen-mapping@^0.3.0":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+ integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
+ integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/source-map@^0.3.3":
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91"
+ integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/sourcemap-codec@1.4.14":
+ version "1.4.14"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
+ integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+ version "1.4.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.18"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6"
+ integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==
+ dependencies:
+ "@jridgewell/resolve-uri" "3.1.0"
+ "@jridgewell/sourcemap-codec" "1.4.14"
+
+"@js-joda/core@3.2.0":
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273"
+ integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg==
+
+"@socket.io/component-emitter@~3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
+ integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
+
+"@types/cookie@^0.4.1":
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
+ integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==
+
+"@types/cors@^2.8.12":
+ version "2.8.13"
+ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.13.tgz#b8ade22ba455a1b8cb3b5d3f35910fd204f84f94"
+ integrity sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==
+ dependencies:
+ "@types/node" "*"
+
+"@types/eslint-scope@^3.7.3":
+ version "3.7.4"
+ resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
+ integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==
+ dependencies:
+ "@types/eslint" "*"
+ "@types/estree" "*"
+
+"@types/eslint@*":
+ version "8.44.0"
+ resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.0.tgz#55818eabb376e2272f77fbf5c96c43137c3c1e53"
+ integrity sha512-gsF+c/0XOguWgaOgvFs+xnnRqt9GwgTvIks36WpE6ueeI4KCEHHd8K/CKHqhOqrJKsYH8m27kRzQEvWXAwXUTw==
+ dependencies:
+ "@types/estree" "*"
+ "@types/json-schema" "*"
+
+"@types/estree@*", "@types/estree@^1.0.0":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
+ integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
+
+"@types/json-schema@*", "@types/json-schema@^7.0.8":
+ version "7.0.12"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
+ integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
+
+"@types/node@*", "@types/node@>=10.0.0":
+ version "20.4.1"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.1.tgz#a6033a8718653c50ac4962977e14d0f984d9527d"
+ integrity sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==
+
+"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24"
+ integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==
+ dependencies:
+ "@webassemblyjs/helper-numbers" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+
+"@webassemblyjs/floating-point-hex-parser@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431"
+ integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==
+
+"@webassemblyjs/helper-api-error@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768"
+ integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==
+
+"@webassemblyjs/helper-buffer@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093"
+ integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==
+
+"@webassemblyjs/helper-numbers@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5"
+ integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==
+ dependencies:
+ "@webassemblyjs/floating-point-hex-parser" "1.11.6"
+ "@webassemblyjs/helper-api-error" "1.11.6"
+ "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/helper-wasm-bytecode@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9"
+ integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==
+
+"@webassemblyjs/helper-wasm-section@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577"
+ integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+
+"@webassemblyjs/ieee754@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a"
+ integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==
+ dependencies:
+ "@xtuc/ieee754" "^1.2.0"
+
+"@webassemblyjs/leb128@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7"
+ integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==
+ dependencies:
+ "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/utf8@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a"
+ integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==
+
+"@webassemblyjs/wasm-edit@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab"
+ integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/helper-wasm-section" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+ "@webassemblyjs/wasm-opt" "1.11.6"
+ "@webassemblyjs/wasm-parser" "1.11.6"
+ "@webassemblyjs/wast-printer" "1.11.6"
+
+"@webassemblyjs/wasm-gen@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268"
+ integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/ieee754" "1.11.6"
+ "@webassemblyjs/leb128" "1.11.6"
+ "@webassemblyjs/utf8" "1.11.6"
+
+"@webassemblyjs/wasm-opt@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2"
+ integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+ "@webassemblyjs/wasm-parser" "1.11.6"
+
+"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1"
+ integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-api-error" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/ieee754" "1.11.6"
+ "@webassemblyjs/leb128" "1.11.6"
+ "@webassemblyjs/utf8" "1.11.6"
+
+"@webassemblyjs/wast-printer@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20"
+ integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@xtuc/long" "4.2.2"
+
+"@webpack-cli/configtest@^2.1.0":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646"
+ integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==
+
+"@webpack-cli/info@^2.0.1":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd"
+ integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==
+
+"@webpack-cli/serve@^2.0.3":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e"
+ integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==
+
+"@xtuc/ieee754@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
+ integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==
+
+"@xtuc/long@4.2.2":
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
+ integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
+
+abab@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
+ integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
+
+accepts@~1.3.4:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
+ integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
+ dependencies:
+ mime-types "~2.1.34"
+ negotiator "0.6.3"
+
+acorn-import-assertions@^1.7.6:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
+ integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
+
+acorn@^8.7.1, acorn@^8.8.2:
+ version "8.10.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
+ integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
+
+ajv-keywords@^3.5.2:
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
+ integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
+
+ajv@^6.12.5:
+ version "6.12.6"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
+ansi-colors@4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
+ integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+anymatch@~3.1.2:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
+ integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+base64id@2.0.0, base64id@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
+ integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
+
+binary-extensions@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
+ integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
+
+body-parser@^1.19.0:
+ version "1.20.2"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
+ integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
+ dependencies:
+ bytes "3.1.2"
+ content-type "~1.0.5"
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ on-finished "2.4.1"
+ qs "6.11.0"
+ raw-body "2.5.2"
+ type-is "~1.6.18"
+ unpipe "1.0.0"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+brace-expansion@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+ integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+ dependencies:
+ balanced-match "^1.0.0"
+
+braces@^3.0.2, braces@~3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
+browser-stdout@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
+ integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
+
+browserslist@^4.14.5:
+ version "4.21.9"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635"
+ integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==
+ dependencies:
+ caniuse-lite "^1.0.30001503"
+ electron-to-chromium "^1.4.431"
+ node-releases "^2.0.12"
+ update-browserslist-db "^1.0.11"
+
+buffer-from@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
+ integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+
+bytes@3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
+ integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
+call-bind@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+ integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
+ dependencies:
+ function-bind "^1.1.1"
+ get-intrinsic "^1.0.2"
+
+camelcase@^6.0.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
+ integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
+
+caniuse-lite@^1.0.30001503:
+ version "1.0.30001513"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001513.tgz#382fe5fbfb0f7abbaf8c55ca3ac71a0307a752e9"
+ integrity sha512-pnjGJo7SOOjAGytZZ203Em95MRM8Cr6jhCXNF/FAXTpCTRTECnqQWLpiTRqrFtdYcth8hf4WECUpkezuYsMVww==
+
+chalk@^4.1.0:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+chokidar@3.5.3, chokidar@^3.5.1:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+ dependencies:
+ anymatch "~3.1.2"
+ braces "~3.0.2"
+ glob-parent "~5.1.2"
+ is-binary-path "~2.1.0"
+ is-glob "~4.0.1"
+ normalize-path "~3.0.0"
+ readdirp "~3.6.0"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+chrome-trace-event@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
+ integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
+
+cliui@^7.0.2:
+ version "7.0.4"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
+ integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^7.0.0"
+
+clone-deep@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
+ integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
+ dependencies:
+ is-plain-object "^2.0.4"
+ kind-of "^6.0.2"
+ shallow-clone "^3.0.0"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+colorette@^2.0.14:
+ version "2.0.20"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
+ integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
+
+commander@^10.0.1:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
+ integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
+
+commander@^2.20.0:
+ version "2.20.3"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+connect@^3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8"
+ integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==
+ dependencies:
+ debug "2.6.9"
+ finalhandler "1.1.2"
+ parseurl "~1.3.3"
+ utils-merge "1.0.1"
+
+content-type@~1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
+ integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
+
+cookie@~0.4.1:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
+ integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
+
+cors@~2.8.5:
+ version "2.8.5"
+ resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
+ integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
+ dependencies:
+ object-assign "^4"
+ vary "^1"
+
+cross-spawn@^7.0.3:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+custom-event@~1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
+ integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==
+
+date-format@^4.0.14:
+ version "4.0.14"
+ resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400"
+ integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==
+
+debug@2.6.9:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@4.3.4, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+decamelize@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
+ integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
+
+depd@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
+destroy@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
+ integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
+
+di@^0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
+ integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==
+
+diff@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
+ integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
+
+dom-serialize@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b"
+ integrity sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==
+ dependencies:
+ custom-event "~1.0.0"
+ ent "~2.2.0"
+ extend "^3.0.0"
+ void-elements "^2.0.0"
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+ integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
+
+electron-to-chromium@^1.4.431:
+ version "1.4.454"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.454.tgz#774dc7cb5e58576d0125939ec34a4182f3ccc87d"
+ integrity sha512-pmf1rbAStw8UEQ0sr2cdJtWl48ZMuPD9Sto8HVQOq9vx9j2WgDEN6lYoaqFvqEHYOmGA9oRGn7LqWI9ta0YugQ==
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+encodeurl@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+ integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
+
+engine.io-parser@~5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.1.0.tgz#d593d6372d7f79212df48f807b8cace1ea1cb1b8"
+ integrity sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==
+
+engine.io@~6.5.0:
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.1.tgz#59725f8593ccc891abb47f1efcdc52a089525a56"
+ integrity sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==
+ dependencies:
+ "@types/cookie" "^0.4.1"
+ "@types/cors" "^2.8.12"
+ "@types/node" ">=10.0.0"
+ accepts "~1.3.4"
+ base64id "2.0.0"
+ cookie "~0.4.1"
+ cors "~2.8.5"
+ debug "~4.3.1"
+ engine.io-parser "~5.1.0"
+ ws "~8.11.0"
+
+enhanced-resolve@^5.13.0:
+ version "5.15.0"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35"
+ integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==
+ dependencies:
+ graceful-fs "^4.2.4"
+ tapable "^2.2.0"
+
+ent@~2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
+ integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==
+
+envinfo@^7.7.3:
+ version "7.10.0"
+ resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.10.0.tgz#55146e3909cc5fe63c22da63fb15b05aeac35b13"
+ integrity sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==
+
+es-module-lexer@^1.2.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.0.tgz#6be9c9e0b4543a60cd166ff6f8b4e9dae0b0c16f"
+ integrity sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==
+
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+ integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+
+escape-string-regexp@4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+eslint-scope@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+ integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^4.1.1"
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^4.1.1:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
+ integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
+
+estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+eventemitter3@^4.0.0:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
+ integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+
+events@^3.2.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
+ integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
+
+extend@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
+fast-deep-equal@^3.1.1:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fastest-levenshtein@^1.0.12:
+ version "1.0.16"
+ resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
+ integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==
+
+fill-range@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+finalhandler@1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
+ integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
+ dependencies:
+ debug "2.6.9"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ on-finished "~2.3.0"
+ parseurl "~1.3.3"
+ statuses "~1.5.0"
+ unpipe "~1.0.0"
+
+find-up@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+find-up@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+ integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+ dependencies:
+ locate-path "^5.0.0"
+ path-exists "^4.0.0"
+
+flat@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
+ integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
+
+flatted@^3.2.7:
+ version "3.2.7"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
+ integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
+
+follow-redirects@^1.0.0:
+ version "1.15.2"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
+ integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
+
+format-util@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271"
+ integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==
+
+fs-extra@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
+ integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^4.0.0"
+ universalify "^0.1.0"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+fsevents@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
+get-intrinsic@^1.0.2:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
+ integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-proto "^1.0.1"
+ has-symbols "^1.0.3"
+
+glob-parent@~5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob-to-regexp@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
+ integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
+
+glob@7.2.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
+ integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@^7.1.3, glob@^7.1.7:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.1.1"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
+ version "4.2.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
+ integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
+ integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
+
+has-symbols@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+he@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+ integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
+http-errors@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
+ integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
+ dependencies:
+ depd "2.0.0"
+ inherits "2.0.4"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ toidentifier "1.0.1"
+
+http-proxy@^1.18.1:
+ version "1.18.1"
+ resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
+ integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
+ dependencies:
+ eventemitter3 "^4.0.0"
+ follow-redirects "^1.0.0"
+ requires-port "^1.0.0"
+
+iconv-lite@0.4.24:
+ version "0.4.24"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+ integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
+iconv-lite@^0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
+ integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
+import-local@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4"
+ integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==
+ dependencies:
+ pkg-dir "^4.2.0"
+ resolve-cwd "^3.0.0"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+interpret@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4"
+ integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==
+
+is-binary-path@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+ integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+ dependencies:
+ binary-extensions "^2.0.0"
+
+is-core-module@^2.11.0:
+ version "2.12.1"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd"
+ integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==
+ dependencies:
+ has "^1.0.3"
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-glob@^4.0.1, is-glob@~4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-plain-obj@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
+ integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
+
+is-plain-object@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
+ integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
+ dependencies:
+ isobject "^3.0.1"
+
+is-unicode-supported@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
+ integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
+
+isbinaryfile@^4.0.8:
+ version "4.0.10"
+ resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3"
+ integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+isobject@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
+ integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
+
+jest-worker@^27.4.5:
+ version "27.5.1"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0"
+ integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==
+ dependencies:
+ "@types/node" "*"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
+
+js-yaml@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
+json-parse-even-better-errors@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+ integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+jsonfile@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
+ integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+karma-chrome-launcher@3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz#eb9c95024f2d6dfbb3748d3415ac9b381906b9a9"
+ integrity sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==
+ dependencies:
+ which "^1.2.1"
+
+karma-mocha@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.1.tgz#4b0254a18dfee71bdbe6188d9a6861bf86b0cd7d"
+ integrity sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==
+ dependencies:
+ minimist "^1.2.3"
+
+karma-sourcemap-loader@0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.4.0.tgz#b01d73f8f688f533bcc8f5d273d43458e13b5488"
+ integrity sha512-xCRL3/pmhAYF3I6qOrcn0uhbQevitc2DERMPH82FMnG+4WReoGcGFZb1pURf2a5apyrOHRdvD+O6K7NljqKHyA==
+ dependencies:
+ graceful-fs "^4.2.10"
+
+karma-webpack@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.0.tgz#2a2c7b80163fe7ffd1010f83f5507f95ef39f840"
+ integrity sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==
+ dependencies:
+ glob "^7.1.3"
+ minimatch "^3.0.4"
+ webpack-merge "^4.1.5"
+
+karma@6.4.2:
+ version "6.4.2"
+ resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.2.tgz#a983f874cee6f35990c4b2dcc3d274653714de8e"
+ integrity sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ==
+ dependencies:
+ "@colors/colors" "1.5.0"
+ body-parser "^1.19.0"
+ braces "^3.0.2"
+ chokidar "^3.5.1"
+ connect "^3.7.0"
+ di "^0.0.1"
+ dom-serialize "^2.2.1"
+ glob "^7.1.7"
+ graceful-fs "^4.2.6"
+ http-proxy "^1.18.1"
+ isbinaryfile "^4.0.8"
+ lodash "^4.17.21"
+ log4js "^6.4.1"
+ mime "^2.5.2"
+ minimatch "^3.0.4"
+ mkdirp "^0.5.5"
+ qjobs "^1.2.0"
+ range-parser "^1.2.1"
+ rimraf "^3.0.2"
+ socket.io "^4.4.1"
+ source-map "^0.6.1"
+ tmp "^0.2.1"
+ ua-parser-js "^0.7.30"
+ yargs "^16.1.1"
+
+kind-of@^6.0.2:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
+ integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+
+loader-runner@^4.2.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
+ integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
+
+locate-path@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+ integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+ dependencies:
+ p-locate "^4.1.0"
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+lodash@^4.17.15, lodash@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+log-symbols@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
+ integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
+ dependencies:
+ chalk "^4.1.0"
+ is-unicode-supported "^0.1.0"
+
+log4js@^6.4.1:
+ version "6.9.1"
+ resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6"
+ integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==
+ dependencies:
+ date-format "^4.0.14"
+ debug "^4.3.4"
+ flatted "^3.2.7"
+ rfdc "^1.3.0"
+ streamroller "^3.1.5"
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+ integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
+
+merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mime@^2.5.2:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
+ integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
+
+minimatch@5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
+ integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimatch@^3.0.4, minimatch@^3.1.1:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@^1.2.3, minimist@^1.2.6:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
+mkdirp@^0.5.5:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
+ integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
+ dependencies:
+ minimist "^1.2.6"
+
+mocha@10.2.0:
+ version "10.2.0"
+ resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8"
+ integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==
+ dependencies:
+ ansi-colors "4.1.1"
+ browser-stdout "1.3.1"
+ chokidar "3.5.3"
+ debug "4.3.4"
+ diff "5.0.0"
+ escape-string-regexp "4.0.0"
+ find-up "5.0.0"
+ glob "7.2.0"
+ he "1.2.0"
+ js-yaml "4.1.0"
+ log-symbols "4.1.0"
+ minimatch "5.0.1"
+ ms "2.1.3"
+ nanoid "3.3.3"
+ serialize-javascript "6.0.0"
+ strip-json-comments "3.1.1"
+ supports-color "8.1.1"
+ workerpool "6.2.1"
+ yargs "16.2.0"
+ yargs-parser "20.2.4"
+ yargs-unparser "2.0.0"
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+ms@2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+nanoid@3.3.3:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
+ integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
+
+negotiator@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
+ integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+
+neo-async@^2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
+ integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+
+node-releases@^2.0.12:
+ version "2.0.13"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
+ integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+object-assign@^4:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
+object-inspect@^1.9.0:
+ version "1.12.3"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
+ integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
+
+on-finished@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ dependencies:
+ ee-first "1.1.1"
+
+on-finished@~2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+ integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==
+ dependencies:
+ ee-first "1.1.1"
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+p-limit@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+ integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
+ dependencies:
+ p-try "^2.0.0"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+ integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+ dependencies:
+ p-limit "^2.2.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+p-try@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+ integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+
+parseurl@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+ integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+picocolors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+picomatch@^2.0.4, picomatch@^2.2.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+pkg-dir@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
+ integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+ dependencies:
+ find-up "^4.0.0"
+
+punycode@^2.1.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
+ integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
+
+qjobs@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
+ integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==
+
+qs@6.11.0:
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
+ integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
+ dependencies:
+ side-channel "^1.0.4"
+
+randombytes@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+ integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
+ dependencies:
+ safe-buffer "^5.1.0"
+
+range-parser@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+ integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+raw-body@2.5.2:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
+ integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
+ dependencies:
+ bytes "3.1.2"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ unpipe "1.0.0"
+
+readdirp@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+ integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+ dependencies:
+ picomatch "^2.2.1"
+
+rechoir@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22"
+ integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==
+ dependencies:
+ resolve "^1.20.0"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
+requires-port@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+ integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
+
+resolve-cwd@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
+ integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==
+ dependencies:
+ resolve-from "^5.0.0"
+
+resolve-from@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
+ integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
+
+resolve@^1.20.0:
+ version "1.22.2"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
+ integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==
+ dependencies:
+ is-core-module "^2.11.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+rfdc@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
+ integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
+
+rimraf@^3.0.0, rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
+safe-buffer@^5.1.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+schema-utils@^3.1.1, schema-utils@^3.1.2:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
+ integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
+ dependencies:
+ "@types/json-schema" "^7.0.8"
+ ajv "^6.12.5"
+ ajv-keywords "^3.5.2"
+
+serialize-javascript@6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
+ integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
+ dependencies:
+ randombytes "^2.1.0"
+
+serialize-javascript@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c"
+ integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==
+ dependencies:
+ randombytes "^2.1.0"
+
+setprototypeof@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+ integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
+shallow-clone@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
+ integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
+ dependencies:
+ kind-of "^6.0.2"
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+side-channel@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+ integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+ dependencies:
+ call-bind "^1.0.0"
+ get-intrinsic "^1.0.2"
+ object-inspect "^1.9.0"
+
+socket.io-adapter@~2.5.2:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12"
+ integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==
+ dependencies:
+ ws "~8.11.0"
+
+socket.io-parser@~4.2.4:
+ version "4.2.4"
+ resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83"
+ integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==
+ dependencies:
+ "@socket.io/component-emitter" "~3.1.0"
+ debug "~4.3.1"
+
+socket.io@^4.4.1:
+ version "4.7.1"
+ resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.1.tgz#9009f31bf7be25478895145e92fbc972ad1db900"
+ integrity sha512-W+utHys2w//dhFjy7iQQu9sGd3eokCjGbl2r59tyLqNiJJBdIebn3GAKEXBr3osqHTObJi2die/25bCx2zsaaw==
+ dependencies:
+ accepts "~1.3.4"
+ base64id "~2.0.0"
+ cors "~2.8.5"
+ debug "~4.3.2"
+ engine.io "~6.5.0"
+ socket.io-adapter "~2.5.2"
+ socket.io-parser "~4.2.4"
+
+source-map-js@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+source-map-loader@4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-4.0.1.tgz#72f00d05f5d1f90f80974eda781cbd7107c125f2"
+ integrity sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==
+ dependencies:
+ abab "^2.0.6"
+ iconv-lite "^0.6.3"
+ source-map-js "^1.0.2"
+
+source-map-support@0.5.21, source-map-support@~0.5.20:
+ version "0.5.21"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
+ integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
+ dependencies:
+ buffer-from "^1.0.0"
+ source-map "^0.6.0"
+
+source-map@^0.6.0, source-map@^0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+statuses@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
+ integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+
+statuses@~1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
+ integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
+
+streamroller@^3.1.5:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff"
+ integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==
+ dependencies:
+ date-format "^4.0.14"
+ debug "^4.3.4"
+ fs-extra "^8.1.0"
+
+string-width@^4.1.0, string-width@^4.2.0:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-json-comments@3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+supports-color@8.1.1, supports-color@^8.0.0:
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+tapable@^2.1.1, tapable@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
+ integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
+
+terser-webpack-plugin@^5.3.7:
+ version "5.3.9"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1"
+ integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jest-worker "^27.4.5"
+ schema-utils "^3.1.1"
+ serialize-javascript "^6.0.1"
+ terser "^5.16.8"
+
+terser@^5.16.8:
+ version "5.18.2"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.18.2.tgz#ff3072a0faf21ffd38f99acc9a0ddf7b5f07b948"
+ integrity sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==
+ dependencies:
+ "@jridgewell/source-map" "^0.3.3"
+ acorn "^8.8.2"
+ commander "^2.20.0"
+ source-map-support "~0.5.20"
+
+tmp@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
+ integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
+ dependencies:
+ rimraf "^3.0.0"
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+toidentifier@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
+ integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+
+type-is@~1.6.18:
+ version "1.6.18"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+ integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.24"
+
+typescript@5.0.4:
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
+ integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==
+
+ua-parser-js@^0.7.30:
+ version "0.7.35"
+ resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.35.tgz#8bda4827be4f0b1dda91699a29499575a1f1d307"
+ integrity sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==
+
+universalify@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
+ integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
+
+unpipe@1.0.0, unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+ integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
+
+update-browserslist-db@^1.0.11:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940"
+ integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+utils-merge@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+ integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
+
+vary@^1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+ integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
+
+void-elements@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
+ integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==
+
+watchpack@^2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
+ integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
+ dependencies:
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.1.2"
+
+webpack-cli@5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.0.tgz#abc4b1f44b50250f2632d8b8b536cfe2f6257891"
+ integrity sha512-a7KRJnCxejFoDpYTOwzm5o21ZXMaNqtRlvS183XzGDUPRdVEzJNImcQokqYZ8BNTnk9DkKiuWxw75+DCCoZ26w==
+ dependencies:
+ "@discoveryjs/json-ext" "^0.5.0"
+ "@webpack-cli/configtest" "^2.1.0"
+ "@webpack-cli/info" "^2.0.1"
+ "@webpack-cli/serve" "^2.0.3"
+ colorette "^2.0.14"
+ commander "^10.0.1"
+ cross-spawn "^7.0.3"
+ envinfo "^7.7.3"
+ fastest-levenshtein "^1.0.12"
+ import-local "^3.0.2"
+ interpret "^3.1.1"
+ rechoir "^0.8.0"
+ webpack-merge "^5.7.3"
+
+webpack-merge@^4.1.5:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d"
+ integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==
+ dependencies:
+ lodash "^4.17.15"
+
+webpack-merge@^5.7.3:
+ version "5.9.0"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826"
+ integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==
+ dependencies:
+ clone-deep "^4.0.1"
+ wildcard "^2.0.0"
+
+webpack-sources@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
+ integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
+
+webpack@5.82.0:
+ version "5.82.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.82.0.tgz#3c0d074dec79401db026b4ba0fb23d6333f88e7d"
+ integrity sha512-iGNA2fHhnDcV1bONdUu554eZx+XeldsaeQ8T67H6KKHl2nUSwX8Zm7cmzOA46ox/X1ARxf7Bjv8wQ/HsB5fxBg==
+ dependencies:
+ "@types/eslint-scope" "^3.7.3"
+ "@types/estree" "^1.0.0"
+ "@webassemblyjs/ast" "^1.11.5"
+ "@webassemblyjs/wasm-edit" "^1.11.5"
+ "@webassemblyjs/wasm-parser" "^1.11.5"
+ acorn "^8.7.1"
+ acorn-import-assertions "^1.7.6"
+ browserslist "^4.14.5"
+ chrome-trace-event "^1.0.2"
+ enhanced-resolve "^5.13.0"
+ es-module-lexer "^1.2.1"
+ eslint-scope "5.1.1"
+ events "^3.2.0"
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.2.9"
+ json-parse-even-better-errors "^2.3.1"
+ loader-runner "^4.2.0"
+ mime-types "^2.1.27"
+ neo-async "^2.6.2"
+ schema-utils "^3.1.2"
+ tapable "^2.1.1"
+ terser-webpack-plugin "^5.3.7"
+ watchpack "^2.4.0"
+ webpack-sources "^3.2.3"
+
+which@^1.2.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+ integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+ dependencies:
+ isexe "^2.0.0"
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+wildcard@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67"
+ integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==
+
+workerpool@6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
+ integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
+
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+ws@~8.11.0:
+ version "8.11.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
+ integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
+
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yargs-parser@20.2.4:
+ version "20.2.4"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
+ integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
+
+yargs-parser@^20.2.2:
+ version "20.2.9"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
+ integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+
+yargs-unparser@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
+ integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
+ dependencies:
+ camelcase "^6.0.0"
+ decamelize "^4.0.0"
+ flat "^5.0.2"
+ is-plain-obj "^2.1.0"
+
+yargs@16.2.0, yargs@^16.1.1:
+ version "16.2.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
+ integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
+ dependencies:
+ cliui "^7.0.2"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.0"
+ y18n "^5.0.5"
+ yargs-parser "^20.2.2"
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/mkdocs.yml b/mkdocs.yml
index 11fd77fb..8fd117c0 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,4 +1,5 @@
site_name: Okio
+site_url: https://square.github.io/okio
repo_name: Okio
repo_url: https://github.com/square/okio
site_description: "A modern I/O library for Android, Kotlin, and Java."
@@ -13,8 +14,22 @@ theme:
favicon: images/icon-square.png
logo: images/icon-square.png
palette:
- primary: 'deep purple'
- accent: 'white'
+ # Palette toggle for light mode
+ - scheme: default
+ media: "(prefers-color-scheme: light)"
+ primary: 'deep purple'
+ accent: 'white'
+ toggle:
+ icon: material/brightness-7
+ name: Switch to dark mode
+ # Palette toggle for dark mode
+ - scheme: slate
+ media: "(prefers-color-scheme: dark)"
+ primary: 'deep purple'
+ accent: 'white'
+ toggle:
+ icon: material/brightness-4
+ name: Switch to light mode
icon:
repo: fontawesome/brands/github
@@ -43,17 +58,23 @@ markdown_extensions:
- pymdownx.magiclink
- pymdownx.smartsymbols
- pymdownx.superfences
- - pymdownx.tabbed
+ - pymdownx.tabbed:
+ alternate_style: true
- pymdownx.tilde
- tables
nav:
- 'Overview': index.md
- 'Stack Overflow ⏏': https://stackoverflow.com/questions/tagged/okio?sort=active
- - '2.x API': 2.x/okio/okio/index.html
+ - 'Recipes': recipes.md
+ - 'java.io Recipes': java_io_recipes.md
+ - '3.x API':
+ - 'okio': 3.x/okio/okio/okio/index.html
+ - 'fakefilesystem': 3.x/okio-fakefilesystem/okio-fakefilesystem/okio.fakefilesystem/-fake-file-system/index.html
+ - 'nodefilesystem': 3.x/okio-nodefilesystem/okio-nodefilesystem/okio/-node-js-file-system/index.html
- '1.x API ⏏': https://square.github.io/okio/1.x/okio/
- 'Change Log': changelog.md
+ - 'File System': file_system.md
- 'Multiplatform': multiplatform.md
- 'Contributing': contributing.md
- 'Code of Conduct': code_of_conduct.md
-
diff --git a/okio-assetfilesystem/README.md b/okio-assetfilesystem/README.md
new file mode 100644
index 00000000..9258ed28
--- /dev/null
+++ b/okio-assetfilesystem/README.md
@@ -0,0 +1,5 @@
+Okio Asset File System
+----------------------
+
+This module contains a `FileSystem` implementation for
+an Android application's `assets/` contents.
diff --git a/okio-assetfilesystem/api/okio-assetfilesystem.api b/okio-assetfilesystem/api/okio-assetfilesystem.api
new file mode 100644
index 00000000..773a8e2c
--- /dev/null
+++ b/okio-assetfilesystem/api/okio-assetfilesystem.api
@@ -0,0 +1,4 @@
+public final class okio/assetfilesystem/AssetFileSystemKt {
+ public static final fun asFileSystem (Landroid/content/res/AssetManager;)Lokio/FileSystem;
+}
+
diff --git a/okio-assetfilesystem/build.gradle.kts b/okio-assetfilesystem/build.gradle.kts
new file mode 100644
index 00000000..b323aea4
--- /dev/null
+++ b/okio-assetfilesystem/build.gradle.kts
@@ -0,0 +1,36 @@
+import com.vanniktech.maven.publish.AndroidSingleVariantLibrary
+import com.vanniktech.maven.publish.MavenPublishBaseExtension
+
+plugins {
+ id("com.android.library")
+ kotlin("android")
+ id("org.jetbrains.dokka")
+ id("com.vanniktech.maven.publish.base")
+ id("binary-compatibility-validator")
+ id("build-support")
+}
+
+android {
+ namespace = "okio.assetfilesystem"
+ compileSdk = 33
+
+ defaultConfig {
+ minSdk = 14
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+}
+
+dependencies {
+ api(projects.okio)
+
+ androidTestImplementation(libs.androidx.test.runner)
+ androidTestImplementation(libs.kotlin.test)
+ androidTestImplementation(libs.test.assertk)
+}
+
+configure<MavenPublishBaseExtension> {
+ configure(
+ AndroidSingleVariantLibrary()
+ )
+}
diff --git a/okio-assetfilesystem/src/androidTest/assets/dir/nested.txt b/okio-assetfilesystem/src/androidTest/assets/dir/nested.txt
new file mode 100644
index 00000000..eefc327e
--- /dev/null
+++ b/okio-assetfilesystem/src/androidTest/assets/dir/nested.txt
@@ -0,0 +1 @@
+Nested!
diff --git a/okio-assetfilesystem/src/androidTest/assets/file.txt b/okio-assetfilesystem/src/androidTest/assets/file.txt
new file mode 100644
index 00000000..9b72baa6
--- /dev/null
+++ b/okio-assetfilesystem/src/androidTest/assets/file.txt
@@ -0,0 +1 @@
+File!
diff --git a/okio-assetfilesystem/src/androidTest/assets/moby10b.txt b/okio-assetfilesystem/src/androidTest/assets/moby10b.txt
new file mode 100644
index 00000000..e052542e
--- /dev/null
+++ b/okio-assetfilesystem/src/androidTest/assets/moby10b.txt
@@ -0,0 +1,23244 @@
+**The Project Gutenberg Etext of Moby Dick, by Herman Melville**
+#3 in our series by Herman Melville
+
+This Project Gutenberg version of Moby Dick is based on a combination
+of the etext from the ERIS project at Virginia Tech and another from
+Project Gutenberg's archives, as compared to a public-domain hard copy.
+
+Copyright laws are changing all over the world, be sure to check
+the copyright laws for your country before posting these files!!
+
+Please take a look at the important information in this header.
+We encourage you to keep this file on your own disk, keeping an
+electronic path open for the next readers. Do not remove this.
+
+
+**Welcome To The World of Free Plain Vanilla Electronic Texts**
+
+**Etexts Readable By Both Humans and By Computers, Since 1971**
+
+*These Etexts Prepared By Hundreds of Volunteers and Donations*
+
+Information on contacting Project Gutenberg to get Etexts, and
+further information is included below. We need your donations.
+
+
+Title: Moby Dick; or The Whale
+
+Author: Herman Melville
+
+June, 2001 [Etext #2701]
+
+**The Project Gutenberg Etext of Moby Dick, by Herman Melville**
+******This file should be named moby10b.txt or moby10b.zip******
+
+Corrected EDITIONS of our etexts get a new NUMBER, moby11b.txt
+VERSIONS based on separate sources get new LETTER, moby10c.txt
+
+This etext was prepared by Daniel Lazarus and Jonesey
+
+Project Gutenberg Etexts are usually created from multiple editions,
+all of which are in the Public Domain in the United States, unless a
+copyright notice is included. Therefore, we usually do NOT keep any
+of these books in compliance with any particular paper edition.
+
+
+We are now trying to release all our books one month in advance
+of the official release dates, leaving time for better editing.
+
+Please note: neither this list nor its contents are final till
+midnight of the last day of the month of any such announcement.
+The official release date of all Project Gutenberg Etexts is at
+Midnight, Central Time, of the last day of the stated month. A
+preliminary version may often be posted for suggestion, comment
+and editing by those who wish to do so. To be sure you have an
+up to date first edition [xxxxx10x.xxx] please check file sizes
+in the first week of the next month. Since our ftp program has
+a bug in it that scrambles the date [tried to fix and failed] a
+look at the file size will have to do, but we will try to see a
+new copy has at least one byte more or less.
+
+
+Information about Project Gutenberg (one page)
+
+We produce about two million dollars for each hour we work. The
+time it takes us, a rather conservative estimate, is fifty hours
+to get any etext selected, entered, proofread, edited, copyright
+searched and analyzed, the copyright letters written, etc. This
+projected audience is one hundred million readers. If our value
+per text is nominally estimated at one dollar then we produce $2
+million dollars per hour this year as we release thirty-six text
+files per month, or 432 more Etexts in 1999 for a total of 2000+
+If these reach just 10% of the computerized population, then the
+total should reach over 200 billion Etexts given away this year.
+
+The Goal of Project Gutenberg is to Give Away One Trillion Etext
+Files by December 31, 2001. [10,000 x 100,000,000 = 1 Trillion]
+This is ten thousand titles each to one hundred million readers,
+which is only ~5% of the present number of computer users.
+
+At our revised rates of production, we will reach only one-third
+of that goal by the end of 2001, or about 3,333 Etexts unless we
+manage to get some real funding; currently our funding is mostly
+from Michael Hart's salary at Carnegie-Mellon University, and an
+assortment of sporadic gifts; this salary is only good for a few
+more years, so we are looking for something to replace it, as we
+don't want Project Gutenberg to be so dependent on one person.
+
+We need your donations more than ever!
+
+
+All donations should be made to "Project Gutenberg/CMU": and are
+tax deductible to the extent allowable by law. (CMU = Carnegie-
+Mellon University).
+
+For these and other matters, please mail to:
+
+Project Gutenberg
+P. O. Box 2782
+Champaign, IL 61825
+
+When all other email fails. . .try our Executive Director:
+Michael S. Hart <hart@pobox.com>
+hart@pobox.com forwards to hart@prairienet.org and archive.org
+if your mail bounces from archive.org, I will still see it, if
+it bounces from prairienet.org, better resend later on. . . .
+
+We would prefer to send you this information by email.
+
+******
+
+To access Project Gutenberg etexts, use any Web browser
+to view http://promo.net/pg. This site lists Etexts by
+author and by title, and includes information about how
+to get involved with Project Gutenberg. You could also
+download our past Newsletters, or subscribe here. This
+is one of our major sites, please email hart@pobox.com,
+for a more complete list of our various sites.
+
+To go directly to the etext collections, use FTP or any
+Web browser to visit a Project Gutenberg mirror (mirror
+sites are available on 7 continents; mirrors are listed
+at http://promo.net/pg).
+
+Mac users, do NOT point and click, typing works better.
+
+Example FTP session:
+
+ftp sunsite.unc.edu
+login: anonymous
+password: your@login
+cd pub/docs/books/gutenberg
+cd etext90 through etext99
+dir [to see files]
+get or mget [to get files. . .set bin for zip files]
+GET GUTINDEX.?? [to get a year's listing of books, e.g., GUTINDEX.99]
+GET GUTINDEX.ALL [to get a listing of ALL books]
+
+***
+
+**Information prepared by the Project Gutenberg legal advisor**
+
+(Three Pages)
+
+
+***START**THE SMALL PRINT!**FOR PUBLIC DOMAIN ETEXTS**START***
+Why is this "Small Print!" statement here? You know: lawyers.
+They tell us you might sue us if there is something wrong with
+your copy of this etext, even if you got it for free from
+someone other than us, and even if what's wrong is not our
+fault. So, among other things, this "Small Print!" statement
+disclaims most of our liability to you. It also tells you how
+you can distribute copies of this etext if you want to.
+
+*BEFORE!* YOU USE OR READ THIS ETEXT
+By using or reading any part of this PROJECT GUTENBERG-tm
+etext, you indicate that you understand, agree to and accept
+this "Small Print!" statement. If you do not, you can receive
+a refund of the money (if any) you paid for this etext by
+sending a request within 30 days of receiving it to the person
+you got it from. If you received this etext on a physical
+medium (such as a disk), you must return it with your request.
+
+ABOUT PROJECT GUTENBERG-TM ETEXTS
+This PROJECT GUTENBERG-tm etext, like most PROJECT GUTENBERG-
+tm etexts, is a "public domain" work distributed by Professor
+Michael S. Hart through the Project Gutenberg Association at
+Carnegie-Mellon University (the "Project"). Among other
+things, this means that no one owns a United States copyright
+on or for this work, so the Project (and you!) can copy and
+distribute it in the United States without permission and
+without paying copyright royalties. Special rules, set forth
+below, apply if you wish to copy and distribute this etext
+under the Project's "PROJECT GUTENBERG" trademark.
+
+To create these etexts, the Project expends considerable
+efforts to identify, transcribe and proofread public domain
+works. Despite these efforts, the Project's etexts and any
+medium they may be on may contain "Defects". Among other
+things, Defects may take the form of incomplete, inaccurate or
+corrupt data, transcription errors, a copyright or other
+intellectual property infringement, a defective or damaged
+disk or other etext medium, a computer virus, or computer
+codes that damage or cannot be read by your equipment.
+
+LIMITED WARRANTY; DISCLAIMER OF DAMAGES
+But for the "Right of Replacement or Refund" described below,
+[1] the Project (and any other party you may receive this
+etext from as a PROJECT GUTENBERG-tm etext) disclaims all
+liability to you for damages, costs and expenses, including
+legal fees, and [2] YOU HAVE NO REMEDIES FOR NEGLIGENCE OR
+UNDER STRICT LIABILITY, OR FOR BREACH OF WARRANTY OR CONTRACT,
+INCLUDING BUT NOT LIMITED TO INDIRECT, CONSEQUENTIAL, PUNITIVE
+OR INCIDENTAL DAMAGES, EVEN IF YOU GIVE NOTICE OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+If you discover a Defect in this etext within 90 days of
+receiving it, you can receive a refund of the money (if any)
+you paid for it by sending an explanatory note within that
+time to the person you received it from. If you received it
+on a physical medium, you must return it with your note, and
+such person may choose to alternatively give you a replacement
+copy. If you received it electronically, such person may
+choose to alternatively give you a second opportunity to
+receive it electronically.
+
+THIS ETEXT IS OTHERWISE PROVIDED TO YOU "AS-IS". NO OTHER
+WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, ARE MADE TO YOU AS
+TO THE ETEXT OR ANY MEDIUM IT MAY BE ON, INCLUDING BUT NOT
+LIMITED TO WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
+PARTICULAR PURPOSE.
+
+Some states do not allow disclaimers of implied warranties or
+the exclusion or limitation of consequential damages, so the
+above disclaimers and exclusions may not apply to you, and you
+may have other legal rights.
+
+INDEMNITY
+You will indemnify and hold the Project, its directors,
+officers, members and agents harmless from all liability, cost
+and expense, including legal fees, that arise directly or
+indirectly from any of the following that you do or cause:
+[1] distribution of this etext, [2] alteration, modification,
+or addition to the etext, or [3] any Defect.
+
+DISTRIBUTION UNDER "PROJECT GUTENBERG-tm"
+You may distribute copies of this etext electronically, or by
+disk, book or any other medium if you either delete this
+"Small Print!" and all other references to Project Gutenberg,
+or:
+
+[1] Only give exact copies of it. Among other things, this
+ requires that you do not remove, alter or modify the
+ etext or this "small print!" statement. You may however,
+ if you wish, distribute this etext in machine readable
+ binary, compressed, mark-up, or proprietary form,
+ including any form resulting from conversion by word pro-
+ cessing or hypertext software, but only so long as
+ *EITHER*:
+
+ [*] The etext, when displayed, is clearly readable, and
+ does *not* contain characters other than those
+ intended by the author of the work, although tilde
+ (~), asterisk (*) and underline (_) characters may
+ be used to convey punctuation intended by the
+ author, and additional characters may be used to
+ indicate hypertext links; OR
+
+ [*] The etext may be readily converted by the reader at
+ no expense into plain ASCII, EBCDIC or equivalent
+ form by the program that displays the etext (as is
+ the case, for instance, with most word processors);
+ OR
+
+ [*] You provide, or agree to also provide on request at
+ no additional cost, fee or expense, a copy of the
+ etext in its original plain ASCII form (or in EBCDIC
+ or other equivalent proprietary form).
+
+[2] Honor the etext refund and replacement provisions of this
+ "Small Print!" statement.
+
+[3] Pay a trademark license fee to the Project of 20% of the
+ net profits you derive calculated using the method you
+ already use to calculate your applicable taxes. If you
+ don't derive profits, no royalty is due. Royalties are
+ payable to "Project Gutenberg Association/Carnegie-Mellon
+ University" within the 60 days following each
+ date you prepare (or were legally required to prepare)
+ your annual (or equivalent periodic) tax return.
+
+WHAT IF YOU *WANT* TO SEND MONEY EVEN IF YOU DON'T HAVE TO?
+The Project gratefully accepts contributions in money, time,
+scanning machines, OCR software, public domain etexts, royalty
+free copyright licenses, and every other sort of contribution
+you can think of. Money should be paid to "Project Gutenberg
+Association / Carnegie-Mellon University".
+
+We are planning on making some changes in our donation structure
+in 2000, so you might want to email me, hart@pobox.com beforehand.
+
+
+
+
+*END THE SMALL PRINT! FOR PUBLIC DOMAIN ETEXTS*Ver.04.29.93*END*
+
+
+
+
+
+This etext was prepared by Daniel Lazarus and Jonesey
+
+
+
+
+
+Notes on this etext of Moby Dick:
+
+This text is a combination of etexts, one from the now-defunct ERIS
+project at Virginia Tech and one from Project Gutenberg's archives.
+The proofreaders of this version are indebted to The University of
+Adelaide Library for preserving the Virginia Tech version. The
+resulting etext was compared with a public domain hard copy version of
+the text.
+
+In chapters 24, 89, and 90, we substituted a capital L for the symbol
+for the British pound, a unit of currency.
+
+
+
+
+
+MOBY DICK; OR THE WHALE
+
+by Herman Melville
+
+
+
+
+ETYMOLOGY.
+
+(Supplied by a Late Consumptive Usher to a Grammar School)
+
+The pale Usher--threadbare in coat, heart, body, and brain; I see him
+now. He was ever dusting his old lexicons and grammars, with a queer
+handkerchief, mockingly embellished with all the gay flags of all the
+known nations of the world. He loved to dust his old grammars; it
+somehow mildly reminded him of his mortality.
+
+"While you take in hand to school others, and to teach them by what
+name a whale-fish is to be called in our tongue leaving out, through
+ignorance, the letter H, which almost alone maketh the signification
+of the word, you deliver that which is not true." --HACKLUYT
+
+"WHALE. ... Sw. and Dan. HVAL. This animal is named from roundness
+or rolling; for in Dan. HVALT is arched or vaulted." --WEBSTER'S
+DICTIONARY
+
+"WHALE. ... It is more immediately from the Dut. and Ger. WALLEN;
+A.S. WALW-IAN, to roll, to wallow." --RICHARDSON'S DICTIONARY
+
+KETOS, GREEK.
+CETUS, LATIN.
+WHOEL, ANGLO-SAXON.
+HVALT, DANISH.
+WAL, DUTCH.
+HWAL, SWEDISH.
+WHALE, ICELANDIC.
+WHALE, ENGLISH.
+BALEINE, FRENCH.
+BALLENA, SPANISH.
+PEKEE-NUEE-NUEE, FEGEE.
+PEKEE-NUEE-NUEE, ERROMANGOAN.
+
+
+
+
+EXTRACTS (Supplied by a Sub-Sub-Librarian).
+
+It will be seen that this mere painstaking burrower and grub-worm of
+a poor devil of a Sub-Sub appears to have gone through the long
+Vaticans and street-stalls of the earth, picking up whatever random
+allusions to whales he could anyways find in any book whatsoever,
+sacred or profane. Therefore you must not, in every case at least,
+take the higgledy-piggledy whale statements, however authentic, in
+these extracts, for veritable gospel cetology. Far from it. As
+touching the ancient authors generally, as well as the poets here
+appearing, these extracts are solely valuable or entertaining, as
+affording a glancing bird's eye view of what has been promiscuously
+said, thought, fancied, and sung of Leviathan, by many nations and
+generations, including our own.
+
+So fare thee well, poor devil of a Sub-Sub, whose commentator I am.
+Thou belongest to that hopeless, sallow tribe which no wine of this
+world will ever warm; and for whom even Pale Sherry would be too
+rosy-strong; but with whom one sometimes loves to sit, and feel
+poor-devilish, too; and grow convivial upon tears; and say to them
+bluntly, with full eyes and empty glasses, and in not altogether
+unpleasant sadness--Give it up, Sub-Subs! For by how much the more
+pains ye take to please the world, by so much the more shall ye for
+ever go thankless! Would that I could clear out Hampton Court and
+the Tuileries for ye! But gulp down your tears and hie aloft to the
+royal-mast with your hearts; for your friends who have gone before
+are clearing out the seven-storied heavens, and making refugees of
+long-pampered Gabriel, Michael, and Raphael, against your coming.
+Here ye strike but splintered hearts together--there, ye shall strike
+unsplinterable glasses!
+
+
+EXTRACTS.
+
+"And God created great whales." --GENESIS.
+
+"Leviathan maketh a path to shine after him; One would think the deep
+to be hoary." --JOB.
+
+"Now the Lord had prepared a great fish to swallow up Jonah."
+--JONAH.
+
+"There go the ships; there is that Leviathan whom thou hast made to
+play therein." --PSALMS.
+
+"In that day, the Lord with his sore, and great, and strong sword,
+shall punish Leviathan the piercing serpent, even Leviathan that
+crooked serpent; and he shall slay the dragon that is in the sea."
+--ISAIAH
+
+"And what thing soever besides cometh within the chaos of this
+monster's mouth, be it beast, boat, or stone, down it goes all
+incontinently that foul great swallow of his, and perisheth in the
+bottomless gulf of his paunch." --HOLLAND'S PLUTARCH'S MORALS.
+
+"The Indian Sea breedeth the most and the biggest fishes that are:
+among which the Whales and Whirlpooles called Balaene, take up as
+much in length as four acres or arpens of land." --HOLLAND'S PLINY.
+
+"Scarcely had we proceeded two days on the sea, when about sunrise a
+great many Whales and other monsters of the sea, appeared. Among the
+former, one was of a most monstrous size. ... This came towards us,
+open-mouthed, raising the waves on all sides, and beating the sea
+before him into a foam." --TOOKE'S LUCIAN. "THE TRUE HISTORY."
+
+"He visited this country also with a view of catching horse-whales,
+which had bones of very great value for their teeth, of which he
+brought some to the king. ... The best whales were catched in his
+own country, of which some were forty-eight, some fifty yards long.
+He said that he was one of six who had killed sixty in two days."
+--OTHER OR OCTHER'S VERBAL NARRATIVE TAKEN DOWN FROM HIS MOUTH BY
+KING ALFRED, A.D. 890.
+
+"And whereas all the other things, whether beast or vessel, that
+enter into the dreadful gulf of this monster's (whale's) mouth, are
+immediately lost and swallowed up, the sea-gudgeon retires into it in
+great security, and there sleeps." --MONTAIGNE. --APOLOGY FOR
+RAIMOND SEBOND.
+
+"Let us fly, let us fly! Old Nick take me if is not Leviathan
+described by the noble prophet Moses in the life of patient Job."
+--RABELAIS.
+
+"This whale's liver was two cartloads." --STOWE'S ANNALS.
+
+"The great Leviathan that maketh the seas to seethe like boiling
+pan." --LORD BACON'S VERSION OF THE PSALMS.
+
+"Touching that monstrous bulk of the whale or ork we have received
+nothing certain. They grow exceeding fat, insomuch that an
+incredible quantity of oil will be extracted out of one whale."
+--IBID. "HISTORY OF LIFE AND DEATH."
+
+"The sovereignest thing on earth is parmacetti for an inward bruise."
+--KING HENRY.
+
+"Very like a whale." --HAMLET.
+
+"Which to secure, no skill of leach's art
+Mote him availle, but to returne againe
+To his wound's worker, that with lowly dart,
+Dinting his breast, had bred his restless paine,
+Like as the wounded whale to shore flies thro' the maine."
+--THE FAERIE QUEEN.
+
+"Immense as whales, the motion of whose vast bodies can in a peaceful
+calm trouble the ocean til it boil." --SIR WILLIAM DAVENANT. PREFACE
+TO GONDIBERT.
+
+"What spermacetti is, men might justly doubt, since the learned
+Hosmannus in his work of thirty years, saith plainly, Nescio quid
+sit." --SIR T. BROWNE. OF SPERMA CETI AND THE SPERMA CETI WHALE.
+VIDE HIS V. E.
+
+"Like Spencer's Talus with his modern flail
+He threatens ruin with his ponderous tail.
+...
+Their fixed jav'lins in his side he wears,
+And on his back a grove of pikes appears." --WALLER'S BATTLE OF THE
+SUMMER ISLANDS.
+
+"By art is created that great Leviathan, called a Commonwealth or
+State--(in Latin, Civitas) which is but an artificial man." --OPENING
+SENTENCE OF HOBBES'S LEVIATHAN.
+
+"Silly Mansoul swallowed it without chewing, as if it had been a
+sprat in the mouth of a whale." --PILGRIM'S PROGRESS.
+
+"That sea beast
+Leviathan, which God of all his works
+Created hugest that swim the ocean stream." --PARADISE LOST.
+
+---"There Leviathan,
+Hugest of living creatures, in the deep
+Stretched like a promontory sleeps or swims,
+And seems a moving land; and at his gills
+Draws in, and at his breath spouts out a sea." --IBID.
+
+"The mighty whales which swim in a sea of water, and have a sea of
+oil swimming in them." --FULLLER'S PROFANE AND HOLY STATE.
+
+"So close behind some promontory lie
+The huge Leviathan to attend their prey,
+And give no chance, but swallow in the fry,
+Which through their gaping jaws mistake the way."
+--DRYDEN'S ANNUS MIRABILIS.
+
+"While the whale is floating at the stern of the ship, they cut off
+his head, and tow it with a boat as near the shore as it will come;
+but it will be aground in twelve or thirteen feet water." --THOMAS
+EDGE'S TEN VOYAGES TO SPITZBERGEN, IN PURCHAS.
+
+"In their way they saw many whales sporting in the ocean, and in
+wantonness fuzzing up the water through their pipes and vents, which
+nature has placed on their shoulders." --SIR T. HERBERT'S VOYAGES
+INTO ASIA AND AFRICA. HARRIS COLL.
+
+"Here they saw such huge troops of whales, that they were forced to
+proceed with a great deal of caution for fear they should run their
+ship upon them." --SCHOUTEN'S SIXTH CIRCUMNAVIGATION.
+
+"We set sail from the Elbe, wind N.E. in the ship called The
+Jonas-in-the-Whale. ... Some say the whale can't open his mouth, but
+that is a fable. ... They frequently climb up the masts to see
+whether they can see a whale, for the first discoverer has a ducat
+for his pains. ... I was told of a whale taken near Shetland, that
+had above a barrel of herrings in his belly. ... One of our
+harpooneers told me that he caught once a whale in Spitzbergen that
+was white all over." --A VOYAGE TO GREENLAND, A.D. 1671 HARRIS COLL.
+
+"Several whales have come in upon this coast (Fife) Anno 1652, one
+eighty feet in length of the whale-bone kind came in, which (as I was
+informed), besides a vast quantity of oil, did afford 500 weight of
+baleen. The jaws of it stand for a gate in the garden of Pitferren."
+--SIBBALD'S FIFE AND KINROSS.
+
+"Myself have agreed to try whether I can master and kill this
+Sperma-ceti whale, for I could never hear of any of that sort that
+was killed by any man, such is his fierceness and swiftness."
+--RICHARD STRAFFORD'S LETTER FROM THE BERMUDAS. PHIL. TRANS. A.D.
+1668.
+
+"Whales in the sea God's voice obey." --N. E. PRIMER.
+
+"We saw also abundance of large whales, there being more in those
+southern seas, as I may say, by a hundred to one; than we have to the
+northward of us." --CAPTAIN COWLEY'S VOYAGE ROUND THE GLOBE, A.D.
+1729.
+
+"... and the breath of the whale is frequendy attended with such an
+insupportable smell, as to bring on a disorder of the brain."
+--ULLOA'S SOUTH AMERICA.
+
+"To fifty chosen sylphs of special note,
+We trust the important charge, the petticoat.
+Oft have we known that seven-fold fence to fail,
+Tho' stuffed with hoops and armed with ribs of whale." --RAPE
+OF THE LOCK.
+
+"If we compare land animals in respect to magnitude, with those that
+take up their abode in the deep, we shall find they will appear
+contemptible in the comparison. The whale is doubtless the largest
+animal in creation." --GOLDSMITH, NAT. HIST.
+
+"If you should write a fable for little fishes, you would make them
+speak like great wales." --GOLDSMITH TO JOHNSON.
+
+"In the afternoon we saw what was supposed to be a rock, but it was
+found to be a dead whale, which some Asiatics had killed, and were
+then towing ashore. They seemed to endeavor to conceal themselves
+behind the whale, in order to avoid being seen by us." --COOK'S
+VOYAGES.
+
+"The larger whales, they seldom venture to attack. They stand in so
+great dread of some of them, that when out at sea they are afraid to
+mention even their names, and carry dung, lime-stone, juniper-wood,
+and some other articles of the same nature in their boats, in order
+to terrify and prevent their too near approach." --UNO VON TROIL'S
+LETTERS ON BANKS'S AND SOLANDER'S VOYAGE TO ICELAND IN 1772.
+
+"The Spermacetti Whale found by the Nantuckois, is an active, fierce
+animal, and requires vast address and boldness in the fishermen."
+--THOMAS JEFFERSON'S WHALE MEMORIAL TO THE FRENCH MINISTER IN 1778.
+
+"And pray, sir, what in the world is equal to it?" --EDMUND BURKE'S
+REFERENCE IN PARLIAMENT TO THE NANTUCKET WHALE-FISHERY.
+
+"Spain--a great whale stranded on the shores of Europe." --EDMUND
+BURKE. (SOMEWHERE.)
+
+"A tenth branch of the king's ordinary revenue, said to be grounded
+on the consideration of his guarding and protecting the seas from
+pirates and robbers, is the right to royal fish, which are whale and
+sturgeon. And these, when either thrown ashore or caught near the
+coast, are the property of the king." --BLACKSTONE.
+
+"Soon to the sport of death the crews repair:
+Rodmond unerring o'er his head suspends
+The barbed steel, and every turn attends."
+--FALCONER'S SHIPWRECK.
+
+"Bright shone the roofs, the domes, the spires,
+And rockets blew self driven,
+To hang their momentary fire
+Around the vault of heaven.
+
+"So fire with water to compare,
+The ocean serves on high,
+Up-spouted by a whale in air,
+To express unwieldy joy." --COWPER, ON THE QUEEN'S
+VISIT TO LONDON.
+
+"Ten or fifteen gallons of blood are thrown out of the heart at a
+stroke, with immense velocity." --JOHN HUNTER'S ACCOUNT OF THE
+DISSECTION OF A WHALE. (A SMALL SIZED ONE.)
+
+"The aorta of a whale is larger in the bore than the main pipe of the
+water-works at London Bridge, and the water roaring in its passage
+through that pipe is inferior in impetus and velocity to the blood
+gushing from the whale's heart." --PALEY'S THEOLOGY.
+
+"The whale is a mammiferous animal without hind feet." --BARON
+CUVIER.
+
+"In 40 degrees south, we saw Spermacetti Whales, but did not take any
+till the first of May, the sea being then covered with them."
+--COLNETT'S VOYAGE FOR THE PURPOSE OF EXTENDING THE SPERMACETI WHALE
+FISHERY.
+
+"In the free element beneath me swam,
+Floundered and dived, in play, in chace, in battle,
+Fishes of every colour, form, and kind;
+Which language cannot paint, and mariner
+Had never seen; from dread Leviathan
+To insect millions peopling every wave:
+Gather'd in shoals immense, like floating islands,
+Led by mysterious instincts through that waste
+And trackless region, though on every side
+Assaulted by voracious enemies,
+Whales, sharks, and monsters, arm'd in front or jaw,
+With swords, saws, spiral horns, or hooked fangs."
+--MONTGOMERY'S WORLD BEFORE THE FLOOD.
+
+"Io! Paean! Io! sing.
+To the finny people's king.
+Not a mightier whale than this
+In the vast Atlantic is;
+Not a fatter fish than he,
+Flounders round the Polar Sea." --CHARLES LAMB'S TRIUMPH OF THE
+WHALE.
+
+"In the year 1690 some persons were on a high hill observing the
+whales spouting and sporting with each other, when one observed:
+there--pointing to the sea--is a green pasture where our children's
+grand-children will go for bread." --OBED MACY'S HISTORY OF
+NANTUCKET.
+
+"I built a cottage for Susan and myself and made a gateway in the
+form of a Gothic Arch, by setting up a whale's jaw bones."
+--HAWTHORNE'S TWICE TOLD TALES.
+
+"She came to bespeak a monument for her first love, who had been
+killed by a whale in the Pacific ocean, no less than forty years
+ago." --IBID.
+
+"No, Sir, 'tis a Right Whale," answered Tom; "I saw his sprout; he
+threw up a pair of as pretty rainbows as a Christian would wish to
+look at. He's a raal oil-butt, that fellow!" --COOPER'S PILOT.
+
+"The papers were brought in, and we saw in the Berlin Gazette that
+whales had been introduced on the stage there." --ECKERMANN'S
+CONVERSATIONS WITH GOETHE.
+
+"My God! Mr. Chace, what is the matter?" I answered, "we have been
+stove by a whale." --"NARRATIVE OF THE SHIPWRECK OF THE WHALE SHIP
+ESSEX OF NANTUCKET, WHICH WAS ATTACKED AND FINALLY DESTROYED BY A
+LARGE SPERM WHALE IN THE PACIFIC OCEAN." BY OWEN CHACE OF NANTUCKET,
+FIRST MATE OF SAID VESSEL. NEW YORK, 1821.
+
+"A mariner sat in the shrouds one night,
+The wind was piping free;
+Now bright, now dimmed, was the moonlight pale,
+And the phospher gleamed in the wake of the whale,
+As it floundered in the sea." --ELIZABETH OAKES SMITH.
+
+"The quantity of line withdrawn from the boats engaged in the capture
+of this one whale, amounted altogether to 10,440 yards or nearly six
+English miles. ...
+
+"Sometimes the whale shakes its tremendous tail in the air, which,
+cracking like a whip, resounds to the distance of three or four
+miles." --SCORESBY.
+
+"Mad with the agonies he endures from these fresh attacks, the
+infuriated Sperm Whale rolls over and over; he rears his enormous
+head, and with wide expanded jaws snaps at everything around him; he
+rushes at the boats with his head; they are propelled before him with
+vast swiftness, and sometimes utterly destroyed. ... It is a matter
+of great astonishment that the consideration of the habits of so
+interesting, and, in a commercial point of view, so important an
+animal (as the Sperm Whale) should have been so entirely neglected,
+or should have excited so little curiosity among the numerous, and
+many of them competent observers, that of late years, must have
+possessed the most abundant and the most convenient opportunities of
+witnessing their habitudes." --THOMAS BEALE'S HISTORY OF THE SPERM
+WHALE, 1839.
+
+"The Cachalot" (Sperm Whale) "is not only better armed than the True
+Whale" (Greenland or Right Whale) "in possessing a formidable weapon
+at either extremity of its body, but also more frequently displays a
+disposition to employ these weapons offensively and in manner at once
+so artful, bold, and mischievous, as to lead to its being regarded as
+the most dangerous to attack of all the known species of the whale
+tribe." --FREDERICK DEBELL BENNETT'S WHALING VOYAGE ROUND THE GLOBE,
+1840.
+
+October 13. "There she blows," was sung out from the mast-head.
+"Where away?" demanded the captain.
+"Three points off the lee bow, sir."
+"Raise up your wheel. Steady!" "Steady, sir."
+"Mast-head ahoy! Do you see that whale now?"
+"Ay ay, sir! A shoal of Sperm Whales! There she blows! There she
+breaches!"
+"Sing out! sing out every time!"
+"Ay Ay, sir! There she blows! there--there--THAR she
+blows--bowes--bo-o-os!"
+"How far off?"
+"Two miles and a half."
+"Thunder and lightning! so near! Call all hands." --J. ROSS BROWNE'S
+ETCHINGS OF A WHALING CRUIZE. 1846.
+
+"The Whale-ship Globe, on board of which vessel occurred the horrid
+transactions we are about to relate, belonged to the island of
+Nantucket." --"NARRATIVE OF THE GLOBE," BY LAY AND HUSSEY SURVIVORS.
+A.D. 1828.
+
+Being once pursued by a whale which he had wounded, he parried the
+assault for some time with a lance; but the furious monster at length
+rushed on the boat; himself and comrades only being preserved by
+leaping into the water when they saw the onset was inevitable."
+--MISSIONARY JOURNAL OF TYERMAN AND BENNETT.
+
+"Nantucket itself," said Mr. Webster, "is a very striking and
+peculiar portion of the National interest. There is a population of
+eight or nine thousand persons living here in the sea, adding largely
+every year to the National wealth by the boldest and most persevering
+industry." --REPORT OF DANIEL WEBSTER'S SPEECH IN THE U. S. SENATE,
+ON THE APPLICATION FOR THE ERECTION OF A BREAKWATER AT NANTUCKET.
+1828.
+
+"The whale fell directly over him, and probably killed him in a
+moment." --"THE WHALE AND HIS CAPTORS, OR THE WHALEMAN'S ADVENTURES
+AND THE WHALE'S BIOGRAPHY, GATHERED ON THE HOMEWARD CRUISE OF THE
+COMMODORE PREBLE." BY REV. HENRY T. CHEEVER.
+
+"If you make the least damn bit of noise," replied Samuel, "I will
+send you to hell." --LIFE OF SAMUEL COMSTOCK (THE MUTINEER), BY HIS
+BROTHER, WILLIAM COMSTOCK. ANOTHER VERSION OF THE WHALE-SHIP GLOBE
+NARRATIVE.
+
+"The voyages of the Dutch and English to the Northern Ocean, in
+order, if possible, to discover a passage through it to India, though
+they failed of their main object, laid-open the haunts of the whale."
+--MCCULLOCH'S COMMERCIAL DICTIONARY.
+
+"These things are reciprocal; the ball rebounds, only to bound
+forward again; for now in laying open the haunts of the whale, the
+whalemen seem to have indirectly hit upon new clews to that same
+mystic North-West Passage." --FROM "SOMETHING" UNPUBLISHED.
+
+"It is impossible to meet a whale-ship on the ocean without being
+struck by her near appearance. The vessel under short sail, with
+look-outs at the mast-heads, eagerly scanning the wide expanse around
+them, has a totally different air from those engaged in regular
+voyage." --CURRENTS AND WHALING. U.S. EX. EX.
+
+"Pedestrians in the vicinity of London and elsewhere may recollect
+having seen large curved bones set upright in the earth, either to
+form arches over gateways, or entrances to alcoves, and they may
+perhaps have been told that these were the ribs of whales." --TALES
+OF A WHALE VOYAGER TO THE ARCTIC OCEAN.
+
+"It was not till the boats returned from the pursuit of these whales,
+that the whites saw their ship in bloody possession of the savages
+enrolled among the crew." --NEWSPAPER ACCOUNT OF THE TAKING AND
+RETAKING OF THE WHALE-SHIP HOBOMACK.
+
+"It is generally well known that out of the crews of Whaling vessels
+(American) few ever return in the ships on board of which they
+departed." --CRUISE IN A WHALE BOAT.
+
+"Suddenly a mighty mass emerged from the water, and shot up
+perpendicularly into the air. It was the while." --MIRIAM COFFIN OR
+THE WHALE FISHERMAN.
+
+"The Whale is harpooned to be sure; but bethink you, how you would
+manage a powerful unbroken colt, with the mere appliance of a rope
+tied to the root of his tail." --A CHAPTER ON WHALING IN RIBS AND
+TRUCKS.
+
+"On one occasion I saw two of these monsters (whales) probably male
+and female, slowly swimming, one after the other, within less than a
+stone's throw of the shore" (Terra Del Fuego), "over which the beech
+tree extended its branches." --DARWIN'S VOYAGE OF A NATURALIST.
+
+"'Stern all!' exclaimed the mate, as upon turning his head, he saw
+the distended jaws of a large Sperm Whale close to the head of the
+boat, threatening it with instant destruction;--'Stern all, for your
+lives!'" --WHARTON THE WHALE KILLER.
+
+"So be cheery, my lads, let your hearts never fail,
+While the bold harpooneer is striking the whale!" --NANTUCKET SONG.
+
+"Oh, the rare old Whale, mid storm and gale
+In his ocean home will be
+A giant in might, where might is right,
+And King of the boundless sea." --WHALE SONG.
+
+
+
+CHAPTER 1
+
+Loomings.
+
+
+Call me Ishmael. Some years ago--never mind how long
+precisely--having little or no money in my purse, and nothing
+particular to interest me on shore, I thought I would sail about a
+little and see the watery part of the world. It is a way I have of
+driving off the spleen and regulating the circulation. Whenever I
+find myself growing grim about the mouth; whenever it is a damp,
+drizzly November in my soul; whenever I find myself involuntarily
+pausing before coffin warehouses, and bringing up the rear of every
+funeral I meet; and especially whenever my hypos get such an upper
+hand of me, that it requires a strong moral principle to prevent me
+from deliberately stepping into the street, and methodically knocking
+people's hats off--then, I account it high time to get to sea as soon
+as I can. This is my substitute for pistol and ball. With a
+philosophical flourish Cato throws himself upon his sword; I quietly
+take to the ship. There is nothing surprising in this. If they but
+knew it, almost all men in their degree, some time or other, cherish
+very nearly the same feelings towards the ocean with me.
+
+There now is your insular city of the Manhattoes, belted round by
+wharves as Indian isles by coral reefs--commerce surrounds it with
+her surf. Right and left, the streets take you waterward. Its
+extreme downtown is the battery, where that noble mole is washed by
+waves, and cooled by breezes, which a few hours previous were out of
+sight of land. Look at the crowds of water-gazers there.
+
+Circumambulate the city of a dreamy Sabbath afternoon. Go from
+Corlears Hook to Coenties Slip, and from thence, by Whitehall,
+northward. What do you see?--Posted like silent sentinels all around
+the town, stand thousands upon thousands of mortal men fixed in ocean
+reveries. Some leaning against the spiles; some seated upon the
+pier-heads; some looking over the bulwarks of ships from China; some
+high aloft in the rigging, as if striving to get a still better
+seaward peep. But these are all landsmen; of week days pent up in
+lath and plaster--tied to counters, nailed to benches, clinched to
+desks. How then is this? Are the green fields gone? What do they
+here?
+
+But look! here come more crowds, pacing straight for the water, and
+seemingly bound for a dive. Strange! Nothing will content them but
+the extremest limit of the land; loitering under the shady lee of
+yonder warehouses will not suffice. No. They must get just as nigh
+the water as they possibly can without falling in. And there they
+stand--miles of them--leagues. Inlanders all, they come from lanes
+and alleys, streets and avenues--north, east, south, and west. Yet
+here they all unite. Tell me, does the magnetic virtue of the
+needles of the compasses of all those ships attract them thither?
+
+Once more. Say you are in the country; in some high land of lakes.
+Take almost any path you please, and ten to one it carries you down
+in a dale, and leaves you there by a pool in the stream. There is
+magic in it. Let the most absent-minded of men be plunged in his
+deepest reveries--stand that man on his legs, set his feet a-going,
+and he will infallibly lead you to water, if water there be in all
+that region. Should you ever be athirst in the great American
+desert, try this experiment, if your caravan happen to be supplied
+with a metaphysical professor. Yes, as every one knows, meditation
+and water are wedded for ever.
+
+But here is an artist. He desires to paint you the dreamiest,
+shadiest, quietest, most enchanting bit of romantic landscape in all
+the valley of the Saco. What is the chief element he employs? There
+stand his trees, each with a hollow trunk, as if a hermit and a
+crucifix were within; and here sleeps his meadow, and there sleep his
+cattle; and up from yonder cottage goes a sleepy smoke. Deep into
+distant woodlands winds a mazy way, reaching to overlapping spurs of
+mountains bathed in their hill-side blue. But though the picture
+lies thus tranced, and though this pine-tree shakes down its sighs
+like leaves upon this shepherd's head, yet all were vain, unless the
+shepherd's eye were fixed upon the magic stream before him. Go visit
+the Prairies in June, when for scores on scores of miles you wade
+knee-deep among Tiger-lilies--what is the one charm
+wanting?--Water--there is not a drop of water there! Were Niagara
+but a cataract of sand, would you travel your thousand miles to see
+it? Why did the poor poet of Tennessee, upon suddenly receiving two
+handfuls of silver, deliberate whether to buy him a coat, which he
+sadly needed, or invest his money in a pedestrian trip to Rockaway
+Beach? Why is almost every robust healthy boy with a robust healthy
+soul in him, at some time or other crazy to go to sea? Why upon your
+first voyage as a passenger, did you yourself feel such a mystical
+vibration, when first told that you and your ship were now out of
+sight of land? Why did the old Persians hold the sea holy? Why did
+the Greeks give it a separate deity, and own brother of Jove? Surely
+all this is not without meaning. And still deeper the meaning of
+that story of Narcissus, who because he could not grasp the
+tormenting, mild image he saw in the fountain, plunged into it and
+was drowned. But that same image, we ourselves see in all rivers and
+oceans. It is the image of the ungraspable phantom of life; and this
+is the key to it all.
+
+Now, when I say that I am in the habit of going to sea whenever I
+begin to grow hazy about the eyes, and begin to be over conscious of
+my lungs, I do not mean to have it inferred that I ever go to sea as
+a passenger. For to go as a passenger you must needs have a purse,
+and a purse is but a rag unless you have something in it. Besides,
+passengers get sea-sick--grow quarrelsome--don't sleep of nights--do
+not enjoy themselves much, as a general thing;--no, I never go as a
+passenger; nor, though I am something of a salt, do I ever go to sea
+as a Commodore, or a Captain, or a Cook. I abandon the glory and
+distinction of such offices to those who like them. For my part, I
+abominate all honourable respectable toils, trials, and tribulations
+of every kind whatsoever. It is quite as much as I can do to take
+care of myself, without taking care of ships, barques, brigs,
+schooners, and what not. And as for going as cook,--though I confess
+there is considerable glory in that, a cook being a sort of officer
+on ship-board--yet, somehow, I never fancied broiling fowls;--though
+once broiled, judiciously buttered, and judgmatically salted and
+peppered, there is no one who will speak more respectfully, not to
+say reverentially, of a broiled fowl than I will. It is out of the
+idolatrous dotings of the old Egyptians upon broiled ibis and roasted
+river horse, that you see the mummies of those creatures in their
+huge bake-houses the pyramids.
+
+No, when I go to sea, I go as a simple sailor, right before the mast,
+plumb down into the forecastle, aloft there to the royal mast-head.
+True, they rather order me about some, and make me jump from spar to
+spar, like a grasshopper in a May meadow. And at first, this sort of
+thing is unpleasant enough. It touches one's sense of honour,
+particularly if you come of an old established family in the land,
+the Van Rensselaers, or Randolphs, or Hardicanutes. And more than
+all, if just previous to putting your hand into the tar-pot, you have
+been lording it as a country schoolmaster, making the tallest boys
+stand in awe of you. The transition is a keen one, I assure you,
+from a schoolmaster to a sailor, and requires a strong decoction of
+Seneca and the Stoics to enable you to grin and bear it. But even
+this wears off in time.
+
+What of it, if some old hunks of a sea-captain orders me to get a
+broom and sweep down the decks? What does that indignity amount to,
+weighed, I mean, in the scales of the New Testament? Do you think
+the archangel Gabriel thinks anything the less of me, because I
+promptly and respectfully obey that old hunks in that particular
+instance? Who ain't a slave? Tell me that. Well, then, however the
+old sea-captains may order me about--however they may thump and punch
+me about, I have the satisfaction of knowing that it is all right;
+that everybody else is one way or other served in much the same
+way--either in a physical or metaphysical point of view, that is; and
+so the universal thump is passed round, and all hands should rub each
+other's shoulder-blades, and be content.
+
+Again, I always go to sea as a sailor, because they make a point of
+paying me for my trouble, whereas they never pay passengers a single
+penny that I ever heard of. On the contrary, passengers themselves
+must pay. And there is all the difference in the world between
+paying and being paid. The act of paying is perhaps the most
+uncomfortable infliction that the two orchard thieves entailed upon
+us. But BEING PAID,--what will compare with it? The urbane activity
+with which a man receives money is really marvellous, considering
+that we so earnestly believe money to be the root of all earthly
+ills, and that on no account can a monied man enter heaven. Ah! how
+cheerfully we consign ourselves to perdition!
+
+Finally, I always go to sea as a sailor, because of the wholesome
+exercise and pure air of the fore-castle deck. For as in this world,
+head winds are far more prevalent than winds from astern (that is, if
+you never violate the Pythagorean maxim), so for the most part the
+Commodore on the quarter-deck gets his atmosphere at second hand from
+the sailors on the forecastle. He thinks he breathes it first; but
+not so. In much the same way do the commonalty lead their leaders in
+many other things, at the same time that the leaders little suspect
+it. But wherefore it was that after having repeatedly smelt the sea
+as a merchant sailor, I should now take it into my head to go on a
+whaling voyage; this the invisible police officer of the Fates, who
+has the constant surveillance of me, and secretly dogs me, and
+influences me in some unaccountable way--he can better answer than
+any one else. And, doubtless, my going on this whaling voyage,
+formed part of the grand programme of Providence that was drawn up a
+long time ago. It came in as a sort of brief interlude and solo
+between more extensive performances. I take it that this part of the
+bill must have run something like this:
+
+
+"GRAND CONTESTED ELECTION FOR THE PRESIDENCY OF THE UNITED STATES.
+"WHALING VOYAGE BY ONE ISHMAEL.
+"BLOODY BATTLE IN AFFGHANISTAN."
+
+
+Though I cannot tell why it was exactly that those stage managers,
+the Fates, put me down for this shabby part of a whaling voyage, when
+others were set down for magnificent parts in high tragedies, and
+short and easy parts in genteel comedies, and jolly parts in
+farces--though I cannot tell why this was exactly; yet, now that I
+recall all the circumstances, I think I can see a little into the
+springs and motives which being cunningly presented to me under
+various disguises, induced me to set about performing the part I did,
+besides cajoling me into the delusion that it was a choice resulting
+from my own unbiased freewill and discriminating judgment.
+
+Chief among these motives was the overwhelming idea of the great
+whale himself. Such a portentous and mysterious monster roused all
+my curiosity. Then the wild and distant seas where he rolled his
+island bulk; the undeliverable, nameless perils of the whale; these,
+with all the attending marvels of a thousand Patagonian sights and
+sounds, helped to sway me to my wish. With other men, perhaps, such
+things would not have been inducements; but as for me, I am tormented
+with an everlasting itch for things remote. I love to sail forbidden
+seas, and land on barbarous coasts. Not ignoring what is good, I am
+quick to perceive a horror, and could still be social with it--would
+they let me--since it is but well to be on friendly terms with all
+the inmates of the place one lodges in.
+
+By reason of these things, then, the whaling voyage was welcome; the
+great flood-gates of the wonder-world swung open, and in the wild
+conceits that swayed me to my purpose, two and two there floated into
+my inmost soul, endless processions of the whale, and, mid most of
+them all, one grand hooded phantom, like a snow hill in the air.
+
+
+
+CHAPTER 2
+
+The Carpet-Bag.
+
+
+I stuffed a shirt or two into my old carpet-bag, tucked it under my
+arm, and started for Cape Horn and the Pacific. Quitting the good
+city of old Manhatto, I duly arrived in New Bedford. It was a
+Saturday night in December. Much was I disappointed upon learning
+that the little packet for Nantucket had already sailed, and that no
+way of reaching that place would offer, till the following Monday.
+
+As most young candidates for the pains and penalties of whaling stop
+at this same New Bedford, thence to embark on their voyage, it may as
+well be related that I, for one, had no idea of so doing. For my
+mind was made up to sail in no other than a Nantucket craft, because
+there was a fine, boisterous something about everything connected
+with that famous old island, which amazingly pleased me. Besides
+though New Bedford has of late been gradually monopolising the
+business of whaling, and though in this matter poor old Nantucket is
+now much behind her, yet Nantucket was her great original--the Tyre
+of this Carthage;--the place where the first dead American whale was
+stranded. Where else but from Nantucket did those aboriginal
+whalemen, the Red-Men, first sally out in canoes to give chase to the
+Leviathan? And where but from Nantucket, too, did that first
+adventurous little sloop put forth, partly laden with imported
+cobblestones--so goes the story--to throw at the whales, in order to
+discover when they were nigh enough to risk a harpoon from the
+bowsprit?
+
+Now having a night, a day, and still another night following before
+me in New Bedford, ere I could embark for my destined port, it
+became a matter of concernment where I was to eat and sleep
+meanwhile. It was a very dubious-looking, nay, a very dark and
+dismal night, bitingly cold and cheerless. I knew no one in the
+place. With anxious grapnels I had sounded my pocket, and only
+brought up a few pieces of silver,--So, wherever you go, Ishmael,
+said I to myself, as I stood in the middle of a dreary street
+shouldering my bag, and comparing the gloom towards the north with
+the darkness towards the south--wherever in your wisdom you may
+conclude to lodge for the night, my dear Ishmael, be sure to inquire
+the price, and don't be too particular.
+
+With halting steps I paced the streets, and passed the sign of "The
+Crossed Harpoons"--but it looked too expensive and jolly there.
+Further on, from the bright red windows of the "Sword-Fish Inn,"
+there came such fervent rays, that it seemed to have melted the
+packed snow and ice from before the house, for everywhere else the
+congealed frost lay ten inches thick in a hard, asphaltic
+pavement,--rather weary for me, when I struck my foot against the
+flinty projections, because from hard, remorseless service the soles
+of my boots were in a most miserable plight. Too expensive and
+jolly, again thought I, pausing one moment to watch the broad glare
+in the street, and hear the sounds of the tinkling glasses within.
+But go on, Ishmael, said I at last; don't you hear? get away from
+before the door; your patched boots are stopping the way. So on I
+went. I now by instinct followed the streets that took me waterward,
+for there, doubtless, were the cheapest, if not the cheeriest inns.
+
+Such dreary streets! blocks of blackness, not houses, on either
+hand, and here and there a candle, like a candle moving about in a
+tomb. At this hour of the night, of the last day of the week, that
+quarter of the town proved all but deserted. But presently I came to
+a smoky light proceeding from a low, wide building, the door of which
+stood invitingly open. It had a careless look, as if it were meant
+for the uses of the public; so, entering, the first thing I did was
+to stumble over an ash-box in the porch. Ha! thought I, ha, as the
+flying particles almost choked me, are these ashes from that
+destroyed city, Gomorrah? But "The Crossed Harpoons," and "The
+Sword-Fish?"--this, then must needs be the sign of "The Trap."
+However, I picked myself up and hearing a loud voice within, pushed
+on and opened a second, interior door.
+
+It seemed the great Black Parliament sitting in Tophet. A hundred
+black faces turned round in their rows to peer; and beyond, a black
+Angel of Doom was beating a book in a pulpit. It was a negro church;
+and the preacher's text was about the blackness of darkness, and the
+weeping and wailing and teeth-gnashing there. Ha, Ishmael, muttered
+I, backing out, Wretched entertainment at the sign of 'The Trap!'
+
+Moving on, I at last came to a dim sort of light not far from the
+docks, and heard a forlorn creaking in the air; and looking up, saw a
+swinging sign over the door with a white painting upon it, faintly
+representing a tall straight jet of misty spray, and these words
+underneath--"The Spouter Inn:--Peter Coffin."
+
+Coffin?--Spouter?--Rather ominous in that particular connexion,
+thought I. But it is a common name in Nantucket, they say, and I
+suppose this Peter here is an emigrant from there. As the light
+looked so dim, and the place, for the time, looked quiet enough, and
+the dilapidated little wooden house itself looked as if it might have
+been carted here from the ruins of some burnt district, and as the
+swinging sign had a poverty-stricken sort of creak to it, I thought
+that here was the very spot for cheap lodgings, and the best of pea
+coffee.
+
+It was a queer sort of place--a gable-ended old house, one side
+palsied as it were, and leaning over sadly. It stood on a sharp
+bleak corner, where that tempestuous wind Euroclydon kept up a worse
+howling than ever it did about poor Paul's tossed craft. Euroclydon,
+nevertheless, is a mighty pleasant zephyr to any one in-doors, with
+his feet on the hob quietly toasting for bed. "In judging of that
+tempestuous wind called Euroclydon," says an old writer--of whose
+works I possess the only copy extant--"it maketh a marvellous
+difference, whether thou lookest out at it from a glass window where
+the frost is all on the outside, or whether thou observest it from
+that sashless window, where the frost is on both sides, and of which
+the wight Death is the only glazier." True enough, thought I, as
+this passage occurred to my mind--old black-letter, thou reasonest
+well. Yes, these eyes are windows, and this body of mine is the
+house. What a pity they didn't stop up the chinks and the crannies
+though, and thrust in a little lint here and there. But it's too
+late to make any improvements now. The universe is finished; the
+copestone is on, and the chips were carted off a million years ago.
+Poor Lazarus there, chattering his teeth against the curbstone for
+his pillow, and shaking off his tatters with his shiverings, he might
+plug up both ears with rags, and put a corn-cob into his mouth, and
+yet that would not keep out the tempestuous Euroclydon. Euroclydon!
+says old Dives, in his red silken wrapper--(he had a redder one
+afterwards) pooh, pooh! What a fine frosty night; how Orion
+glitters; what northern lights! Let them talk of their oriental
+summer climes of everlasting conservatories; give me the privilege of
+making my own summer with my own coals.
+
+But what thinks Lazarus? Can he warm his blue hands by holding them
+up to the grand northern lights? Would not Lazarus rather be in
+Sumatra than here? Would he not far rather lay him down lengthwise
+along the line of the equator; yea, ye gods! go down to the fiery pit
+itself, in order to keep out this frost?
+
+Now, that Lazarus should lie stranded there on the curbstone before
+the door of Dives, this is more wonderful than that an iceberg should
+be moored to one of the Moluccas. Yet Dives himself, he too lives
+like a Czar in an ice palace made of frozen sighs, and being a
+president of a temperance society, he only drinks the tepid tears of
+orphans.
+
+But no more of this blubbering now, we are going a-whaling, and there
+is plenty of that yet to come. Let us scrape the ice from our
+frosted feet, and see what sort of a place this "Spouter" may be.
+
+
+
+CHAPTER 3
+
+The Spouter-Inn.
+
+
+Entering that gable-ended Spouter-Inn, you found yourself in a wide,
+low, straggling entry with old-fashioned wainscots, reminding one of
+the bulwarks of some condemned old craft. On one side hung a very
+large oilpainting so thoroughly besmoked, and every way defaced,
+that in the unequal crosslights by which you viewed it, it was only
+by diligent study and a series of systematic visits to it, and
+careful inquiry of the neighbors, that you could any way arrive at an
+understanding of its purpose. Such unaccountable masses of shades
+and shadows, that at first you almost thought some ambitious young
+artist, in the time of the New England hags, had endeavored to
+delineate chaos bewitched. But by dint of much and earnest
+contemplation, and oft repeated ponderings, and especially by
+throwing open the little window towards the back of the entry, you at
+last come to the conclusion that such an idea, however wild, might
+not be altogether unwarranted.
+
+But what most puzzled and confounded you was a long, limber,
+portentous, black mass of something hovering in the centre of the
+picture over three blue, dim, perpendicular lines floating in a
+nameless yeast. A boggy, soggy, squitchy picture truly, enough to
+drive a nervous man distracted. Yet was there a sort of indefinite,
+half-attained, unimaginable sublimity about it that fairly froze you
+to it, till you involuntarily took an oath with yourself to find out
+what that marvellous painting meant. Ever and anon a bright, but,
+alas, deceptive idea would dart you through.--It's the Black Sea in a
+midnight gale.--It's the unnatural combat of the four primal
+elements.--It's a blasted heath.--It's a Hyperborean winter
+scene.--It's the breaking-up of the icebound stream of Time. But at
+last all these fancies yielded to that one portentous something in
+the picture's midst. THAT once found out, and all the rest were
+plain. But stop; does it not bear a faint resemblance to a gigantic
+fish? even the great leviathan himself?
+
+In fact, the artist's design seemed this: a final theory of my own,
+partly based upon the aggregated opinions of many aged persons with
+whom I conversed upon the subject. The picture represents a
+Cape-Horner in a great hurricane; the half-foundered ship weltering
+there with its three dismantled masts alone visible; and an
+exasperated whale, purposing to spring clean over the craft, is in
+the enormous act of impaling himself upon the three mast-heads.
+
+The opposite wall of this entry was hung all over with a heathenish
+array of monstrous clubs and spears. Some were thickly set with
+glittering teeth resembling ivory saws; others were tufted with knots
+of human hair; and one was sickle-shaped, with a vast handle sweeping
+round like the segment made in the new-mown grass by a long-armed
+mower. You shuddered as you gazed, and wondered what monstrous
+cannibal and savage could ever have gone a death-harvesting with such
+a hacking, horrifying implement. Mixed with these were rusty old
+whaling lances and harpoons all broken and deformed. Some were
+storied weapons. With this once long lance, now wildly elbowed,
+fifty years ago did Nathan Swain kill fifteen whales between a
+sunrise and a sunset. And that harpoon--so like a corkscrew now--was
+flung in Javan seas, and run away with by a whale, years afterwards
+slain off the Cape of Blanco. The original iron entered nigh the
+tail, and, like a restless needle sojourning in the body of a man,
+travelled full forty feet, and at last was found imbedded in the
+hump.
+
+Crossing this dusky entry, and on through yon low-arched way--cut
+through what in old times must have been a great central chimney with
+fireplaces all round--you enter the public room. A still duskier
+place is this, with such low ponderous beams above, and such old
+wrinkled planks beneath, that you would almost fancy you trod some
+old craft's cockpits, especially of such a howling night, when this
+corner-anchored old ark rocked so furiously. On one side stood a
+long, low, shelf-like table covered with cracked glass cases, filled
+with dusty rarities gathered from this wide world's remotest nooks.
+Projecting from the further angle of the room stands a dark-looking
+den--the bar--a rude attempt at a right whale's head. Be that how it
+may, there stands the vast arched bone of the whale's jaw, so wide, a
+coach might almost drive beneath it. Within are shabby shelves,
+ranged round with old decanters, bottles, flasks; and in those jaws
+of swift destruction, like another cursed Jonah (by which name indeed
+they called him), bustles a little withered old man, who, for their
+money, dearly sells the sailors deliriums and death.
+
+Abominable are the tumblers into which he pours his poison. Though
+true cylinders without--within, the villanous green goggling glasses
+deceitfully tapered downwards to a cheating bottom. Parallel
+meridians rudely pecked into the glass, surround these footpads'
+goblets. Fill to THIS mark, and your charge is but a penny; to THIS
+a penny more; and so on to the full glass--the Cape Horn measure,
+which you may gulp down for a shilling.
+
+Upon entering the place I found a number of young seamen gathered
+about a table, examining by a dim light divers specimens of
+SKRIMSHANDER. I sought the landlord, and telling him I desired to be
+accommodated with a room, received for answer that his house was
+full--not a bed unoccupied. "But avast," he added, tapping his
+forehead, "you haint no objections to sharing a harpooneer's blanket,
+have ye? I s'pose you are goin' a-whalin', so you'd better get used
+to that sort of thing."
+
+I told him that I never liked to sleep two in a bed; that if I should
+ever do so, it would depend upon who the harpooneer might be, and
+that if he (the landlord) really had no other place for me, and the
+harpooneer was not decidedly objectionable, why rather than wander
+further about a strange town on so bitter a night, I would put up
+with the half of any decent man's blanket.
+
+"I thought so. All right; take a seat. Supper?--you want supper?
+Supper'll be ready directly."
+
+I sat down on an old wooden settle, carved all over like a bench on
+the Battery. At one end a ruminating tar was still further adorning
+it with his jack-knife, stooping over and diligently working away at
+the space between his legs. He was trying his hand at a ship under
+full sail, but he didn't make much headway, I thought.
+
+At last some four or five of us were summoned to our meal in an
+adjoining room. It was cold as Iceland--no fire at all--the landlord
+said he couldn't afford it. Nothing but two dismal tallow candles,
+each in a winding sheet. We were fain to button up our monkey
+jackets, and hold to our lips cups of scalding tea with our half
+frozen fingers. But the fare was of the most substantial kind--not
+only meat and potatoes, but dumplings; good heavens! dumplings for
+supper! One young fellow in a green box coat, addressed himself to
+these dumplings in a most direful manner.
+
+"My boy," said the landlord, "you'll have the nightmare to a dead
+sartainty."
+
+"Landlord," I whispered, "that aint the harpooneer is it?"
+
+"Oh, no," said he, looking a sort of diabolically funny, "the
+harpooneer is a dark complexioned chap. He never eats dumplings, he
+don't--he eats nothing but steaks, and he likes 'em rare."
+
+"The devil he does," says I. "Where is that harpooneer? Is he
+here?"
+
+"He'll be here afore long," was the answer.
+
+I could not help it, but I began to feel suspicious of this "dark
+complexioned" harpooneer. At any rate, I made up my mind that if it
+so turned out that we should sleep together, he must undress and get
+into bed before I did.
+
+Supper over, the company went back to the bar-room, when, knowing not
+what else to do with myself, I resolved to spend the rest of the
+evening as a looker on.
+
+Presently a rioting noise was heard without. Starting up, the
+landlord cried, "That's the Grampus's crew. I seed her reported in
+the offing this morning; a three years' voyage, and a full ship.
+Hurrah, boys; now we'll have the latest news from the Feegees."
+
+A tramping of sea boots was heard in the entry; the door was flung
+open, and in rolled a wild set of mariners enough. Enveloped in
+their shaggy watch coats, and with their heads muffled in woollen
+comforters, all bedarned and ragged, and their beards stiff with
+icicles, they seemed an eruption of bears from Labrador. They had
+just landed from their boat, and this was the first house they
+entered. No wonder, then, that they made a straight wake for the
+whale's mouth--the bar--when the wrinkled little old Jonah, there
+officiating, soon poured them out brimmers all round. One complained
+of a bad cold in his head, upon which Jonah mixed him a pitch-like
+potion of gin and molasses, which he swore was a sovereign cure for
+all colds and catarrhs whatsoever, never mind of how long standing,
+or whether caught off the coast of Labrador, or on the weather side
+of an ice-island.
+
+The liquor soon mounted into their heads, as it generally does even
+with the arrantest topers newly landed from sea, and they began
+capering about most obstreperously.
+
+I observed, however, that one of them held somewhat aloof, and though
+he seemed desirous not to spoil the hilarity of his shipmates by his
+own sober face, yet upon the whole he refrained from making as much
+noise as the rest. This man interested me at once; and since the
+sea-gods had ordained that he should soon become my shipmate (though
+but a sleeping-partner one, so far as this narrative is concerned),
+I will here venture upon a little description of him. He stood full
+six feet in height, with noble shoulders, and a chest like a
+coffer-dam. I have seldom seen such brawn in a man. His face was
+deeply brown and burnt, making his white teeth dazzling by the
+contrast; while in the deep shadows of his eyes floated some
+reminiscences that did not seem to give him much joy. His voice at
+once announced that he was a Southerner, and from his fine stature, I
+thought he must be one of those tall mountaineers from the
+Alleghanian Ridge in Virginia. When the revelry of his companions
+had mounted to its height, this man slipped away unobserved, and I
+saw no more of him till he became my comrade on the sea. In a few
+minutes, however, he was missed by his shipmates, and being, it
+seems, for some reason a huge favourite with them, they raised a cry
+of "Bulkington! Bulkington! where's Bulkington?" and darted out of
+the house in pursuit of him.
+
+It was now about nine o'clock, and the room seeming almost
+supernaturally quiet after these orgies, I began to congratulate
+myself upon a little plan that had occurred to me just previous to
+the entrance of the seamen.
+
+No man prefers to sleep two in a bed. In fact, you would a good deal
+rather not sleep with your own brother. I don't know how it is, but
+people like to be private when they are sleeping. And when it comes
+to sleeping with an unknown stranger, in a strange inn, in a strange
+town, and that stranger a harpooneer, then your objections
+indefinitely multiply. Nor was there any earthly reason why I as a
+sailor should sleep two in a bed, more than anybody else; for sailors
+no more sleep two in a bed at sea, than bachelor Kings do ashore. To
+be sure they all sleep together in one apartment, but you have your
+own hammock, and cover yourself with your own blanket, and sleep in
+your own skin.
+
+The more I pondered over this harpooneer, the more I abominated the
+thought of sleeping with him. It was fair to presume that being a
+harpooneer, his linen or woollen, as the case might be, would not be
+of the tidiest, certainly none of the finest. I began to twitch all
+over. Besides, it was getting late, and my decent harpooneer ought
+to be home and going bedwards. Suppose now, he should tumble in upon
+me at midnight--how could I tell from what vile hole he had been
+coming?
+
+"Landlord! I've changed my mind about that harpooneer.--I shan't
+sleep with him. I'll try the bench here."
+
+"Just as you please; I'm sorry I cant spare ye a tablecloth for a
+mattress, and it's a plaguy rough board here"--feeling of the knots
+and notches. "But wait a bit, Skrimshander; I've got a carpenter's
+plane there in the bar--wait, I say, and I'll make ye snug enough."
+So saying he procured the plane; and with his old silk handkerchief
+first dusting the bench, vigorously set to planing away at my bed,
+the while grinning like an ape. The shavings flew right and left;
+till at last the plane-iron came bump against an indestructible knot.
+The landlord was near spraining his wrist, and I told him for
+heaven's sake to quit--the bed was soft enough to suit me, and I did
+not know how all the planing in the world could make eider down of a
+pine plank. So gathering up the shavings with another grin, and
+throwing them into the great stove in the middle of the room, he went
+about his business, and left me in a brown study.
+
+I now took the measure of the bench, and found that it was a foot too
+short; but that could be mended with a chair. But it was a foot too
+narrow, and the other bench in the room was about four inches higher
+than the planed one--so there was no yoking them. I then placed the
+first bench lengthwise along the only clear space against the wall,
+leaving a little interval between, for my back to settle down in.
+But I soon found that there came such a draught of cold air over me
+from under the sill of the window, that this plan would never do at
+all, especially as another current from the rickety door met the one
+from the window, and both together formed a series of small
+whirlwinds in the immediate vicinity of the spot where I had thought
+to spend the night.
+
+The devil fetch that harpooneer, thought I, but stop, couldn't I
+steal a march on him--bolt his door inside, and jump into his bed,
+not to be wakened by the most violent knockings? It seemed no bad
+idea; but upon second thoughts I dismissed it. For who could tell
+but what the next morning, so soon as I popped out of the room, the
+harpooneer might be standing in the entry, all ready to knock me
+down!
+
+Still, looking round me again, and seeing no possible chance of
+spending a sufferable night unless in some other person's bed, I
+began to think that after all I might be cherishing unwarrantable
+prejudices against this unknown harpooneer. Thinks I, I'll wait
+awhile; he must be dropping in before long. I'll have a good look at
+him then, and perhaps we may become jolly good bedfellows after
+all--there's no telling.
+
+But though the other boarders kept coming in by ones, twos, and
+threes, and going to bed, yet no sign of my harpooneer.
+
+"Landlord! said I, "what sort of a chap is he--does he always keep
+such late hours?" It was now hard upon twelve o'clock.
+
+The landlord chuckled again with his lean chuckle, and seemed to be
+mightily tickled at something beyond my comprehension. "No," he
+answered, "generally he's an early bird--airley to bed and airley to
+rise--yes, he's the bird what catches the worm. But to-night he
+went out a peddling, you see, and I don't see what on airth keeps him
+so late, unless, may be, he can't sell his head."
+
+"Can't sell his head?--What sort of a bamboozingly story is this you
+are telling me?" getting into a towering rage. "Do you pretend to
+say, landlord, that this harpooneer is actually engaged this blessed
+Saturday night, or rather Sunday morning, in peddling his head around
+this town?"
+
+"That's precisely it," said the landlord, "and I told him he couldn't
+sell it here, the market's overstocked."
+
+"With what?" shouted I.
+
+"With heads to be sure; ain't there too many heads in the world?"
+
+"I tell you what it is, landlord," said I quite calmly, "you'd better
+stop spinning that yarn to me--I'm not green."
+
+"May be not," taking out a stick and whittling a toothpick, "but I
+rayther guess you'll be done BROWN if that ere harpooneer hears you a
+slanderin' his head."
+
+"I'll break it for him," said I, now flying into a passion again at
+this unaccountable farrago of the landlord's.
+
+"It's broke a'ready," said he.
+
+"Broke," said I--"BROKE, do you mean?"
+
+"Sartain, and that's the very reason he can't sell it, I guess."
+
+"Landlord," said I, going up to him as cool as Mt. Hecla in a
+snow-storm--"landlord, stop whittling. You and I must understand one
+another, and that too without delay. I come to your house and want a
+bed; you tell me you can only give me half a one; that the other half
+belongs to a certain harpooneer. And about this harpooneer, whom I
+have not yet seen, you persist in telling me the most mystifying and
+exasperating stories tending to beget in me an uncomfortable feeling
+towards the man whom you design for my bedfellow--a sort of
+connexion, landlord, which is an intimate and confidential one in the
+highest degree. I now demand of you to speak out and tell me who and
+what this harpooneer is, and whether I shall be in all respects safe
+to spend the night with him. And in the first place, you will be so
+good as to unsay that story about selling his head, which if true I
+take to be good evidence that this harpooneer is stark mad, and I've
+no idea of sleeping with a madman; and you, sir, YOU I mean,
+landlord, YOU, sir, by trying to induce me to do so knowingly, would
+thereby render yourself liable to a criminal prosecution."
+
+"Wall," said the landlord, fetching a long breath, "that's a purty
+long sarmon for a chap that rips a little now and then. But be easy,
+be easy, this here harpooneer I have been tellin' you of has just
+arrived from the south seas, where he bought up a lot of 'balmed New
+Zealand heads (great curios, you know), and he's sold all on 'em but
+one, and that one he's trying to sell to-night, cause to-morrow's
+Sunday, and it would not do to be sellin' human heads about the
+streets when folks is goin' to churches. He wanted to, last Sunday,
+but I stopped him just as he was goin' out of the door with four
+heads strung on a string, for all the airth like a string of inions."
+
+This account cleared up the otherwise unaccountable mystery, and
+showed that the landlord, after all, had had no idea of fooling
+me--but at the same time what could I think of a harpooneer who
+stayed out of a Saturday night clean into the holy Sabbath, engaged
+in such a cannibal business as selling the heads of dead idolators?
+
+"Depend upon it, landlord, that harpooneer is a dangerous man."
+
+"He pays reg'lar," was the rejoinder. "But come, it's getting
+dreadful late, you had better be turning flukes--it's a nice bed;
+Sal and me slept in that ere bed the night we were spliced. There's
+plenty of room for two to kick about in that bed; it's an almighty
+big bed that. Why, afore we give it up, Sal used to put our Sam and
+little Johnny in the foot of it. But I got a dreaming and sprawling
+about one night, and somehow, Sam got pitched on the floor, and came
+near breaking his arm. Arter that, Sal said it wouldn't do. Come
+along here, I'll give ye a glim in a jiffy;" and so saying he lighted
+a candle and held it towards me, offering to lead the way. But I
+stood irresolute; when looking at a clock in the corner, he exclaimed
+"I vum it's Sunday--you won't see that harpooneer to-night; he's come
+to anchor somewhere--come along then; DO come; WON'T ye come?"
+
+I considered the matter a moment, and then up stairs we went, and I
+was ushered into a small room, cold as a clam, and furnished, sure
+enough, with a prodigious bed, almost big enough indeed for any four
+harpooneers to sleep abreast.
+
+"There," said the landlord, placing the candle on a crazy old sea
+chest that did double duty as a wash-stand and centre table; "there,
+make yourself comfortable now, and good night to ye." I turned
+round from eyeing the bed, but he had disappeared.
+
+Folding back the counterpane, I stooped over the bed. Though none of
+the most elegant, it yet stood the scrutiny tolerably well. I then
+glanced round the room; and besides the bedstead and centre table,
+could see no other furniture belonging to the place, but a rude
+shelf, the four walls, and a papered fireboard representing a man
+striking a whale. Of things not properly belonging to the room,
+there was a hammock lashed up, and thrown upon the floor in one
+corner; also a large seaman's bag, containing the harpooneer's
+wardrobe, no doubt in lieu of a land trunk. Likewise, there was a
+parcel of outlandish bone fish hooks on the shelf over the
+fire-place, and a tall harpoon standing at the head of the bed.
+
+But what is this on the chest? I took it up, and held it close to
+the light, and felt it, and smelt it, and tried every way possible to
+arrive at some satisfactory conclusion concerning it. I can compare
+it to nothing but a large door mat, ornamented at the edges with
+little tinkling tags something like the stained porcupine quills
+round an Indian moccasin. There was a hole or slit in the middle of
+this mat, as you see the same in South American ponchos. But could
+it be possible that any sober harpooneer would get into a door mat,
+and parade the streets of any Christian town in that sort of guise?
+I put it on, to try it, and it weighed me down like a hamper, being
+uncommonly shaggy and thick, and I thought a little damp, as though
+this mysterious harpooneer had been wearing it of a rainy day. I
+went up in it to a bit of glass stuck against the wall, and I never
+saw such a sight in my life. I tore myself out of it in such a hurry
+that I gave myself a kink in the neck.
+
+I sat down on the side of the bed, and commenced thinking about this
+head-peddling harpooneer, and his door mat. After thinking some time
+on the bed-side, I got up and took off my monkey jacket, and then
+stood in the middle of the room thinking. I then took off my coat,
+and thought a little more in my shirt sleeves. But beginning to feel
+very cold now, half undressed as I was, and remembering what the
+landlord said about the harpooneer's not coming home at all that
+night, it being so very late, I made no more ado, but jumped out of
+my pantaloons and boots, and then blowing out the light tumbled into
+bed, and commended myself to the care of heaven.
+
+Whether that mattress was stuffed with corn-cobs or broken crockery,
+there is no telling, but I rolled about a good deal, and could not
+sleep for a long time. At last I slid off into a light doze, and had
+pretty nearly made a good offing towards the land of Nod, when I
+heard a heavy footfall in the passage, and saw a glimmer of light
+come into the room from under the door.
+
+Lord save me, thinks I, that must be the harpooneer, the infernal
+head-peddler. But I lay perfectly still, and resolved not to say a
+word till spoken to. Holding a light in one hand, and that identical
+New Zealand head in the other, the stranger entered the room, and
+without looking towards the bed, placed his candle a good way off
+from me on the floor in one corner, and then began working away at
+the knotted cords of the large bag I before spoke of as being in the
+room. I was all eagerness to see his face, but he kept it averted
+for some time while employed in unlacing the bag's mouth. This
+accomplished, however, he turned round--when, good heavens! what a
+sight! Such a face! It was of a dark, purplish, yellow colour, here
+and there stuck over with large blackish looking squares. Yes, it's
+just as I thought, he's a terrible bedfellow; he's been in a fight,
+got dreadfully cut, and here he is, just from the surgeon. But at
+that moment he chanced to turn his face so towards the light, that I
+plainly saw they could not be sticking-plasters at all, those black
+squares on his cheeks. They were stains of some sort or other. At
+first I knew not what to make of this; but soon an inkling of the
+truth occurred to me. I remembered a story of a white man--a
+whaleman too--who, falling among the cannibals, had been tattooed by
+them. I concluded that this harpooneer, in the course of his distant
+voyages, must have met with a similar adventure. And what is it,
+thought I, after all! It's only his outside; a man can be honest in
+any sort of skin. But then, what to make of his unearthly
+complexion, that part of it, I mean, lying round about, and
+completely independent of the squares of tattooing. To be sure, it
+might be nothing but a good coat of tropical tanning; but I never
+heard of a hot sun's tanning a white man into a purplish yellow one.
+However, I had never been in the South Seas; and perhaps the sun
+there produced these extraordinary effects upon the skin. Now, while
+all these ideas were passing through me like lightning, this
+harpooneer never noticed me at all. But, after some difficulty
+having opened his bag, he commenced fumbling in it, and presently
+pulled out a sort of tomahawk, and a seal-skin wallet with the hair
+on. Placing these on the old chest in the middle of the room, he
+then took the New Zealand head--a ghastly thing enough--and crammed
+it down into the bag. He now took off his hat--a new beaver
+hat--when I came nigh singing out with fresh surprise. There was no
+hair on his head--none to speak of at least--nothing but a small
+scalp-knot twisted up on his forehead. His bald purplish head now
+looked for all the world like a mildewed skull. Had not the stranger
+stood between me and the door, I would have bolted out of it quicker
+than ever I bolted a dinner.
+
+Even as it was, I thought something of slipping out of the window,
+but it was the second floor back. I am no coward, but what to make
+of this head-peddling purple rascal altogether passed my
+comprehension. Ignorance is the parent of fear, and being completely
+nonplussed and confounded about the stranger, I confess I was now as
+much afraid of him as if it was the devil himself who had thus broken
+into my room at the dead of night. In fact, I was so afraid of him
+that I was not game enough just then to address him, and demand a
+satisfactory answer concerning what seemed inexplicable in him.
+
+Meanwhile, he continued the business of undressing, and at last
+showed his chest and arms. As I live, these covered parts of him
+were checkered with the same squares as his face; his back, too, was
+all over the same dark squares; he seemed to have been in a Thirty
+Years' War, and just escaped from it with a sticking-plaster shirt.
+Still more, his very legs were marked, as if a parcel of dark green
+frogs were running up the trunks of young palms. It was now quite
+plain that he must be some abominable savage or other shipped aboard
+of a whaleman in the South Seas, and so landed in this Christian
+country. I quaked to think of it. A peddler of heads too--perhaps
+the heads of his own brothers. He might take a fancy to
+mine--heavens! look at that tomahawk!
+
+But there was no time for shuddering, for now the savage went about
+something that completely fascinated my attention, and convinced me
+that he must indeed be a heathen. Going to his heavy grego, or
+wrapall, or dreadnaught, which he had previously hung on a chair, he
+fumbled in the pockets, and produced at length a curious little
+deformed image with a hunch on its back, and exactly the colour of a
+three days' old Congo baby. Remembering the embalmed head, at first
+I almost thought that this black manikin was a real baby preserved
+in some similar manner. But seeing that it was not at all limber,
+and that it glistened a good deal like polished ebony, I concluded
+that it must be nothing but a wooden idol, which indeed it proved to
+be. For now the savage goes up to the empty fire-place, and removing
+the papered fire-board, sets up this little hunch-backed image, like
+a tenpin, between the andirons. The chimney jambs and all the bricks
+inside were very sooty, so that I thought this fire-place made a very
+appropriate little shrine or chapel for his Congo idol.
+
+I now screwed my eyes hard towards the half hidden image, feeling but
+ill at ease meantime--to see what was next to follow. First he takes
+about a double handful of shavings out of his grego pocket, and
+places them carefully before the idol; then laying a bit of ship
+biscuit on top and applying the flame from the lamp, he kindled the
+shavings into a sacrificial blaze. Presently, after many hasty
+snatches into the fire, and still hastier withdrawals of his fingers
+(whereby he seemed to be scorching them badly), he at last succeeded
+in drawing out the biscuit; then blowing off the heat and ashes a
+little, he made a polite offer of it to the little negro. But the
+little devil did not seem to fancy such dry sort of fare at all; he
+never moved his lips. All these strange antics were accompanied by
+still stranger guttural noises from the devotee, who seemed to be
+praying in a sing-song or else singing some pagan psalmody or other,
+during which his face twitched about in the most unnatural manner.
+At last extinguishing the fire, he took the idol up very
+unceremoniously, and bagged it again in his grego pocket as
+carelessly as if he were a sportsman bagging a dead woodcock.
+
+All these queer proceedings increased my uncomfortableness, and
+seeing him now exhibiting strong symptoms of concluding his business
+operations, and jumping into bed with me, I thought it was high time,
+now or never, before the light was put out, to break the spell in
+which I had so long been bound.
+
+But the interval I spent in deliberating what to say, was a fatal
+one. Taking up his tomahawk from the table, he examined the head of
+it for an instant, and then holding it to the light, with his mouth
+at the handle, he puffed out great clouds of tobacco smoke. The next
+moment the light was extinguished, and this wild cannibal, tomahawk
+between his teeth, sprang into bed with me. I sang out, I could not
+help it now; and giving a sudden grunt of astonishment he began
+feeling me.
+
+Stammering out something, I knew not what, I rolled away from him
+against the wall, and then conjured him, whoever or whatever he might
+be, to keep quiet, and let me get up and light the lamp again. But
+his guttural responses satisfied me at once that he but ill
+comprehended my meaning.
+
+"Who-e debel you?"--he at last said--"you no speak-e, dam-me, I
+kill-e." And so saying the lighted tomahawk began flourishing about
+me in the dark.
+
+"Landlord, for God's sake, Peter Coffin!" shouted I. "Landlord!
+Watch! Coffin! Angels! save me!"
+
+"Speak-e! tell-ee me who-ee be, or dam-me, I kill-e!" again growled
+the cannibal, while his horrid flourishings of the tomahawk scattered
+the hot tobacco ashes about me till I thought my linen would get on
+fire. But thank heaven, at that moment the landlord came into the
+room light in hand, and leaping from the bed I ran up to him.
+
+"Don't be afraid now," said he, grinning again, "Queequeg here
+wouldn't harm a hair of your head."
+
+"Stop your grinning," shouted I, "and why didn't you tell me that
+that infernal harpooneer was a cannibal?"
+
+"I thought ye know'd it;--didn't I tell ye, he was a peddlin' heads
+around town?--but turn flukes again and go to sleep. Queequeg, look
+here--you sabbee me, I sabbee--you this man sleepe you--you sabbee?"
+
+"Me sabbee plenty"--grunted Queequeg, puffing away at his pipe and
+sitting up in bed.
+
+"You gettee in," he added, motioning to me with his tomahawk, and
+throwing the clothes to one side. He really did this in not only a
+civil but a really kind and charitable way. I stood looking at him a
+moment. For all his tattooings he was on the whole a clean, comely
+looking cannibal. What's all this fuss I have been making about,
+thought I to myself--the man's a human being just as I am: he has
+just as much reason to fear me, as I have to be afraid of him.
+Better sleep with a sober cannibal than a drunken Christian.
+
+"Landlord," said I, "tell him to stash his tomahawk there, or pipe,
+or whatever you call it; tell him to stop smoking, in short, and I
+will turn in with him. But I don't fancy having a man smoking in bed
+with me. It's dangerous. Besides, I ain't insured."
+
+This being told to Queequeg, he at once complied, and again politely
+motioned me to get into bed--rolling over to one side as much as to
+say--I won't touch a leg of ye."
+
+"Good night, landlord," said I, "you may go."
+
+I turned in, and never slept better in my life.
+
+
+
+CHAPTER 4
+
+The Counterpane.
+
+
+Upon waking next morning about daylight, I found Queequeg's arm
+thrown over me in the most loving and affectionate manner. You had
+almost thought I had been his wife. The counterpane was of
+patchwork, full of odd little parti-coloured squares and triangles;
+and this arm of his tattooed all over with an interminable Cretan
+labyrinth of a figure, no two parts of which were of one precise
+shade--owing I suppose to his keeping his arm at sea unmethodically
+in sun and shade, his shirt sleeves irregularly rolled up at various
+times--this same arm of his, I say, looked for all the world like a
+strip of that same patchwork quilt. Indeed, partly lying on it as
+the arm did when I first awoke, I could hardly tell it from the
+quilt, they so blended their hues together; and it was only by the
+sense of weight and pressure that I could tell that Queequeg was
+hugging me.
+
+My sensations were strange. Let me try to explain them. When I was
+a child, I well remember a somewhat similar circumstance that befell
+me; whether it was a reality or a dream, I never could entirely
+settle. The circumstance was this. I had been cutting up some caper
+or other--I think it was trying to crawl up the chimney, as I had
+seen a little sweep do a few days previous; and my stepmother who,
+somehow or other, was all the time whipping me, or sending me to bed
+supperless,--my mother dragged me by the legs out of the chimney and
+packed me off to bed, though it was only two o'clock in the afternoon
+of the 21st June, the longest day in the year in our hemisphere. I
+felt dreadfully. But there was no help for it, so up stairs I went
+to my little room in the third floor, undressed myself as slowly as
+possible so as to kill time, and with a bitter sigh got between the
+sheets.
+
+I lay there dismally calculating that sixteen entire hours must
+elapse before I could hope for a resurrection. Sixteen hours in bed!
+the small of my back ached to think of it. And it was so light too;
+the sun shining in at the window, and a great rattling of coaches in
+the streets, and the sound of gay voices all over the house. I felt
+worse and worse--at last I got up, dressed, and softly going down in
+my stockinged feet, sought out my stepmother, and suddenly threw
+myself at her feet, beseeching her as a particular favour to give me a
+good slippering for my misbehaviour; anything indeed but condemning
+me to lie abed such an unendurable length of time. But she was the
+best and most conscientious of stepmothers, and back I had to go to
+my room. For several hours I lay there broad awake, feeling a great
+deal worse than I have ever done since, even from the greatest
+subsequent misfortunes. At last I must have fallen into a troubled
+nightmare of a doze; and slowly waking from it--half steeped in
+dreams--I opened my eyes, and the before sun-lit room was now wrapped
+in outer darkness. Instantly I felt a shock running through all my
+frame; nothing was to be seen, and nothing was to be heard; but a
+supernatural hand seemed placed in mine. My arm hung over the
+counterpane, and the nameless, unimaginable, silent form or phantom,
+to which the hand belonged, seemed closely seated by my bed-side.
+For what seemed ages piled on ages, I lay there, frozen with the most
+awful fears, not daring to drag away my hand; yet ever thinking that
+if I could but stir it one single inch, the horrid spell would be
+broken. I knew not how this consciousness at last glided away from
+me; but waking in the morning, I shudderingly remembered it all, and
+for days and weeks and months afterwards I lost myself in confounding
+attempts to explain the mystery. Nay, to this very hour, I often
+puzzle myself with it.
+
+Now, take away the awful fear, and my sensations at feeling the
+supernatural hand in mine were very similar, in their strangeness,
+to those which I experienced on waking up and seeing Queequeg's pagan
+arm thrown round me. But at length all the past night's events
+soberly recurred, one by one, in fixed reality, and then I lay only
+alive to the comical predicament. For though I tried to move his
+arm--unlock his bridegroom clasp--yet, sleeping as he was, he still
+hugged me tightly, as though naught but death should part us twain.
+I now strove to rouse him--"Queequeg!"--but his only answer was a
+snore. I then rolled over, my neck feeling as if it were in a
+horse-collar; and suddenly felt a slight scratch. Throwing aside the
+counterpane, there lay the tomahawk sleeping by the savage's side, as
+if it were a hatchet-faced baby. A pretty pickle, truly, thought I;
+abed here in a strange house in the broad day, with a cannibal and a
+tomahawk! "Queequeg!--in the name of goodness, Queequeg, wake!" At
+length, by dint of much wriggling, and loud and incessant
+expostulations upon the unbecomingness of his hugging a fellow male
+in that matrimonial sort of style, I succeeded in extracting a grunt;
+and presently, he drew back his arm, shook himself all over like a
+Newfoundland dog just from the water, and sat up in bed, stiff as a
+pike-staff, looking at me, and rubbing his eyes as if he did not
+altogether remember how I came to be there, though a dim
+consciousness of knowing something about me seemed slowly dawning
+over him. Meanwhile, I lay quietly eyeing him, having no serious
+misgivings now, and bent upon narrowly observing so curious a
+creature. When, at last, his mind seemed made up touching the
+character of his bedfellow, and he became, as it were, reconciled to
+the fact; he jumped out upon the floor, and by certain signs and
+sounds gave me to understand that, if it pleased me, he would dress
+first and then leave me to dress afterwards, leaving the whole
+apartment to myself. Thinks I, Queequeg, under the circumstances,
+this is a very civilized overture; but, the truth is, these savages
+have an innate sense of delicacy, say what you will; it is marvellous
+how essentially polite they are. I pay this particular compliment to
+Queequeg, because he treated me with so much civility and
+consideration, while I was guilty of great rudeness; staring at him
+from the bed, and watching all his toilette motions; for the time my
+curiosity getting the better of my breeding. Nevertheless, a man
+like Queequeg you don't see every day, he and his ways were well
+worth unusual regarding.
+
+He commenced dressing at top by donning his beaver hat, a very tall
+one, by the by, and then--still minus his trowsers--he hunted up his
+boots. What under the heavens he did it for, I cannot tell, but his
+next movement was to crush himself--boots in hand, and hat on--under
+the bed; when, from sundry violent gaspings and strainings, I
+inferred he was hard at work booting himself; though by no law of
+propriety that I ever heard of, is any man required to be private
+when putting on his boots. But Queequeg, do you see, was a creature
+in the transition stage--neither caterpillar nor butterfly. He was
+just enough civilized to show off his outlandishness in the strangest
+possible manners. His education was not yet completed. He was an
+undergraduate. If he had not been a small degree civilized, he very
+probably would not have troubled himself with boots at all; but then,
+if he had not been still a savage, he never would have dreamt of
+getting under the bed to put them on. At last, he emerged with his
+hat very much dented and crushed down over his eyes, and began
+creaking and limping about the room, as if, not being much accustomed
+to boots, his pair of damp, wrinkled cowhide ones--probably not made
+to order either--rather pinched and tormented him at the first go off
+of a bitter cold morning.
+
+Seeing, now, that there were no curtains to the window, and that the
+street being very narrow, the house opposite commanded a plain view
+into the room, and observing more and more the indecorous figure that
+Queequeg made, staving about with little else but his hat and boots
+on; I begged him as well as I could, to accelerate his toilet
+somewhat, and particularly to get into his pantaloons as soon as
+possible. He complied, and then proceeded to wash himself. At that
+time in the morning any Christian would have washed his face; but
+Queequeg, to my amazement, contented himself with restricting his
+ablutions to his chest, arms, and hands. He then donned his
+waistcoat, and taking up a piece of hard soap on the wash-stand
+centre table, dipped it into water and commenced lathering his face.
+I was watching to see where he kept his razor, when lo and behold, he
+takes the harpoon from the bed corner, slips out the long wooden
+stock, unsheathes the head, whets it a little on his boot, and
+striding up to the bit of mirror against the wall, begins a vigorous
+scraping, or rather harpooning of his cheeks. Thinks I, Queequeg,
+this is using Rogers's best cutlery with a vengeance. Afterwards I
+wondered the less at this operation when I came to know of what fine
+steel the head of a harpoon is made, and how exceedingly sharp the
+long straight edges are always kept.
+
+The rest of his toilet was soon achieved, and he proudly marched out
+of the room, wrapped up in his great pilot monkey jacket, and
+sporting his harpoon like a marshal's baton.
+
+
+
+CHAPTER 5
+
+Breakfast.
+
+
+I quickly followed suit, and descending into the bar-room accosted
+the grinning landlord very pleasantly. I cherished no malice towards
+him, though he had been skylarking with me not a little in the matter
+of my bedfellow.
+
+However, a good laugh is a mighty good thing, and rather too scarce a
+good thing; the more's the pity. So, if any one man, in his own
+proper person, afford stuff for a good joke to anybody, let him not
+be backward, but let him cheerfully allow himself to spend and be
+spent in that way. And the man that has anything bountifully
+laughable about him, be sure there is more in that man than you
+perhaps think for.
+
+The bar-room was now full of the boarders who had been dropping in
+the night previous, and whom I had not as yet had a good look at.
+They were nearly all whalemen; chief mates, and second mates, and
+third mates, and sea carpenters, and sea coopers, and sea
+blacksmiths, and harpooneers, and ship keepers; a brown and brawny
+company, with bosky beards; an unshorn, shaggy set, all wearing
+monkey jackets for morning gowns.
+
+You could pretty plainly tell how long each one had been ashore.
+This young fellow's healthy cheek is like a sun-toasted pear in hue,
+and would seem to smell almost as musky; he cannot have been three
+days landed from his Indian voyage. That man next him looks a few
+shades lighter; you might say a touch of satin wood is in him. In
+the complexion of a third still lingers a tropic tawn, but slightly
+bleached withal; HE doubtless has tarried whole weeks ashore. But
+who could show a cheek like Queequeg? which, barred with various
+tints, seemed like the Andes' western slope, to show forth in one
+array, contrasting climates, zone by zone.
+
+"Grub, ho!" now cried the landlord, flinging open a door, and in we
+went to breakfast.
+
+They say that men who have seen the world, thereby become quite at
+ease in manner, quite self-possessed in company. Not always, though:
+Ledyard, the great New England traveller, and Mungo Park, the Scotch
+one; of all men, they possessed the least assurance in the parlor.
+But perhaps the mere crossing of Siberia in a sledge drawn by dogs as
+Ledyard did, or the taking a long solitary walk on an empty stomach,
+in the negro heart of Africa, which was the sum of poor Mungo's
+performances--this kind of travel, I say, may not be the very best
+mode of attaining a high social polish. Still, for the most part,
+that sort of thing is to be had anywhere.
+
+These reflections just here are occasioned by the circumstance that
+after we were all seated at the table, and I was preparing to hear
+some good stories about whaling; to my no small surprise, nearly
+every man maintained a profound silence. And not only that, but they
+looked embarrassed. Yes, here were a set of sea-dogs, many of whom
+without the slightest bashfulness had boarded great whales on the
+high seas--entire strangers to them--and duelled them dead without
+winking; and yet, here they sat at a social breakfast table--all of
+the same calling, all of kindred tastes--looking round as sheepishly
+at each other as though they had never been out of sight of some
+sheepfold among the Green Mountains. A curious sight; these bashful
+bears, these timid warrior whalemen!
+
+But as for Queequeg--why, Queequeg sat there among them--at the head
+of the table, too, it so chanced; as cool as an icicle. To be sure I
+cannot say much for his breeding. His greatest admirer could not
+have cordially justified his bringing his harpoon into breakfast with
+him, and using it there without ceremony; reaching over the table
+with it, to the imminent jeopardy of many heads, and grappling the
+beefsteaks towards him. But THAT was certainly very coolly done by
+him, and every one knows that in most people's estimation, to do
+anything coolly is to do it genteelly.
+
+We will not speak of all Queequeg's peculiarities here; how he
+eschewed coffee and hot rolls, and applied his undivided attention to
+beefsteaks, done rare. Enough, that when breakfast was over he
+withdrew like the rest into the public room, lighted his
+tomahawk-pipe, and was sitting there quietly digesting and smoking
+with his inseparable hat on, when I sallied out for a stroll.
+
+
+
+CHAPTER 6
+
+The Street.
+
+
+If I had been astonished at first catching a glimpse of so outlandish
+an individual as Queequeg circulating among the polite society of a
+civilized town, that astonishment soon departed upon taking my first
+daylight stroll through the streets of New Bedford.
+
+In thoroughfares nigh the docks, any considerable seaport will
+frequently offer to view the queerest looking nondescripts from
+foreign parts. Even in Broadway and Chestnut streets, Mediterranean
+mariners will sometimes jostle the affrighted ladies. Regent Street
+is not unknown to Lascars and Malays; and at Bombay, in the Apollo
+Green, live Yankees have often scared the natives. But New Bedford
+beats all Water Street and Wapping. In these last-mentioned haunts
+you see only sailors; but in New Bedford, actual cannibals stand
+chatting at street corners; savages outright; many of whom yet carry
+on their bones unholy flesh. It makes a stranger stare.
+
+But, besides the Feegeeans, Tongatobooarrs, Erromanggoans,
+Pannangians, and Brighggians, and, besides the wild specimens of the
+whaling-craft which unheeded reel about the streets, you will see
+other sights still more curious, certainly more comical. There
+weekly arrive in this town scores of green Vermonters and New
+Hampshire men, all athirst for gain and glory in the fishery. They
+are mostly young, of stalwart frames; fellows who have felled
+forests, and now seek to drop the axe and snatch the whale-lance.
+Many are as green as the Green Mountains whence they came. In some
+things you would think them but a few hours old. Look there! that
+chap strutting round the corner. He wears a beaver hat and
+swallow-tailed coat, girdled with a sailor-belt and sheath-knife.
+Here comes another with a sou'-wester and a bombazine cloak.
+
+No town-bred dandy will compare with a country-bred one--I mean a
+downright bumpkin dandy--a fellow that, in the dog-days, will mow his
+two acres in buckskin gloves for fear of tanning his hands. Now when
+a country dandy like this takes it into his head to make a
+distinguished reputation, and joins the great whale-fishery, you
+should see the comical things he does upon reaching the seaport. In
+bespeaking his sea-outfit, he orders bell-buttons to his waistcoats;
+straps to his canvas trowsers. Ah, poor Hay-Seed! how bitterly will
+burst those straps in the first howling gale, when thou art driven,
+straps, buttons, and all, down the throat of the tempest.
+
+But think not that this famous town has only harpooneers, cannibals,
+and bumpkins to show her visitors. Not at all. Still New Bedford is
+a queer place. Had it not been for us whalemen, that tract of land
+would this day perhaps have been in as howling condition as the coast
+of Labrador. As it is, parts of her back country are enough to
+frighten one, they look so bony. The town itself is perhaps the
+dearest place to live in, in all New England. It is a land of oil,
+true enough: but not like Canaan; a land, also, of corn and wine.
+The streets do not run with milk; nor in the spring-time do they pave
+them with fresh eggs. Yet, in spite of this, nowhere in all America
+will you find more patrician-like houses; parks and gardens more
+opulent, than in New Bedford. Whence came they? how planted upon
+this once scraggy scoria of a country?
+
+Go and gaze upon the iron emblematical harpoons round yonder lofty
+mansion, and your question will be answered. Yes; all these brave
+houses and flowery gardens came from the Atlantic, Pacific, and
+Indian oceans. One and all, they were harpooned and dragged up
+hither from the bottom of the sea. Can Herr Alexander perform a feat
+like that?
+
+In New Bedford, fathers, they say, give whales for dowers to their
+daughters, and portion off their nieces with a few porpoises a-piece.
+You must go to New Bedford to see a brilliant wedding; for, they
+say, they have reservoirs of oil in every house, and every night
+recklessly burn their lengths in spermaceti candles.
+
+In summer time, the town is sweet to see; full of fine maples--long
+avenues of green and gold. And in August, high in air, the beautiful
+and bountiful horse-chestnuts, candelabra-wise, proffer the passer-by
+their tapering upright cones of congregated blossoms. So omnipotent
+is art; which in many a district of New Bedford has superinduced
+bright terraces of flowers upon the barren refuse rocks thrown aside
+at creation's final day.
+
+And the women of New Bedford, they bloom like their own red roses.
+But roses only bloom in summer; whereas the fine carnation of their
+cheeks is perennial as sunlight in the seventh heavens. Elsewhere
+match that bloom of theirs, ye cannot, save in Salem, where they tell
+me the young girls breathe such musk, their sailor sweethearts smell
+them miles off shore, as though they were drawing nigh the odorous
+Moluccas instead of the Puritanic sands.
+
+
+
+CHAPTER 7
+
+The Chapel.
+
+
+In this same New Bedford there stands a Whaleman's Chapel, and few
+are the moody fishermen, shortly bound for the Indian Ocean or
+Pacific, who fail to make a Sunday visit to the spot. I am sure that
+I did not.
+
+Returning from my first morning stroll, I again sallied out upon this
+special errand. The sky had changed from clear, sunny cold, to
+driving sleet and mist. Wrapping myself in my shaggy jacket of the
+cloth called bearskin, I fought my way against the stubborn storm.
+Entering, I found a small scattered congregation of sailors, and
+sailors' wives and widows. A muffled silence reigned, only broken at
+times by the shrieks of the storm. Each silent worshipper seemed
+purposely sitting apart from the other, as if each silent grief were
+insular and incommunicable. The chaplain had not yet arrived; and
+there these silent islands of men and women sat steadfastly eyeing
+several marble tablets, with black borders, masoned into the wall on
+either side the pulpit. Three of them ran something like the
+following, but I do not pretend to quote:--
+
+SACRED
+TO THE MEMORY
+OF
+JOHN TALBOT,
+Who, at the age of eighteen, was lost overboard,
+Near the Isle of Desolation, off Patagonia,
+November 1st, 1836.
+THIS TABLET
+Is erected to his Memory
+BY HIS
+SISTER.
+_____________
+
+SACRED
+TO THE MEMORY
+OF
+ROBERT LONG, WILLIS ELLERY,
+NATHAN COLEMAN, WALTER CANNY, SETH MACY,
+AND SAMUEL GLEIG,
+Forming one of the boats' crews
+OF
+THE SHIP ELIZA
+Who were towed out of sight by a Whale,
+On the Off-shore Ground in the
+PACIFIC,
+December 31st, 1839.
+THIS MARBLE
+Is here placed by their surviving
+SHIPMATES.
+_____________
+
+SACRED
+TO THE MEMORY
+OF
+The late
+CAPTAIN EZEKIEL HARDY,
+Who in the bows of his boat was killed by a
+Sperm Whale on the coast of Japan,
+AUGUST 3d, 1833.
+THIS TABLET
+Is erected to his Memory
+BY
+HIS WIDOW.
+
+Shaking off the sleet from my ice-glazed hat and jacket, I seated
+myself near the door, and turning sideways was surprised to see
+Queequeg near me. Affected by the solemnity of the scene, there was
+a wondering gaze of incredulous curiosity in his countenance. This
+savage was the only person present who seemed to notice my entrance;
+because he was the only one who could not read, and, therefore, was
+not reading those frigid inscriptions on the wall. Whether any of
+the relatives of the seamen whose names appeared there were now among
+the congregation, I knew not; but so many are the unrecorded
+accidents in the fishery, and so plainly did several women present
+wear the countenance if not the trappings of some unceasing grief,
+that I feel sure that here before me were assembled those, in whose
+unhealing hearts the sight of those bleak tablets sympathetically
+caused the old wounds to bleed afresh.
+
+Oh! ye whose dead lie buried beneath the green grass; who standing
+among flowers can say--here, HERE lies my beloved; ye know not the
+desolation that broods in bosoms like these. What bitter blanks in
+those black-bordered marbles which cover no ashes! What despair in
+those immovable inscriptions! What deadly voids and unbidden
+infidelities in the lines that seem to gnaw upon all Faith, and
+refuse resurrections to the beings who have placelessly perished
+without a grave. As well might those tablets stand in the cave of
+Elephanta as here.
+
+In what census of living creatures, the dead of mankind are included;
+why it is that a universal proverb says of them, that they tell no
+tales, though containing more secrets than the Goodwin Sands; how it
+is that to his name who yesterday departed for the other world, we
+prefix so significant and infidel a word, and yet do not thus entitle
+him, if he but embarks for the remotest Indies of this living earth;
+why the Life Insurance Companies pay death-forfeitures upon
+immortals; in what eternal, unstirring paralysis, and deadly,
+hopeless trance, yet lies antique Adam who died sixty round centuries
+ago; how it is that we still refuse to be comforted for those who we
+nevertheless maintain are dwelling in unspeakable bliss; why all the
+living so strive to hush all the dead; wherefore but the rumor of a
+knocking in a tomb will terrify a whole city. All these things are
+not without their meanings.
+
+But Faith, like a jackal, feeds among the tombs, and even from these
+dead doubts she gathers her most vital hope.
+
+It needs scarcely to be told, with what feelings, on the eve of a
+Nantucket voyage, I regarded those marble tablets, and by the murky
+light of that darkened, doleful day read the fate of the whalemen who
+had gone before me. Yes, Ishmael, the same fate may be thine. But
+somehow I grew merry again. Delightful inducements to embark, fine
+chance for promotion, it seems--aye, a stove boat will make me an
+immortal by brevet. Yes, there is death in this business of
+whaling--a speechlessly quick chaotic bundling of a man into
+Eternity. But what then? Methinks we have hugely mistaken this
+matter of Life and Death. Methinks that what they call my shadow
+here on earth is my true substance. Methinks that in looking at
+things spiritual, we are too much like oysters observing the sun
+through the water, and thinking that thick water the thinnest of air.
+Methinks my body is but the lees of my better being. In fact take
+my body who will, take it I say, it is not me. And therefore three
+cheers for Nantucket; and come a stove boat and stove body when they
+will, for stave my soul, Jove himself cannot.
+
+
+
+CHAPTER 8
+
+The Pulpit.
+
+
+I had not been seated very long ere a man of a certain venerable
+robustness entered; immediately as the storm-pelted door flew back
+upon admitting him, a quick regardful eyeing of him by all the
+congregation, sufficiently attested that this fine old man was the
+chaplain. Yes, it was the famous Father Mapple, so called by the
+whalemen, among whom he was a very great favourite. He had been a
+sailor and a harpooneer in his youth, but for many years past had
+dedicated his life to the ministry. At the time I now write of,
+Father Mapple was in the hardy winter of a healthy old age; that sort
+of old age which seems merging into a second flowering youth, for
+among all the fissures of his wrinkles, there shone certain mild
+gleams of a newly developing bloom--the spring verdure peeping forth
+even beneath February's snow. No one having previously heard his
+history, could for the first time behold Father Mapple without the
+utmost interest, because there were certain engrafted clerical
+peculiarities about him, imputable to that adventurous maritime life
+he had led. When he entered I observed that he carried no umbrella,
+and certainly had not come in his carriage, for his tarpaulin hat ran
+down with melting sleet, and his great pilot cloth jacket seemed
+almost to drag him to the floor with the weight of the water it had
+absorbed. However, hat and coat and overshoes were one by one
+removed, and hung up in a little space in an adjacent corner; when,
+arrayed in a decent suit, he quietly approached the pulpit.
+
+Like most old fashioned pulpits, it was a very lofty one, and since a
+regular stairs to such a height would, by its long angle with the
+floor, seriously contract the already small area of the chapel, the
+architect, it seemed, had acted upon the hint of Father Mapple, and
+finished the pulpit without a stairs, substituting a perpendicular
+side ladder, like those used in mounting a ship from a boat at sea.
+The wife of a whaling captain had provided the chapel with a handsome
+pair of red worsted man-ropes for this ladder, which, being itself
+nicely headed, and stained with a mahogany colour, the whole
+contrivance, considering what manner of chapel it was, seemed by no
+means in bad taste. Halting for an instant at the foot of the
+ladder, and with both hands grasping the ornamental knobs of the
+man-ropes, Father Mapple cast a look upwards, and then with a truly
+sailor-like but still reverential dexterity, hand over hand, mounted
+the steps as if ascending the main-top of his vessel.
+
+The perpendicular parts of this side ladder, as is usually the case
+with swinging ones, were of cloth-covered rope, only the rounds were
+of wood, so that at every step there was a joint. At my first
+glimpse of the pulpit, it had not escaped me that however convenient
+for a ship, these joints in the present instance seemed unnecessary.
+For I was not prepared to see Father Mapple after gaining the height,
+slowly turn round, and stooping over the pulpit, deliberately drag up
+the ladder step by step, till the whole was deposited within, leaving
+him impregnable in his little Quebec.
+
+I pondered some time without fully comprehending the reason for this.
+Father Mapple enjoyed such a wide reputation for sincerity and
+sanctity, that I could not suspect him of courting notoriety by any
+mere tricks of the stage. No, thought I, there must be some sober
+reason for this thing; furthermore, it must symbolize something
+unseen. Can it be, then, that by that act of physical isolation, he
+signifies his spiritual withdrawal for the time, from all outward
+worldly ties and connexions? Yes, for replenished with the meat and
+wine of the word, to the faithful man of God, this pulpit, I see, is
+a self-containing stronghold--a lofty Ehrenbreitstein, with a
+perennial well of water within the walls.
+
+But the side ladder was not the only strange feature of the place,
+borrowed from the chaplain's former sea-farings. Between the marble
+cenotaphs on either hand of the pulpit, the wall which formed its
+back was adorned with a large painting representing a gallant ship
+beating against a terrible storm off a lee coast of black rocks and
+snowy breakers. But high above the flying scud and dark-rolling
+clouds, there floated a little isle of sunlight, from which beamed
+forth an angel's face; and this bright face shed a distinct spot of
+radiance upon the ship's tossed deck, something like that silver
+plate now inserted into the Victory's plank where Nelson fell. "Ah,
+noble ship," the angel seemed to say, "beat on, beat on, thou noble
+ship, and bear a hardy helm; for lo! the sun is breaking through; the
+clouds are rolling off--serenest azure is at hand."
+
+Nor was the pulpit itself without a trace of the same sea-taste that
+had achieved the ladder and the picture. Its panelled front was in
+the likeness of a ship's bluff bows, and the Holy Bible rested on a
+projecting piece of scroll work, fashioned after a ship's
+fiddle-headed beak.
+
+What could be more full of meaning?--for the pulpit is ever this
+earth's foremost part; all the rest comes in its rear; the pulpit
+leads the world. From thence it is the storm of God's quick wrath is
+first descried, and the bow must bear the earliest brunt. From
+thence it is the God of breezes fair or foul is first invoked for
+favourable winds. Yes, the world's a ship on its passage out, and not
+a voyage complete; and the pulpit is its prow.
+
+
+
+CHAPTER 9
+
+The Sermon.
+
+
+Father Mapple rose, and in a mild voice of unassuming authority
+ordered the scattered people to condense. "Starboard gangway,
+there! side away to larboard--larboard gangway to starboard!
+Midships! midships!"
+
+There was a low rumbling of heavy sea-boots among the benches, and a
+still slighter shuffling of women's shoes, and all was quiet again,
+and every eye on the preacher.
+
+He paused a little; then kneeling in the pulpit's bows, folded his
+large brown hands across his chest, uplifted his closed eyes, and
+offered a prayer so deeply devout that he seemed kneeling and praying
+at the bottom of the sea.
+
+This ended, in prolonged solemn tones, like the continual tolling of
+a bell in a ship that is foundering at sea in a fog--in such tones he
+commenced reading the following hymn; but changing his manner towards
+the concluding stanzas, burst forth with a pealing exultation and
+joy--
+
+"The ribs and terrors in the whale,
+Arched over me a dismal gloom,
+While all God's sun-lit waves rolled by,
+And lift me deepening down to doom.
+
+"I saw the opening maw of hell,
+With endless pains and sorrows there;
+Which none but they that feel can tell--
+Oh, I was plunging to despair.
+
+"In black distress, I called my God,
+When I could scarce believe him mine,
+He bowed his ear to my complaints--
+No more the whale did me confine.
+
+"With speed he flew to my relief,
+As on a radiant dolphin borne;
+Awful, yet bright, as lightning shone
+The face of my Deliverer God.
+
+"My song for ever shall record
+That terrible, that joyful hour;
+I give the glory to my God,
+His all the mercy and the power.
+
+
+Nearly all joined in singing this hymn, which swelled high above the
+howling of the storm. A brief pause ensued; the preacher slowly
+turned over the leaves of the Bible, and at last, folding his hand
+down upon the proper page, said: "Beloved shipmates, clinch the last
+verse of the first chapter of Jonah--'And God had prepared a great
+fish to swallow up Jonah.'"
+
+"Shipmates, this book, containing only four chapters--four yarns--is
+one of the smallest strands in the mighty cable of the Scriptures.
+Yet what depths of the soul does Jonah's deep sealine sound! what a
+pregnant lesson to us is this prophet! What a noble thing is that
+canticle in the fish's belly! How billow-like and boisterously
+grand! We feel the floods surging over us; we sound with him to the
+kelpy bottom of the waters; sea-weed and all the slime of the sea is
+about us! But WHAT is this lesson that the book of Jonah teaches?
+Shipmates, it is a two-stranded lesson; a lesson to us all as sinful
+men, and a lesson to me as a pilot of the living God. As sinful men,
+it is a lesson to us all, because it is a story of the sin,
+hard-heartedness, suddenly awakened fears, the swift punishment,
+repentance, prayers, and finally the deliverance and joy of Jonah.
+As with all sinners among men, the sin of this son of Amittai was in
+his wilful disobedience of the command of God--never mind now what
+that command was, or how conveyed--which he found a hard command.
+But all the things that God would have us do are hard for us to
+do--remember that--and hence, he oftener commands us than endeavors
+to persuade. And if we obey God, we must disobey ourselves; and it
+is in this disobeying ourselves, wherein the hardness of obeying God
+consists.
+
+"With this sin of disobedience in him, Jonah still further flouts at
+God, by seeking to flee from Him. He thinks that a ship made by men
+will carry him into countries where God does not reign, but only the
+Captains of this earth. He skulks about the wharves of Joppa, and
+seeks a ship that's bound for Tarshish. There lurks, perhaps, a
+hitherto unheeded meaning here. By all accounts Tarshish could have
+been no other city than the modern Cadiz. That's the opinion of
+learned men. And where is Cadiz, shipmates? Cadiz is in Spain; as
+far by water, from Joppa, as Jonah could possibly have sailed in
+those ancient days, when the Atlantic was an almost unknown sea.
+Because Joppa, the modern Jaffa, shipmates, is on the most easterly
+coast of the Mediterranean, the Syrian; and Tarshish or Cadiz more
+than two thousand miles to the westward from that, just outside the
+Straits of Gibraltar. See ye not then, shipmates, that Jonah sought
+to flee world-wide from God? Miserable man! Oh! most contemptible
+and worthy of all scorn; with slouched hat and guilty eye, skulking
+from his God; prowling among the shipping like a vile burglar
+hastening to cross the seas. So disordered, self-condemning is his
+look, that had there been policemen in those days, Jonah, on the mere
+suspicion of something wrong, had been arrested ere he touched a
+deck. How plainly he's a fugitive! no baggage, not a hat-box,
+valise, or carpet-bag,--no friends accompany him to the wharf with
+their adieux. At last, after much dodging search, he finds the
+Tarshish ship receiving the last items of her cargo; and as he steps
+on board to see its Captain in the cabin, all the sailors for the
+moment desist from hoisting in the goods, to mark the stranger's evil
+eye. Jonah sees this; but in vain he tries to look all ease and
+confidence; in vain essays his wretched smile. Strong intuitions of
+the man assure the mariners he can be no innocent. In their gamesome
+but still serious way, one whispers to the other--"Jack, he's robbed
+a widow;" or, "Joe, do you mark him; he's a bigamist;" or, "Harry
+lad, I guess he's the adulterer that broke jail in old Gomorrah, or
+belike, one of the missing murderers from Sodom." Another runs to
+read the bill that's stuck against the spile upon the wharf to which
+the ship is moored, offering five hundred gold coins for the
+apprehension of a parricide, and containing a description of his
+person. He reads, and looks from Jonah to the bill; while all his
+sympathetic shipmates now crowd round Jonah, prepared to lay their
+hands upon him. Frighted Jonah trembles, and summoning all his
+boldness to his face, only looks so much the more a coward. He will
+not confess himself suspected; but that itself is strong suspicion.
+So he makes the best of it; and when the sailors find him not to be
+the man that is advertised, they let him pass, and he descends into
+the cabin.
+
+"'Who's there?' cries the Captain at his busy desk, hurriedly making
+out his papers for the Customs--'Who's there?' Oh! how that harmless
+question mangles Jonah! For the instant he almost turns to flee
+again. But he rallies. 'I seek a passage in this ship to Tarshish;
+how soon sail ye, sir?' Thus far the busy Captain had not looked up
+to Jonah, though the man now stands before him; but no sooner does he
+hear that hollow voice, than he darts a scrutinizing glance. 'We
+sail with the next coming tide,' at last he slowly answered, still
+intently eyeing him. 'No sooner, sir?'--'Soon enough for any honest
+man that goes a passenger.' Ha! Jonah, that's another stab. But he
+swiftly calls away the Captain from that scent. 'I'll sail with
+ye,'--he says,--'the passage money how much is that?--I'll pay now.'
+For it is particularly written, shipmates, as if it were a thing not
+to be overlooked in this history, 'that he paid the fare thereof' ere
+the craft did sail. And taken with the context, this is full of
+meaning.
+
+"Now Jonah's Captain, shipmates, was one whose discernment detects
+crime in any, but whose cupidity exposes it only in the penniless.
+In this world, shipmates, sin that pays its way can travel freely,
+and without a passport; whereas Virtue, if a pauper, is stopped at
+all frontiers. So Jonah's Captain prepares to test the length of
+Jonah's purse, ere he judge him openly. He charges him thrice the
+usual sum; and it's assented to. Then the Captain knows that Jonah
+is a fugitive; but at the same time resolves to help a flight that
+paves its rear with gold. Yet when Jonah fairly takes out his purse,
+prudent suspicions still molest the Captain. He rings every coin to
+find a counterfeit. Not a forger, any way, he mutters; and Jonah is
+put down for his passage. 'Point out my state-room, Sir,' says Jonah
+now, 'I'm travel-weary; I need sleep.' 'Thou lookest like it,' says
+the Captain, 'there's thy room.' Jonah enters, and would lock the
+door, but the lock contains no key. Hearing him foolishly fumbling
+there, the Captain laughs lowly to himself, and mutters something
+about the doors of convicts' cells being never allowed to be locked
+within. All dressed and dusty as he is, Jonah throws himself into
+his berth, and finds the little state-room ceiling almost resting on
+his forehead. The air is close, and Jonah gasps. Then, in that
+contracted hole, sunk, too, beneath the ship's water-line, Jonah
+feels the heralding presentiment of that stifling hour, when the
+whale shall hold him in the smallest of his bowels' wards.
+
+"Screwed at its axis against the side, a swinging lamp slightly
+oscillates in Jonah's room; and the ship, heeling over towards the
+wharf with the weight of the last bales received, the lamp, flame and
+all, though in slight motion, still maintains a permanent obliquity
+with reference to the room; though, in truth, infallibly straight
+itself, it but made obvious the false, lying levels among which it
+hung. The lamp alarms and frightens Jonah; as lying in his berth his
+tormented eyes roll round the place, and this thus far successful
+fugitive finds no refuge for his restless glance. But that
+contradiction in the lamp more and more appals him. The floor, the
+ceiling, and the side, are all awry. 'Oh! so my conscience hangs in
+me!' he groans, 'straight upwards, so it burns; but the chambers of
+my soul are all in crookedness!'
+
+"Like one who after a night of drunken revelry hies to his bed, still
+reeling, but with conscience yet pricking him, as the plungings of
+the Roman race-horse but so much the more strike his steel tags into
+him; as one who in that miserable plight still turns and turns in
+giddy anguish, praying God for annihilation until the fit be passed;
+and at last amid the whirl of woe he feels, a deep stupor steals over
+him, as over the man who bleeds to death, for conscience is the
+wound, and there's naught to staunch it; so, after sore wrestlings in
+his berth, Jonah's prodigy of ponderous misery drags him drowning
+down to sleep.
+
+"And now the time of tide has come; the ship casts off her cables;
+and from the deserted wharf the uncheered ship for Tarshish, all
+careening, glides to sea. That ship, my friends, was the first of
+recorded smugglers! the contraband was Jonah. But the sea rebels; he
+will not bear the wicked burden. A dreadful storm comes on, the
+ship is like to break. But now when the boatswain calls all hands to
+lighten her; when boxes, bales, and jars are clattering overboard;
+when the wind is shrieking, and the men are yelling, and every plank
+thunders with trampling feet right over Jonah's head; in all this
+raging tumult, Jonah sleeps his hideous sleep. He sees no black sky
+and raging sea, feels not the reeling timbers, and little hears he or
+heeds he the far rush of the mighty whale, which even now with open
+mouth is cleaving the seas after him. Aye, shipmates, Jonah was gone
+down into the sides of the ship--a berth in the cabin as I have taken
+it, and was fast asleep. But the frightened master comes to him, and
+shrieks in his dead ear, 'What meanest thou, O, sleeper! arise!'
+Startled from his lethargy by that direful cry, Jonah staggers to his
+feet, and stumbling to the deck, grasps a shroud, to look out upon
+the sea. But at that moment he is sprung upon by a panther billow
+leaping over the bulwarks. Wave after wave thus leaps into the ship,
+and finding no speedy vent runs roaring fore and aft, till the
+mariners come nigh to drowning while yet afloat. And ever, as the
+white moon shows her affrighted face from the steep gullies in the
+blackness overhead, aghast Jonah sees the rearing bowsprit pointing
+high upward, but soon beat downward again towards the tormented deep.
+
+"Terrors upon terrors run shouting through his soul. In all his
+cringing attitudes, the God-fugitive is now too plainly known. The
+sailors mark him; more and more certain grow their suspicions of him,
+and at last, fully to test the truth, by referring the whole matter
+to high Heaven, they fall to casting lots, to see for whose
+cause this great tempest was upon them. The lot is Jonah's; that
+discovered, then how furiously they mob him with their questions.
+'What is thine occupation? Whence comest thou? Thy country? What
+people? But mark now, my shipmates, the behavior of poor Jonah. The
+eager mariners but ask him who he is, and where from; whereas, they
+not only receive an answer to those questions, but likewise another
+answer to a question not put by them, but the unsolicited answer is
+forced from Jonah by the hard hand of God that is upon him.
+
+"'I am a Hebrew,' he cries--and then--'I fear the Lord the God of
+Heaven who hath made the sea and the dry land!' Fear him, O Jonah?
+Aye, well mightest thou fear the Lord God THEN! Straightway, he now
+goes on to make a full confession; whereupon the mariners became more
+and more appalled, but still are pitiful. For when Jonah, not yet
+supplicating God for mercy, since he but too well knew the darkness
+of his deserts,--when wretched Jonah cries out to them to take him
+and cast him forth into the sea, for he knew that for HIS sake this
+great tempest was upon them; they mercifully turn from him, and seek
+by other means to save the ship. But all in vain; the indignant gale
+howls louder; then, with one hand raised invokingly to God, with the
+other they not unreluctantly lay hold of Jonah.
+
+"And now behold Jonah taken up as an anchor and dropped into the sea;
+when instantly an oily calmness floats out from the east, and the sea
+is still, as Jonah carries down the gale with him, leaving smooth
+water behind. He goes down in the whirling heart of such a
+masterless commotion that he scarce heeds the moment when he drops
+seething into the yawning jaws awaiting him; and the whale shoots-to
+all his ivory teeth, like so many white bolts, upon his prison. Then
+Jonah prayed unto the Lord out of the fish's belly. But observe his
+prayer, and learn a weighty lesson. For sinful as he is, Jonah does
+not weep and wail for direct deliverance. He feels that his dreadful
+punishment is just. He leaves all his deliverance to God, contenting
+himself with this, that spite of all his pains and pangs, he will
+still look towards His holy temple. And here, shipmates, is true and
+faithful repentance; not clamorous for pardon, but grateful for
+punishment. And how pleasing to God was this conduct in Jonah, is
+shown in the eventual deliverance of him from the sea and the whale.
+Shipmates, I do not place Jonah before you to be copied for his sin
+but I do place him before you as a model for repentance. Sin not;
+but if you do, take heed to repent of it like Jonah."
+
+While he was speaking these words, the howling of the shrieking,
+slanting storm without seemed to add new power to the preacher, who,
+when describing Jonah's sea-storm, seemed tossed by a storm himself.
+His deep chest heaved as with a ground-swell; his tossed arms seemed
+the warring elements at work; and the thunders that rolled away from
+off his swarthy brow, and the light leaping from his eye, made all
+his simple hearers look on him with a quick fear that was strange to
+them.
+
+There now came a lull in his look, as he silently turned over the
+leaves of the Book once more; and, at last, standing motionless, with
+closed eyes, for the moment, seemed communing with God and himself.
+
+But again he leaned over towards the people, and bowing his head
+lowly, with an aspect of the deepest yet manliest humility, he spake
+these words:
+
+"Shipmates, God has laid but one hand upon you; both his hands press
+upon me. I have read ye by what murky light may be mine the lesson
+that Jonah teaches to all sinners; and therefore to ye, and still
+more to me, for I am a greater sinner than ye. And now how gladly
+would I come down from this mast-head and sit on the hatches there
+where you sit, and listen as you listen, while some one of you reads
+ME that other and more awful lesson which Jonah teaches to ME, as a
+pilot of the living God. How being an anointed pilot-prophet, or
+speaker of true things, and bidden by the Lord to sound those
+unwelcome truths in the ears of a wicked Nineveh, Jonah, appalled at
+the hostility he should raise, fled from his mission, and sought to
+escape his duty and his God by taking ship at Joppa. But God is
+everywhere; Tarshish he never reached. As we have seen, God came
+upon him in the whale, and swallowed him down to living gulfs of
+doom, and with swift slantings tore him along 'into the midst of the
+seas,' where the eddying depths sucked him ten thousand fathoms down,
+and 'the weeds were wrapped about his head,' and all the watery world
+of woe bowled over him. Yet even then beyond the reach of any
+plummet--'out of the belly of hell'--when the whale grounded upon the
+ocean's utmost bones, even then, God heard the engulphed, repenting
+prophet when he cried. Then God spake unto the fish; and from the
+shuddering cold and blackness of the sea, the whale came breeching up
+towards the warm and pleasant sun, and all the delights of air and
+earth; and 'vomited out Jonah upon the dry land;' when the word of
+the Lord came a second time; and Jonah, bruised and beaten--his ears,
+like two sea-shells, still multitudinously murmuring of the
+ocean--Jonah did the Almighty's bidding. And what was that,
+shipmates? To preach the Truth to the face of Falsehood! That was
+it!
+
+"This, shipmates, this is that other lesson; and woe to that pilot of
+the living God who slights it. Woe to him whom this world charms
+from Gospel duty! Woe to him who seeks to pour oil upon the waters
+when God has brewed them into a gale! Woe to him who seeks to please
+rather than to appal! Woe to him whose good name is more to him than
+goodness! Woe to him who, in this world, courts not dishonour! Woe
+to him who would not be true, even though to be false were salvation!
+Yea, woe to him who, as the great Pilot Paul has it, while preaching
+to others is himself a castaway!"
+
+He dropped and fell away from himself for a moment; then lifting his
+face to them again, showed a deep joy in his eyes, as he cried out
+with a heavenly enthusiasm,--"But oh! shipmates! on the starboard
+hand of every woe, there is a sure delight; and higher the top of
+that delight, than the bottom of the woe is deep. Is not the
+main-truck higher than the kelson is low? Delight is to him--a far,
+far upward, and inward delight--who against the proud gods and
+commodores of this earth, ever stands forth his own inexorable self.
+Delight is to him whose strong arms yet support him, when the ship of
+this base treacherous world has gone down beneath him. Delight is to
+him, who gives no quarter in the truth, and kills, burns, and
+destroys all sin though he pluck it out from under the robes of
+Senators and Judges. Delight,--top-gallant delight is to him, who
+acknowledges no law or lord, but the Lord his God, and is only a
+patriot to heaven. Delight is to him, whom all the waves of the
+billows of the seas of the boisterous mob can never shake from this
+sure Keel of the Ages. And eternal delight and deliciousness will be
+his, who coming to lay him down, can say with his final breath--O
+Father!--chiefly known to me by Thy rod--mortal or immortal, here I
+die. I have striven to be Thine, more than to be this world's, or
+mine own. Yet this is nothing: I leave eternity to Thee; for what
+is man that he should live out the lifetime of his God?"
+
+He said no more, but slowly waving a benediction, covered his face
+with his hands, and so remained kneeling, till all the people had
+departed, and he was left alone in the place.
+
+
+
+CHAPTER 10
+
+A Bosom Friend.
+
+
+Returning to the Spouter-Inn from the Chapel, I found Queequeg there
+quite alone; he having left the Chapel before the benediction some
+time. He was sitting on a bench before the fire, with his feet on
+the stove hearth, and in one hand was holding close up to his face
+that little negro idol of his; peering hard into its face, and with a
+jack-knife gently whittling away at its nose, meanwhile humming to
+himself in his heathenish way.
+
+But being now interrupted, he put up the image; and pretty soon,
+going to the table, took up a large book there, and placing it on his
+lap began counting the pages with deliberate regularity; at every
+fiftieth page--as I fancied--stopping a moment, looking vacantly
+around him, and giving utterance to a long-drawn gurgling whistle of
+astonishment. He would then begin again at the next fifty; seeming
+to commence at number one each time, as though he could not count
+more than fifty, and it was only by such a large number of fifties
+being found together, that his astonishment at the multitude of pages
+was excited.
+
+With much interest I sat watching him. Savage though he was, and
+hideously marred about the face--at least to my taste--his
+countenance yet had a something in it which was by no means
+disagreeable. You cannot hide the soul. Through all his unearthly
+tattooings, I thought I saw the traces of a simple honest heart; and
+in his large, deep eyes, fiery black and bold, there seemed tokens of
+a spirit that would dare a thousand devils. And besides all this,
+there was a certain lofty bearing about the Pagan, which even his
+uncouthness could not altogether maim. He looked like a man who had
+never cringed and never had had a creditor. Whether it was, too,
+that his head being shaved, his forehead was drawn out in freer and
+brighter relief, and looked more expansive than it otherwise would,
+this I will not venture to decide; but certain it was his head was
+phrenologically an excellent one. It may seem ridiculous, but it
+reminded me of General Washington's head, as seen in the popular
+busts of him. It had the same long regularly graded retreating slope
+from above the brows, which were likewise very projecting, like two
+long promontories thickly wooded on top. Queequeg was George
+Washington cannibalistically developed.
+
+Whilst I was thus closely scanning him, half-pretending meanwhile to
+be looking out at the storm from the casement, he never heeded my
+presence, never troubled himself with so much as a single glance; but
+appeared wholly occupied with counting the pages of the marvellous
+book. Considering how sociably we had been sleeping together the
+night previous, and especially considering the affectionate arm I had
+found thrown over me upon waking in the morning, I thought this
+indifference of his very strange. But savages are strange beings; at
+times you do not know exactly how to take them. At first they are
+overawing; their calm self-collectedness of simplicity seems a
+Socratic wisdom. I had noticed also that Queequeg never consorted at
+all, or but very little, with the other seamen in the inn. He made
+no advances whatever; appeared to have no desire to enlarge the
+circle of his acquaintances. All this struck me as mighty singular;
+yet, upon second thoughts, there was something almost sublime in it.
+Here was a man some twenty thousand miles from home, by the way of
+Cape Horn, that is--which was the only way he could get there--thrown
+among people as strange to him as though he were in the planet
+Jupiter; and yet he seemed entirely at his ease; preserving the
+utmost serenity; content with his own companionship; always equal to
+himself. Surely this was a touch of fine philosophy; though no doubt
+he had never heard there was such a thing as that. But, perhaps, to
+be true philosophers, we mortals should not be conscious of so living
+or so striving. So soon as I hear that such or such a man gives
+himself out for a philosopher, I conclude that, like the dyspeptic
+old woman, he must have "broken his digester."
+
+As I sat there in that now lonely room; the fire burning low, in that
+mild stage when, after its first intensity has warmed the air, it
+then only glows to be looked at; the evening shades and phantoms
+gathering round the casements, and peering in upon us silent,
+solitary twain; the storm booming without in solemn swells; I began
+to be sensible of strange feelings. I felt a melting in me. No more
+my splintered heart and maddened hand were turned against the wolfish
+world. This soothing savage had redeemed it. There he sat, his very
+indifference speaking a nature in which there lurked no civilized
+hypocrisies and bland deceits. Wild he was; a very sight of sights
+to see; yet I began to feel myself mysteriously drawn towards him.
+And those same things that would have repelled most others, they were
+the very magnets that thus drew me. I'll try a pagan friend, thought
+I, since Christian kindness has proved but hollow courtesy. I drew
+my bench near him, and made some friendly signs and hints, doing my
+best to talk with him meanwhile. At first he little noticed these
+advances; but presently, upon my referring to his last night's
+hospitalities, he made out to ask me whether we were again to be
+bedfellows. I told him yes; whereat I thought he looked pleased,
+perhaps a little complimented.
+
+We then turned over the book together, and I endeavored to explain to
+him the purpose of the printing, and the meaning of the few pictures
+that were in it. Thus I soon engaged his interest; and from that we
+went to jabbering the best we could about the various outer sights to
+be seen in this famous town. Soon I proposed a social smoke; and,
+producing his pouch and tomahawk, he quietly offered me a puff. And
+then we sat exchanging puffs from that wild pipe of his, and keeping
+it regularly passing between us.
+
+If there yet lurked any ice of indifference towards me in the Pagan's
+breast, this pleasant, genial smoke we had, soon thawed it out, and
+left us cronies. He seemed to take to me quite as naturally and
+unbiddenly as I to him; and when our smoke was over, he pressed his
+forehead against mine, clasped me round the waist, and said that
+henceforth we were married; meaning, in his country's phrase, that we
+were bosom friends; he would gladly die for me, if need should be.
+In a countryman, this sudden flame of friendship would have seemed
+far too premature, a thing to be much distrusted; but in this simple
+savage those old rules would not apply.
+
+After supper, and another social chat and smoke, we went to our room
+together. He made me a present of his embalmed head; took out his
+enormous tobacco wallet, and groping under the tobacco, drew out some
+thirty dollars in silver; then spreading them on the table, and
+mechanically dividing them into two equal portions, pushed one of
+them towards me, and said it was mine. I was going to remonstrate;
+but he silenced me by pouring them into my trowsers' pockets. I let
+them stay. He then went about his evening prayers, took out his
+idol, and removed the paper fireboard. By certain signs and
+symptoms, I thought he seemed anxious for me to join him; but well
+knowing what was to follow, I deliberated a moment whether, in case
+he invited me, I would comply or otherwise.
+
+I was a good Christian; born and bred in the bosom of the infallible
+Presbyterian Church. How then could I unite with this wild idolator
+in worshipping his piece of wood? But what is worship? thought I.
+Do you suppose now, Ishmael, that the magnanimous God of heaven and
+earth--pagans and all included--can possibly be jealous of an
+insignificant bit of black wood? Impossible! But what is
+worship?--to do the will of God--THAT is worship. And what is the
+will of God?--to do to my fellow man what I would have my fellow man
+to do to me--THAT is the will of God. Now, Queequeg is my fellow
+man. And what do I wish that this Queequeg would do to me? Why,
+unite with me in my particular Presbyterian form of worship.
+Consequently, I must then unite with him in his; ergo, I must turn
+idolator. So I kindled the shavings; helped prop up the innocent
+little idol; offered him burnt biscuit with Queequeg; salamed before
+him twice or thrice; kissed his nose; and that done, we undressed and
+went to bed, at peace with our own consciences and all the world.
+But we did not go to sleep without some little chat.
+
+How it is I know not; but there is no place like a bed for
+confidential disclosures between friends. Man and wife, they say,
+there open the very bottom of their souls to each other; and some old
+couples often lie and chat over old times till nearly morning. Thus,
+then, in our hearts' honeymoon, lay I and Queequeg--a cosy, loving
+pair.
+
+
+
+CHAPTER 11
+
+Nightgown.
+
+
+We had lain thus in bed, chatting and napping at short intervals, and
+Queequeg now and then affectionately throwing his brown tattooed legs
+over mine, and then drawing them back; so entirely sociable and free
+and easy were we; when, at last, by reason of our confabulations,
+what little nappishness remained in us altogether departed, and we
+felt like getting up again, though day-break was yet some way down
+the future.
+
+Yes, we became very wakeful; so much so that our recumbent position
+began to grow wearisome, and by little and little we found ourselves
+sitting up; the clothes well tucked around us, leaning against the
+head-board with our four knees drawn up close together, and our two
+noses bending over them, as if our kneepans were warming-pans. We
+felt very nice and snug, the more so since it was so chilly out of
+doors; indeed out of bed-clothes too, seeing that there was no fire
+in the room. The more so, I say, because truly to enjoy bodily
+warmth, some small part of you must be cold, for there is no quality
+in this world that is not what it is merely by contrast. Nothing
+exists in itself. If you flatter yourself that you are all over
+comfortable, and have been so a long time, then you cannot be said to
+be comfortable any more. But if, like Queequeg and me in the bed,
+the tip of your nose or the crown of your head be slightly chilled,
+why then, indeed, in the general consciousness you feel most
+delightfully and unmistakably warm. For this reason a sleeping
+apartment should never be furnished with a fire, which is one of the
+luxurious discomforts of the rich. For the height of this sort of
+deliciousness is to have nothing but the blanket between you and
+your snugness and the cold of the outer air. Then there you lie like
+the one warm spark in the heart of an arctic crystal.
+
+We had been sitting in this crouching manner for some time, when all
+at once I thought I would open my eyes; for when between sheets,
+whether by day or by night, and whether asleep or awake, I have a way
+of always keeping my eyes shut, in order the more to concentrate the
+snugness of being in bed. Because no man can ever feel his own
+identity aright except his eyes be closed; as if darkness were
+indeed the proper element of our essences, though light be more
+congenial to our clayey part. Upon opening my eyes then, and coming
+out of my own pleasant and self-created darkness into the imposed and
+coarse outer gloom of the unilluminated twelve-o'clock-at-night, I
+experienced a disagreeable revulsion. Nor did I at all object to the
+hint from Queequeg that perhaps it were best to strike a light,
+seeing that we were so wide awake; and besides he felt a strong
+desire to have a few quiet puffs from his Tomahawk. Be it said, that
+though I had felt such a strong repugnance to his smoking in the bed
+the night before, yet see how elastic our stiff prejudices grow when
+love once comes to bend them. For now I liked nothing better than
+to have Queequeg smoking by me, even in bed, because he seemed to be
+full of such serene household joy then. I no more felt unduly
+concerned for the landlord's policy of insurance. I was only alive
+to the condensed confidential comfortableness of sharing a pipe and a
+blanket with a real friend. With our shaggy jackets drawn about our
+shoulders, we now passed the Tomahawk from one to the other, till
+slowly there grew over us a blue hanging tester of smoke, illuminated
+by the flame of the new-lit lamp.
+
+Whether it was that this undulating tester rolled the savage away to
+far distant scenes, I know not, but he now spoke of his native
+island; and, eager to hear his history, I begged him to go on and
+tell it. He gladly complied. Though at the time I but ill
+comprehended not a few of his words, yet subsequent disclosures, when
+I had become more familiar with his broken phraseology, now enable me
+to present the whole story such as it may prove in the mere skeleton
+I give.
+
+
+
+CHAPTER 12
+
+Biographical.
+
+
+Queequeg was a native of Rokovoko, an island far away to the West
+and South. It is not down in any map; true places never are.
+
+When a new-hatched savage running wild about his native woodlands in
+a grass clout, followed by the nibbling goats, as if he were a green
+sapling; even then, in Queequeg's ambitious soul, lurked a strong
+desire to see something more of Christendom than a specimen whaler or
+two. His father was a High Chief, a King; his uncle a High Priest;
+and on the maternal side he boasted aunts who were the wives of
+unconquerable warriors. There was excellent blood in his
+veins--royal stuff; though sadly vitiated, I fear, by the cannibal
+propensity he nourished in his untutored youth.
+
+A Sag Harbor ship visited his father's bay, and Queequeg sought a
+passage to Christian lands. But the ship, having her full complement
+of seamen, spurned his suit; and not all the King his father's
+influence could prevail. But Queequeg vowed a vow. Alone in his
+canoe, he paddled off to a distant strait, which he knew the ship
+must pass through when she quitted the island. On one side was a
+coral reef; on the other a low tongue of land, covered with mangrove
+thickets that grew out into the water. Hiding his canoe, still
+afloat, among these thickets, with its prow seaward, he sat down in
+the stern, paddle low in hand; and when the ship was gliding by, like
+a flash he darted out; gained her side; with one backward dash of his
+foot capsized and sank his canoe; climbed up the chains; and throwing
+himself at full length upon the deck, grappled a ring-bolt there, and
+swore not to let it go, though hacked in pieces.
+
+In vain the captain threatened to throw him overboard; suspended a
+cutlass over his naked wrists; Queequeg was the son of a King, and
+Queequeg budged not. Struck by his desperate dauntlessness, and his
+wild desire to visit Christendom, the captain at last relented, and
+told him he might make himself at home. But this fine young
+savage--this sea Prince of Wales, never saw the Captain's cabin.
+They put him down among the sailors, and made a whaleman of him. But
+like Czar Peter content to toil in the shipyards of foreign cities,
+Queequeg disdained no seeming ignominy, if thereby he might happily
+gain the power of enlightening his untutored countrymen. For at
+bottom--so he told me--he was actuated by a profound desire to learn
+among the Christians, the arts whereby to make his people still
+happier than they were; and more than that, still better than they
+were. But, alas! the practices of whalemen soon convinced him that
+even Christians could be both miserable and wicked; infinitely more
+so, than all his father's heathens. Arrived at last in old Sag
+Harbor; and seeing what the sailors did there; and then going on to
+Nantucket, and seeing how they spent their wages in that place also,
+poor Queequeg gave it up for lost. Thought he, it's a wicked world
+in all meridians; I'll die a pagan.
+
+And thus an old idolator at heart, he yet lived among these
+Christians, wore their clothes, and tried to talk their gibberish.
+Hence the queer ways about him, though now some time from home.
+
+By hints, I asked him whether he did not propose going back, and
+having a coronation; since he might now consider his father dead and
+gone, he being very old and feeble at the last accounts. He answered
+no, not yet; and added that he was fearful Christianity, or rather
+Christians, had unfitted him for ascending the pure and undefiled
+throne of thirty pagan Kings before him. But by and by, he said, he
+would return,--as soon as he felt himself baptized again. For the
+nonce, however, he proposed to sail about, and sow his wild oats in
+all four oceans. They had made a harpooneer of him, and that barbed
+iron was in lieu of a sceptre now.
+
+I asked him what might be his immediate purpose, touching his future
+movements. He answered, to go to sea again, in his old vocation.
+Upon this, I told him that whaling was my own design, and informed
+him of my intention to sail out of Nantucket, as being the most
+promising port for an adventurous whaleman to embark from. He at
+once resolved to accompany me to that island, ship aboard the same
+vessel, get into the same watch, the same boat, the same mess with
+me, in short to share my every hap; with both my hands in his, boldly
+dip into the Potluck of both worlds. To all this I joyously
+assented; for besides the affection I now felt for Queequeg, he was
+an experienced harpooneer, and as such, could not fail to be of great
+usefulness to one, who, like me, was wholly ignorant of the mysteries
+of whaling, though well acquainted with the sea, as known to merchant
+seamen.
+
+His story being ended with his pipe's last dying puff, Queequeg
+embraced me, pressed his forehead against mine, and blowing out the
+light, we rolled over from each other, this way and that, and very
+soon were sleeping.
+
+
+CHAPTER 13
+
+Wheelbarrow.
+
+
+Next morning, Monday, after disposing of the embalmed head to a
+barber, for a block, I settled my own and comrade's bill; using,
+however, my comrade's money. The grinning landlord, as well as the
+boarders, seemed amazingly tickled at the sudden friendship which had
+sprung up between me and Queequeg--especially as Peter Coffin's cock
+and bull stories about him had previously so much alarmed me
+concerning the very person whom I now companied with.
+
+We borrowed a wheelbarrow, and embarking our things, including my own
+poor carpet-bag, and Queequeg's canvas sack and hammock, away we went
+down to "the Moss," the little Nantucket packet schooner moored at
+the wharf. As we were going along the people stared; not at Queequeg
+so much--for they were used to seeing cannibals like him in their
+streets,--but at seeing him and me upon such confidential terms. But
+we heeded them not, going along wheeling the barrow by turns, and
+Queequeg now and then stopping to adjust the sheath on his harpoon
+barbs. I asked him why he carried such a troublesome thing with him
+ashore, and whether all whaling ships did not find their own
+harpoons. To this, in substance, he replied, that though what I
+hinted was true enough, yet he had a particular affection for his own
+harpoon, because it was of assured stuff, well tried in many a mortal
+combat, and deeply intimate with the hearts of whales. In short,
+like many inland reapers and mowers, who go into the farmers' meadows
+armed with their own scythes--though in no wise obliged to furnish
+them--even so, Queequeg, for his own private reasons, preferred his
+own harpoon.
+
+Shifting the barrow from my hand to his, he told me a funny story
+about the first wheelbarrow he had ever seen. It was in Sag Harbor.
+The owners of his ship, it seems, had lent him one, in which to carry
+his heavy chest to his boarding house. Not to seem ignorant about
+the thing--though in truth he was entirely so, concerning the precise
+way in which to manage the barrow--Queequeg puts his chest upon it;
+lashes it fast; and then shoulders the barrow and marches up the
+wharf. "Why," said I, "Queequeg, you might have known better than
+that, one would think. Didn't the people laugh?"
+
+Upon this, he told me another story. The people of his island of
+Rokovoko, it seems, at their wedding feasts express the fragrant
+water of young cocoanuts into a large stained calabash like a
+punchbowl; and this punchbowl always forms the great central ornament
+on the braided mat where the feast is held. Now a certain grand
+merchant ship once touched at Rokovoko, and its commander--from all
+accounts, a very stately punctilious gentleman, at least for a sea
+captain--this commander was invited to the wedding feast of
+Queequeg's sister, a pretty young princess just turned of ten. Well;
+when all the wedding guests were assembled at the bride's bamboo
+cottage, this Captain marches in, and being assigned the post of
+honour, placed himself over against the punchbowl, and between the
+High Priest and his majesty the King, Queequeg's father. Grace being
+said,--for those people have their grace as well as we--though
+Queequeg told me that unlike us, who at such times look downwards to
+our platters, they, on the contrary, copying the ducks, glance
+upwards to the great Giver of all feasts--Grace, I say, being said,
+the High Priest opens the banquet by the immemorial ceremony of the
+island; that is, dipping his consecrated and consecrating fingers
+into the bowl before the blessed beverage circulates. Seeing himself
+placed next the Priest, and noting the ceremony, and thinking
+himself--being Captain of a ship--as having plain precedence over a
+mere island King, especially in the King's own house--the Captain
+coolly proceeds to wash his hands in the punchbowl;--taking it I
+suppose for a huge finger-glass. "Now," said Queequeg, "what you
+tink now?--Didn't our people laugh?"
+
+At last, passage paid, and luggage safe, we stood on board the
+schooner. Hoisting sail, it glided down the Acushnet river. On one
+side, New Bedford rose in terraces of streets, their ice-covered
+trees all glittering in the clear, cold air. Huge hills and
+mountains of casks on casks were piled upon her wharves, and side by
+side the world-wandering whale ships lay silent and safely moored at
+last; while from others came a sound of carpenters and coopers, with
+blended noises of fires and forges to melt the pitch, all betokening
+that new cruises were on the start; that one most perilous and long
+voyage ended, only begins a second; and a second ended, only begins a
+third, and so on, for ever and for aye. Such is the endlessness,
+yea, the intolerableness of all earthly effort.
+
+Gaining the more open water, the bracing breeze waxed fresh; the
+little Moss tossed the quick foam from her bows, as a young colt his
+snortings. How I snuffed that Tartar air!--how I spurned that
+turnpike earth!--that common highway all over dented with the marks
+of slavish heels and hoofs; and turned me to admire the magnanimity
+of the sea which will permit no records.
+
+At the same foam-fountain, Queequeg seemed to drink and reel with me.
+His dusky nostrils swelled apart; he showed his filed and pointed
+teeth. On, on we flew; and our offing gained, the Moss did homage to
+the blast; ducked and dived her bows as a slave before the Sultan.
+Sideways leaning, we sideways darted; every ropeyarn tingling like a
+wire; the two tall masts buckling like Indian canes in land
+tornadoes. So full of this reeling scene were we, as we stood by the
+plunging bowsprit, that for some time we did not notice the jeering
+glances of the passengers, a lubber-like assembly, who marvelled that
+two fellow beings should be so companionable; as though a white man
+were anything more dignified than a whitewashed negro. But there
+were some boobies and bumpkins there, who, by their intense
+greenness, must have come from the heart and centre of all verdure.
+Queequeg caught one of these young saplings mimicking him behind his
+back. I thought the bumpkin's hour of doom was come. Dropping his
+harpoon, the brawny savage caught him in his arms, and by an almost
+miraculous dexterity and strength, sent him high up bodily into the
+air; then slightly tapping his stern in mid-somerset, the fellow
+landed with bursting lungs upon his feet, while Queequeg, turning his
+back upon him, lighted his tomahawk pipe and passed it to me for a
+puff.
+
+"Capting! Capting! yelled the bumpkin, running towards that officer;
+"Capting, Capting, here's the devil."
+
+"Hallo, YOU sir," cried the Captain, a gaunt rib of the sea, stalking
+up to Queequeg, "what in thunder do you mean by that? Don't you know
+you might have killed that chap?"
+
+"What him say?" said Queequeg, as he mildly turned to me.
+
+"He say," said I, "that you came near kill-e that man there,"
+pointing to the still shivering greenhorn.
+
+"Kill-e," cried Queequeg, twisting his tattooed face into an
+unearthly expression of disdain, "ah! him bevy small-e fish-e;
+Queequeg no kill-e so small-e fish-e; Queequeg kill-e big whale!"
+
+"Look you," roared the Captain, "I'll kill-e YOU, you cannibal, if
+you try any more of your tricks aboard here; so mind your eye."
+
+But it so happened just then, that it was high time for the Captain
+to mind his own eye. The prodigious strain upon the main-sail had
+parted the weather-sheet, and the tremendous boom was now flying from
+side to side, completely sweeping the entire after part of the deck.
+The poor fellow whom Queequeg had handled so roughly, was swept
+overboard; all hands were in a panic; and to attempt snatching at the
+boom to stay it, seemed madness. It flew from right to left, and
+back again, almost in one ticking of a watch, and every instant
+seemed on the point of snapping into splinters. Nothing was done,
+and nothing seemed capable of being done; those on deck rushed
+towards the bows, and stood eyeing the boom as if it were the lower
+jaw of an exasperated whale. In the midst of this consternation,
+Queequeg dropped deftly to his knees, and crawling under the path of
+the boom, whipped hold of a rope, secured one end to the bulwarks,
+and then flinging the other like a lasso, caught it round the boom as
+it swept over his head, and at the next jerk, the spar was that way
+trapped, and all was safe. The schooner was run into the wind, and
+while the hands were clearing away the stern boat, Queequeg, stripped
+to the waist, darted from the side with a long living arc of a leap.
+For three minutes or more he was seen swimming like a dog, throwing
+his long arms straight out before him, and by turns revealing his
+brawny shoulders through the freezing foam. I looked at the grand
+and glorious fellow, but saw no one to be saved. The greenhorn had
+gone down. Shooting himself perpendicularly from the water,
+Queequeg, now took an instant's glance around him, and seeming to see
+just how matters were, dived down and disappeared. A few minutes
+more, and he rose again, one arm still striking out, and with the
+other dragging a lifeless form. The boat soon picked them up. The
+poor bumpkin was restored. All hands voted Queequeg a noble trump;
+the captain begged his pardon. From that hour I clove to Queequeg
+like a barnacle; yea, till poor Queequeg took his last long dive.
+
+Was there ever such unconsciousness? He did not seem to think that
+he at all deserved a medal from the Humane and Magnanimous Societies.
+He only asked for water--fresh water--something to wipe the brine
+off; that done, he put on dry clothes, lighted his pipe, and leaning
+against the bulwarks, and mildly eyeing those around him, seemed to
+be saying to himself--"It's a mutual, joint-stock world, in all
+meridians. We cannibals must help these Christians."
+
+
+
+CHAPTER 14
+
+Nantucket.
+
+
+Nothing more happened on the passage worthy the mentioning; so, after
+a fine run, we safely arrived in Nantucket.
+
+Nantucket! Take out your map and look at it. See what a real corner
+of the world it occupies; how it stands there, away off shore, more
+lonely than the Eddystone lighthouse. Look at it--a mere hillock,
+and elbow of sand; all beach, without a background. There is more
+sand there than you would use in twenty years as a substitute for
+blotting paper. Some gamesome wights will tell you that they have to
+plant weeds there, they don't grow naturally; that they import Canada
+thistles; that they have to send beyond seas for a spile to stop a
+leak in an oil cask; that pieces of wood in Nantucket are carried
+about like bits of the true cross in Rome; that people there plant
+toadstools before their houses, to get under the shade in summer
+time; that one blade of grass makes an oasis, three blades in a day's
+walk a prairie; that they wear quicksand shoes, something like
+Laplander snow-shoes; that they are so shut up, belted about, every
+way inclosed, surrounded, and made an utter island of by the ocean,
+that to their very chairs and tables small clams will sometimes be
+found adhering, as to the backs of sea turtles. But these
+extravaganzas only show that Nantucket is no Illinois.
+
+Look now at the wondrous traditional story of how this island was
+settled by the red-men. Thus goes the legend. In olden times an
+eagle swooped down upon the New England coast, and carried off an
+infant Indian in his talons. With loud lament the parents saw their
+child borne out of sight over the wide waters. They resolved to
+follow in the same direction. Setting out in their canoes, after a
+perilous passage they discovered the island, and there they found an
+empty ivory casket,--the poor little Indian's skeleton.
+
+What wonder, then, that these Nantucketers, born on a beach, should
+take to the sea for a livelihood! They first caught crabs and
+quohogs in the sand; grown bolder, they waded out with nets for
+mackerel; more experienced, they pushed off in boats and captured
+cod; and at last, launching a navy of great ships on the sea,
+explored this watery world; put an incessant belt of
+circumnavigations round it; peeped in at Behring's Straits; and in
+all seasons and all oceans declared everlasting war with the
+mightiest animated mass that has survived the flood; most monstrous
+and most mountainous! That Himmalehan, salt-sea Mastodon, clothed
+with such portentousness of unconscious power, that his very panics
+are more to be dreaded than his most fearless and malicious assaults!
+
+And thus have these naked Nantucketers, these sea hermits, issuing
+from their ant-hill in the sea, overrun and conquered the watery
+world like so many Alexanders; parcelling out among them the
+Atlantic, Pacific, and Indian oceans, as the three pirate powers did
+Poland. Let America add Mexico to Texas, and pile Cuba upon Canada;
+let the English overswarm all India, and hang out their blazing
+banner from the sun; two thirds of this terraqueous globe are the
+Nantucketer's. For the sea is his; he owns it, as Emperors own
+empires; other seamen having but a right of way through it. Merchant
+ships are but extension bridges; armed ones but floating forts; even
+pirates and privateers, though following the sea as highwaymen the
+road, they but plunder other ships, other fragments of the land like
+themselves, without seeking to draw their living from the bottomless
+deep itself. The Nantucketer, he alone resides and riots on the sea;
+he alone, in Bible language, goes down to it in ships; to and fro
+ploughing it as his own special plantation. THERE is his home; THERE
+lies his business, which a Noah's flood would not interrupt, though
+it overwhelmed all the millions in China. He lives on the sea, as
+prairie cocks in the prairie; he hides among the waves, he climbs
+them as chamois hunters climb the Alps. For years he knows not the
+land; so that when he comes to it at last, it smells like another
+world, more strangely than the moon would to an Earthsman. With the
+landless gull, that at sunset folds her wings and is rocked to sleep
+between billows; so at nightfall, the Nantucketer, out of sight of
+land, furls his sails, and lays him to his rest, while under his very
+pillow rush herds of walruses and whales.
+
+
+
+CHAPTER 15
+
+Chowder.
+
+
+It was quite late in the evening when the little Moss came snugly to
+anchor, and Queequeg and I went ashore; so we could attend to no
+business that day, at least none but a supper and a bed. The
+landlord of the Spouter-Inn had recommended us to his cousin Hosea
+Hussey of the Try Pots, whom he asserted to be the proprietor of one
+of the best kept hotels in all Nantucket, and moreover he had assured
+us that Cousin Hosea, as he called him, was famous for his chowders.
+In short, he plainly hinted that we could not possibly do better than
+try pot-luck at the Try Pots. But the directions he had given us
+about keeping a yellow warehouse on our starboard hand till we opened
+a white church to the larboard, and then keeping that on the larboard
+hand till we made a corner three points to the starboard, and that
+done, then ask the first man we met where the place was: these
+crooked directions of his very much puzzled us at first, especially
+as, at the outset, Queequeg insisted that the yellow warehouse--our
+first point of departure--must be left on the larboard hand, whereas
+I had understood Peter Coffin to say it was on the starboard.
+However, by dint of beating about a little in the dark, and now and
+then knocking up a peaceable inhabitant to inquire the way, we at
+last came to something which there was no mistaking.
+
+Two enormous wooden pots painted black, and suspended by asses' ears,
+swung from the cross-trees of an old top-mast, planted in front of an
+old doorway. The horns of the cross-trees were sawed off on the
+other side, so that this old top-mast looked not a little like a
+gallows. Perhaps I was over sensitive to such impressions at the
+time, but I could not help staring at this gallows with a vague
+misgiving. A sort of crick was in my neck as I gazed up to the two
+remaining horns; yes, TWO of them, one for Queequeg, and one for me.
+It's ominous, thinks I. A Coffin my Innkeeper upon landing in my
+first whaling port; tombstones staring at me in the whalemen's
+chapel; and here a gallows! and a pair of prodigious black pots too!
+Are these last throwing out oblique hints touching Tophet?
+
+I was called from these reflections by the sight of a freckled woman
+with yellow hair and a yellow gown, standing in the porch of the inn,
+under a dull red lamp swinging there, that looked much like an
+injured eye, and carrying on a brisk scolding with a man in a purple
+woollen shirt.
+
+"Get along with ye," said she to the man, "or I'll be combing ye!"
+
+"Come on, Queequeg," said I, "all right. There's Mrs. Hussey."
+
+And so it turned out; Mr. Hosea Hussey being from home, but leaving
+Mrs. Hussey entirely competent to attend to all his affairs. Upon
+making known our desires for a supper and a bed, Mrs. Hussey,
+postponing further scolding for the present, ushered us into a little
+room, and seating us at a table spread with the relics of a recently
+concluded repast, turned round to us and said--"Clam or Cod?"
+
+"What's that about Cods, ma'am?" said I, with much politeness.
+
+"Clam or Cod?" she repeated.
+
+"A clam for supper? a cold clam; is THAT what you mean, Mrs. Hussey?"
+says I, "but that's a rather cold and clammy reception in the winter
+time, ain't it, Mrs. Hussey?"
+
+But being in a great hurry to resume scolding the man in the purple
+Shirt, who was waiting for it in the entry, and seeming to hear
+nothing but the word "clam," Mrs. Hussey hurried towards an open door
+leading to the kitchen, and bawling out "clam for two," disappeared.
+
+"Queequeg," said I, "do you think that we can make out a supper for
+us both on one clam?"
+
+However, a warm savory steam from the kitchen served to belie the
+apparently cheerless prospect before us. But when that smoking
+chowder came in, the mystery was delightfully explained. Oh, sweet
+friends! hearken to me. It was made of small juicy clams, scarcely
+bigger than hazel nuts, mixed with pounded ship biscuit, and salted
+pork cut up into little flakes; the whole enriched with butter, and
+plentifully seasoned with pepper and salt. Our appetites being
+sharpened by the frosty voyage, and in particular, Queequeg seeing
+his favourite fishing food before him, and the chowder being
+surpassingly excellent, we despatched it with great expedition: when
+leaning back a moment and bethinking me of Mrs. Hussey's clam and cod
+announcement, I thought I would try a little experiment. Stepping to
+the kitchen door, I uttered the word "cod" with great emphasis, and
+resumed my seat. In a few moments the savoury steam came forth
+again, but with a different flavor, and in good time a fine
+cod-chowder was placed before us.
+
+We resumed business; and while plying our spoons in the bowl, thinks
+I to myself, I wonder now if this here has any effect on the head?
+What's that stultifying saying about chowder-headed people? "But
+look, Queequeg, ain't that a live eel in your bowl? Where's your
+harpoon?"
+
+Fishiest of all fishy places was the Try Pots, which well deserved
+its name; for the pots there were always boiling chowders. Chowder
+for breakfast, and chowder for dinner, and chowder for supper, till
+you began to look for fish-bones coming through your clothes. The
+area before the house was paved with clam-shells. Mrs. Hussey wore a
+polished necklace of codfish vertebra; and Hosea Hussey had his
+account books bound in superior old shark-skin. There was a fishy
+flavor to the milk, too, which I could not at all account for, till
+one morning happening to take a stroll along the beach among some
+fishermen's boats, I saw Hosea's brindled cow feeding on fish
+remnants, and marching along the sand with each foot in a cod's
+decapitated head, looking very slip-shod, I assure ye.
+
+Supper concluded, we received a lamp, and directions from Mrs. Hussey
+concerning the nearest way to bed; but, as Queequeg was about to
+precede me up the stairs, the lady reached forth her arm, and
+demanded his harpoon; she allowed no harpoon in her chambers. "Why
+not? said I; "every true whaleman sleeps with his harpoon--but why
+not?" "Because it's dangerous," says she. "Ever since young Stiggs
+coming from that unfort'nt v'y'ge of his, when he was gone four years
+and a half, with only three barrels of ILE, was found dead in my
+first floor back, with his harpoon in his side; ever since then I
+allow no boarders to take sich dangerous weepons in their rooms at
+night. So, Mr. Queequeg" (for she had learned his name), "I will
+just take this here iron, and keep it for you till morning. But the
+chowder; clam or cod to-morrow for breakfast, men?"
+
+"Both," says I; "and let's have a couple of smoked herring by way of
+variety."
+
+
+
+CHAPTER 16
+
+The Ship.
+
+
+In bed we concocted our plans for the morrow. But to my surprise and
+no small concern, Queequeg now gave me to understand, that he had
+been diligently consulting Yojo--the name of his black little
+god--and Yojo had told him two or three times over, and strongly
+insisted upon it everyway, that instead of our going together among
+the whaling-fleet in harbor, and in concert selecting our craft;
+instead of this, I say, Yojo earnestly enjoined that the selection of
+the ship should rest wholly with me, inasmuch as Yojo purposed
+befriending us; and, in order to do so, had already pitched upon a
+vessel, which, if left to myself, I, Ishmael, should infallibly light
+upon, for all the world as though it had turned out by chance; and in
+that vessel I must immediately ship myself, for the present
+irrespective of Queequeg.
+
+I have forgotten to mention that, in many things, Queequeg placed
+great confidence in the excellence of Yojo's judgment and surprising
+forecast of things; and cherished Yojo with considerable esteem, as a
+rather good sort of god, who perhaps meant well enough upon the
+whole, but in all cases did not succeed in his benevolent designs.
+
+Now, this plan of Queequeg's, or rather Yojo's, touching the
+selection of our craft; I did not like that plan at all. I had not a
+little relied upon Queequeg's sagacity to point out the whaler best
+fitted to carry us and our fortunes securely. But as all my
+remonstrances produced no effect upon Queequeg, I was obliged to
+acquiesce; and accordingly prepared to set about this business with a
+determined rushing sort of energy and vigor, that should quickly
+settle that trifling little affair. Next morning early, leaving
+Queequeg shut up with Yojo in our little bedroom--for it seemed that
+it was some sort of Lent or Ramadan, or day of fasting, humiliation,
+and prayer with Queequeg and Yojo that day; HOW it was I never could
+find out, for, though I applied myself to it several times, I never
+could master his liturgies and XXXIX Articles--leaving Queequeg,
+then, fasting on his tomahawk pipe, and Yojo warming himself at his
+sacrificial fire of shavings, I sallied out among the shipping.
+After much prolonged sauntering and many random inquiries, I learnt
+that there were three ships up for three-years' voyages--The
+Devil-dam, the Tit-bit, and the Pequod. DEVIL-DAM, I do not know
+the origin of; TIT-BIT is obvious; PEQUOD, you will no doubt
+remember, was the name of a celebrated tribe of Massachusetts
+Indians; now extinct as the ancient Medes. I peered and pryed about
+the Devil-dam; from her, hopped over to the Tit-bit; and finally,
+going on board the Pequod, looked around her for a moment, and then
+decided that this was the very ship for us.
+
+You may have seen many a quaint craft in your day, for aught I
+know;--square-toed luggers; mountainous Japanese junks; butter-box
+galliots, and what not; but take my word for it, you never saw such a
+rare old craft as this same rare old Pequod. She was a ship of the
+old school, rather small if anything; with an old-fashioned
+claw-footed look about her. Long seasoned and weather-stained in the
+typhoons and calms of all four oceans, her old hull's complexion was
+darkened like a French grenadier's, who has alike fought in Egypt and
+Siberia. Her venerable bows looked bearded. Her masts--cut
+somewhere on the coast of Japan, where her original ones were lost
+overboard in a gale--her masts stood stiffly up like the spines of
+the three old kings of Cologne. Her ancient decks were worn and
+wrinkled, like the pilgrim-worshipped flag-stone in Canterbury
+Cathedral where Becket bled. But to all these her old antiquities,
+were added new and marvellous features, pertaining to the wild
+business that for more than half a century she had followed. Old
+Captain Peleg, many years her chief-mate, before he commanded another
+vessel of his own, and now a retired seaman, and one of the principal
+owners of the Pequod,--this old Peleg, during the term of his
+chief-mateship, had built upon her original grotesqueness, and inlaid
+it, all over, with a quaintness both of material and device,
+unmatched by anything except it be Thorkill-Hake's carved buckler or
+bedstead. She was apparelled like any barbaric Ethiopian emperor,
+his neck heavy with pendants of polished ivory. She was a thing of
+trophies. A cannibal of a craft, tricking herself forth in the
+chased bones of her enemies. All round, her unpanelled, open
+bulwarks were garnished like one continuous jaw, with the long sharp
+teeth of the sperm whale, inserted there for pins, to fasten her old
+hempen thews and tendons to. Those thews ran not through base blocks
+of land wood, but deftly travelled over sheaves of sea-ivory.
+Scorning a turnstile wheel at her reverend helm, she sported there a
+tiller; and that tiller was in one mass, curiously carved from the
+long narrow lower jaw of her hereditary foe. The helmsman who
+steered by that tiller in a tempest, felt like the Tartar, when he
+holds back his fiery steed by clutching its jaw. A noble craft, but
+somehow a most melancholy! All noble things are touched with that.
+
+Now when I looked about the quarter-deck, for some one having
+authority, in order to propose myself as a candidate for the voyage,
+at first I saw nobody; but I could not well overlook a strange sort
+of tent, or rather wigwam, pitched a little behind the main-mast. It
+seemed only a temporary erection used in port. It was of a conical
+shape, some ten feet high; consisting of the long, huge slabs of
+limber black bone taken from the middle and highest part of the jaws
+of the right-whale. Planted with their broad ends on the deck, a
+circle of these slabs laced together, mutually sloped towards each
+other, and at the apex united in a tufted point, where the loose
+hairy fibres waved to and fro like the top-knot on some old
+Pottowottamie Sachem's head. A triangular opening faced towards the
+bows of the ship, so that the insider commanded a complete view
+forward.
+
+And half concealed in this queer tenement, I at length found one who
+by his aspect seemed to have authority; and who, it being noon, and
+the ship's work suspended, was now enjoying respite from the burden
+of command. He was seated on an old-fashioned oaken chair, wriggling
+all over with curious carving; and the bottom of which was formed of
+a stout interlacing of the same elastic stuff of which the wigwam was
+constructed.
+
+There was nothing so very particular, perhaps, about the appearance
+of the elderly man I saw; he was brown and brawny, like most old
+seamen, and heavily rolled up in blue pilot-cloth, cut in the Quaker
+style; only there was a fine and almost microscopic net-work of the
+minutest wrinkles interlacing round his eyes, which must have arisen
+from his continual sailings in many hard gales, and always looking to
+windward;--for this causes the muscles about the eyes to become
+pursed together. Such eye-wrinkles are very effectual in a scowl.
+
+"Is this the Captain of the Pequod?" said I, advancing to the door of
+the tent.
+
+"Supposing it be the captain of the Pequod, what dost thou want of
+him?" he demanded.
+
+"I was thinking of shipping."
+
+"Thou wast, wast thou? I see thou art no Nantucketer--ever been in
+a stove boat?"
+
+"No, Sir, I never have."
+
+"Dost know nothing at all about whaling, I dare say--eh?
+
+"Nothing, Sir; but I have no doubt I shall soon learn. I've been
+several voyages in the merchant service, and I think that--"
+
+"Merchant service be damned. Talk not that lingo to me. Dost see
+that leg?--I'll take that leg away from thy stern, if ever thou
+talkest of the marchant service to me again. Marchant service
+indeed! I suppose now ye feel considerable proud of having served in
+those marchant ships. But flukes! man, what makes thee want to go a
+whaling, eh?--it looks a little suspicious, don't it, eh?--Hast not
+been a pirate, hast thou?--Didst not rob thy last Captain, didst
+thou?--Dost not think of murdering the officers when thou gettest to
+sea?"
+
+I protested my innocence of these things. I saw that under the mask
+of these half humorous innuendoes, this old seaman, as an insulated
+Quakerish Nantucketer, was full of his insular prejudices, and rather
+distrustful of all aliens, unless they hailed from Cape Cod or the
+Vineyard.
+
+"But what takes thee a-whaling? I want to know that before I think
+of shipping ye."
+
+"Well, sir, I want to see what whaling is. I want to see the world."
+
+"Want to see what whaling is, eh? Have ye clapped eye on Captain
+Ahab?"
+
+"Who is Captain Ahab, sir?"
+
+"Aye, aye, I thought so. Captain Ahab is the Captain of this ship."
+
+"I am mistaken then. I thought I was speaking to the Captain
+himself."
+
+"Thou art speaking to Captain Peleg--that's who ye are speaking to,
+young man. It belongs to me and Captain Bildad to see the Pequod
+fitted out for the voyage, and supplied with all her needs, including
+crew. We are part owners and agents. But as I was going to say, if
+thou wantest to know what whaling is, as thou tellest ye do, I can
+put ye in a way of finding it out before ye bind yourself to it, past
+backing out. Clap eye on Captain Ahab, young man, and thou wilt find
+that he has only one leg."
+
+"What do you mean, sir? Was the other one lost by a whale?"
+
+"Lost by a whale! Young man, come nearer to me: it was devoured,
+chewed up, crunched by the monstrousest parmacetty that ever chipped
+a boat!--ah, ah!"
+
+I was a little alarmed by his energy, perhaps also a little touched
+at the hearty grief in his concluding exclamation, but said as calmly
+as I could, "What you say is no doubt true enough, sir; but how could
+I know there was any peculiar ferocity in that particular whale,
+though indeed I might have inferred as much from the simple fact of
+the accident."
+
+"Look ye now, young man, thy lungs are a sort of soft, d'ye see; thou
+dost not talk shark a bit. SURE, ye've been to sea before now; sure
+of that?"
+
+"Sir," said I, "I thought I told you that I had been four voyages in
+the merchant--"
+
+"Hard down out of that! Mind what I said about the marchant
+service--don't aggravate me--I won't have it. But let us understand
+each other. I have given thee a hint about what whaling is; do ye
+yet feel inclined for it?"
+
+"I do, sir."
+
+"Very good. Now, art thou the man to pitch a harpoon down a live
+whale's throat, and then jump after it? Answer, quick!"
+
+"I am, sir, if it should be positively indispensable to do so; not to
+be got rid of, that is; which I don't take to be the fact."
+
+"Good again. Now then, thou not only wantest to go a-whaling, to
+find out by experience what whaling is, but ye also want to go in
+order to see the world? Was not that what ye said? I thought so.
+Well then, just step forward there, and take a peep over the
+weather-bow, and then back to me and tell me what ye see there."
+
+For a moment I stood a little puzzled by this curious request, not
+knowing exactly how to take it, whether humorously or in earnest.
+But concentrating all his crow's feet into one scowl, Captain Peleg
+started me on the errand.
+
+Going forward and glancing over the weather bow, I perceived that the
+ship swinging to her anchor with the flood-tide, was now obliquely
+pointing towards the open ocean. The prospect was unlimited, but
+exceedingly monotonous and forbidding; not the slightest variety that
+I could see.
+
+"Well, what's the report?" said Peleg when I came back; "what did ye
+see?"
+
+"Not much," I replied--"nothing but water; considerable horizon
+though, and there's a squall coming up, I think."
+
+"Well, what does thou think then of seeing the world? Do ye wish to
+go round Cape Horn to see any more of it, eh? Can't ye see the world
+where you stand?"
+
+I was a little staggered, but go a-whaling I must, and I would; and
+the Pequod was as good a ship as any--I thought the best--and all
+this I now repeated to Peleg. Seeing me so determined, he expressed
+his willingness to ship me.
+
+"And thou mayest as well sign the papers right off," he added--"come
+along with ye." And so saying, he led the way below deck into the
+cabin.
+
+Seated on the transom was what seemed to me a most uncommon and
+surprising figure. It turned out to be Captain Bildad, who along
+with Captain Peleg was one of the largest owners of the vessel; the
+other shares, as is sometimes the case in these ports, being held by
+a crowd of old annuitants; widows, fatherless children, and chancery
+wards; each owning about the value of a timber head, or a foot of
+plank, or a nail or two in the ship. People in Nantucket invest
+their money in whaling vessels, the same way that you do yours in
+approved state stocks bringing in good interest.
+
+Now, Bildad, like Peleg, and indeed many other Nantucketers, was a
+Quaker, the island having been originally settled by that sect; and
+to this day its inhabitants in general retain in an uncommon measure
+the peculiarities of the Quaker, only variously and anomalously
+modified by things altogether alien and heterogeneous. For some of
+these same Quakers are the most sanguinary of all sailors and
+whale-hunters. They are fighting Quakers; they are Quakers with a
+vengeance.
+
+So that there are instances among them of men, who, named with
+Scripture names--a singularly common fashion on the island--and in
+childhood naturally imbibing the stately dramatic thee and thou of
+the Quaker idiom; still, from the audacious, daring, and boundless
+adventure of their subsequent lives, strangely blend with these
+unoutgrown peculiarities, a thousand bold dashes of character, not
+unworthy a Scandinavian sea-king, or a poetical Pagan Roman. And
+when these things unite in a man of greatly superior natural force,
+with a globular brain and a ponderous heart; who has also by the
+stillness and seclusion of many long night-watches in the remotest
+waters, and beneath constellations never seen here at the north, been
+led to think untraditionally and independently; receiving all
+nature's sweet or savage impressions fresh from her own virgin
+voluntary and confiding breast, and thereby chiefly, but with some
+help from accidental advantages, to learn a bold and nervous lofty
+language--that man makes one in a whole nation's census--a mighty
+pageant creature, formed for noble tragedies. Nor will it at all
+detract from him, dramatically regarded, if either by birth or other
+circumstances, he have what seems a half wilful overruling morbidness
+at the bottom of his nature. For all men tragically great are made
+so through a certain morbidness. Be sure of this, O young ambition,
+all mortal greatness is but disease. But, as yet we have not to do
+with such an one, but with quite another; and still a man, who, if
+indeed peculiar, it only results again from another phase of the
+Quaker, modified by individual circumstances.
+
+Like Captain Peleg, Captain Bildad was a well-to-do, retired
+whaleman. But unlike Captain Peleg--who cared not a rush for what
+are called serious things, and indeed deemed those self-same serious
+things the veriest of all trifles--Captain Bildad had not only been
+originally educated according to the strictest sect of Nantucket
+Quakerism, but all his subsequent ocean life, and the sight of many
+unclad, lovely island creatures, round the Horn--all that had not
+moved this native born Quaker one single jot, had not so much as
+altered one angle of his vest. Still, for all this immutableness,
+was there some lack of common consistency about worthy Captain
+Peleg. Though refusing, from conscientious scruples, to bear arms
+against land invaders, yet himself had illimitably invaded the
+Atlantic and Pacific; and though a sworn foe to human bloodshed, yet
+had he in his straight-bodied coat, spilled tuns upon tuns of
+leviathan gore. How now in the contemplative evening of his days,
+the pious Bildad reconciled these things in the reminiscence, I do
+not know; but it did not seem to concern him much, and very probably
+he had long since come to the sage and sensible conclusion that a
+man's religion is one thing, and this practical world quite another.
+This world pays dividends. Rising from a little cabin-boy in short
+clothes of the drabbest drab, to a harpooneer in a broad shad-bellied
+waistcoat; from that becoming boat-header, chief-mate, and captain,
+and finally a ship owner; Bildad, as I hinted before, had concluded
+his adventurous career by wholly retiring from active life at the
+goodly age of sixty, and dedicating his remaining days to the quiet
+receiving of his well-earned income.
+
+Now, Bildad, I am sorry to say, had the reputation of being an
+incorrigible old hunks, and in his sea-going days, a bitter, hard
+task-master. They told me in Nantucket, though it certainly seems a
+curious story, that when he sailed the old Categut whaleman, his
+crew, upon arriving home, were mostly all carried ashore to the
+hospital, sore exhausted and worn out. For a pious man, especially
+for a Quaker, he was certainly rather hard-hearted, to say the
+least. He never used to swear, though, at his men, they said; but
+somehow he got an inordinate quantity of cruel, unmitigated hard work
+out of them. When Bildad was a chief-mate, to have his drab-coloured
+eye intently looking at you, made you feel completely nervous, till
+you could clutch something--a hammer or a marling-spike, and go to
+work like mad, at something or other, never mind what. Indolence and
+idleness perished before him. His own person was the exact
+embodiment of his utilitarian character. On his long, gaunt body, he
+carried no spare flesh, no superfluous beard, his chin having a soft,
+economical nap to it, like the worn nap of his broad-brimmed hat.
+
+Such, then, was the person that I saw seated on the transom when I
+followed Captain Peleg down into the cabin. The space between the
+decks was small; and there, bolt-upright, sat old Bildad, who always
+sat so, and never leaned, and this to save his coat tails. His
+broad-brim was placed beside him; his legs were stiffly crossed; his
+drab vesture was buttoned up to his chin; and spectacles on nose, he
+seemed absorbed in reading from a ponderous volume.
+
+"Bildad," cried Captain Peleg, "at it again, Bildad, eh? Ye have
+been studying those Scriptures, now, for the last thirty years, to my
+certain knowledge. How far ye got, Bildad?"
+
+As if long habituated to such profane talk from his old shipmate,
+Bildad, without noticing his present irreverence, quietly looked up,
+and seeing me, glanced again inquiringly towards Peleg.
+
+"He says he's our man, Bildad," said Peleg, "he wants to ship."
+
+"Dost thee?" said Bildad, in a hollow tone, and turning round to me.
+
+"I dost," said I unconsciously, he was so intense a Quaker.
+
+"What do ye think of him, Bildad?" said Peleg.
+
+"He'll do," said Bildad, eyeing me, and then went on spelling away at
+his book in a mumbling tone quite audible.
+
+I thought him the queerest old Quaker I ever saw, especially as
+Peleg, his friend and old shipmate, seemed such a blusterer. But I
+said nothing, only looking round me sharply. Peleg now threw open a
+chest, and drawing forth the ship's articles, placed pen and ink
+before him, and seated himself at a little table. I began to think
+it was high time to settle with myself at what terms I would be
+willing to engage for the voyage. I was already aware that in the
+whaling business they paid no wages; but all hands, including the
+captain, received certain shares of the profits called lays, and that
+these lays were proportioned to the degree of importance pertaining
+to the respective duties of the ship's company. I was also aware
+that being a green hand at whaling, my own lay would not be very
+large; but considering that I was used to the sea, could steer a
+ship, splice a rope, and all that, I made no doubt that from all I
+had heard I should be offered at least the 275th lay--that is, the
+275th part of the clear net proceeds of the voyage, whatever that
+might eventually amount to. And though the 275th lay was what they
+call a rather LONG LAY, yet it was better than nothing; and if we had
+a lucky voyage, might pretty nearly pay for the clothing I would wear
+out on it, not to speak of my three years' beef and board, for which
+I would not have to pay one stiver.
+
+It might be thought that this was a poor way to accumulate a princely
+fortune--and so it was, a very poor way indeed. But I am one of
+those that never take on about princely fortunes, and am quite
+content if the world is ready to board and lodge me, while I am
+putting up at this grim sign of the Thunder Cloud. Upon the whole, I
+thought that the 275th lay would be about the fair thing, but would not
+have been surprised had I been offered the 200th, considering I was
+of a broad-shouldered make.
+
+But one thing, nevertheless, that made me a little distrustful about
+receiving a generous share of the profits was this: Ashore, I had
+heard something of both Captain Peleg and his unaccountable old crony
+Bildad; how that they being the principal proprietors of the Pequod,
+therefore the other and more inconsiderable and scattered owners,
+left nearly the whole management of the ship's affairs to these two.
+And I did not know but what the stingy old Bildad might have a mighty
+deal to say about shipping hands, especially as I now found him on
+board the Pequod, quite at home there in the cabin, and reading his
+Bible as if at his own fireside. Now while Peleg was vainly trying
+to mend a pen with his jack-knife, old Bildad, to my no small
+surprise, considering that he was such an interested party in these
+proceedings; Bildad never heeded us, but went on mumbling to himself
+out of his book, "LAY not up for yourselves treasures upon earth,
+where moth--"
+
+"Well, Captain Bildad," interrupted Peleg, "what d'ye say, what lay
+shall we give this young man?"
+
+"Thou knowest best," was the sepulchral reply, "the seven hundred and
+seventy-seventh wouldn't be too much, would it?--'where moth and rust
+do corrupt, but LAY--'"
+
+LAY, indeed, thought I, and such a lay! the seven hundred and
+seventy-seventh! Well, old Bildad, you are determined that I, for
+one, shall not LAY up many LAYS here below, where moth and rust do
+corrupt. It was an exceedingly LONG LAY that, indeed; and though
+from the magnitude of the figure it might at first deceive a
+landsman, yet the slightest consideration will show that though seven
+hundred and seventy-seven is a pretty large number, yet, when you
+come to make a TEENTH of it, you will then see, I say, that the seven
+hundred and seventy-seventh part of a farthing is a good deal less
+than seven hundred and seventy-seven gold doubloons; and so I thought
+at the time.
+
+"Why, blast your eyes, Bildad," cried Peleg, "thou dost not want to
+swindle this young man! he must have more than that."
+
+"Seven hundred and seventy-seventh," again said Bildad, without
+lifting his eyes; and then went on mumbling--"for where your treasure
+is, there will your heart be also."
+
+"I am going to put him down for the three hundredth," said Peleg, "do
+ye hear that, Bildad! The three hundredth lay, I say."
+
+Bildad laid down his book, and turning solemnly towards him said,
+"Captain Peleg, thou hast a generous heart; but thou must consider
+the duty thou owest to the other owners of this ship--widows and
+orphans, many of them--and that if we too abundantly reward the
+labors of this young man, we may be taking the bread from those
+widows and those orphans. The seven hundred and seventy-seventh lay,
+Captain Peleg."
+
+"Thou Bildad!" roared Peleg, starting up and clattering about the
+cabin. "Blast ye, Captain Bildad, if I had followed thy advice in
+these matters, I would afore now had a conscience to lug about that
+would be heavy enough to founder the largest ship that ever sailed
+round Cape Horn."
+
+"Captain Peleg," said Bildad steadily, "thy conscience may be drawing
+ten inches of water, or ten fathoms, I can't tell; but as thou art
+still an impenitent man, Captain Peleg, I greatly fear lest thy
+conscience be but a leaky one; and will in the end sink thee
+foundering down to the fiery pit, Captain Peleg."
+
+"Fiery pit! fiery pit! ye insult me, man; past all natural bearing,
+ye insult me. It's an all-fired outrage to tell any human creature
+that he's bound to hell. Flukes and flames! Bildad, say that again
+to me, and start my soul-bolts, but I'll--I'll--yes, I'll swallow a
+live goat with all his hair and horns on. Out of the cabin, ye
+canting, drab-coloured son of a wooden gun--a straight wake with ye!"
+
+As he thundered out this he made a rush at Bildad, but with a
+marvellous oblique, sliding celerity, Bildad for that time eluded
+him.
+
+Alarmed at this terrible outburst between the two principal and
+responsible owners of the ship, and feeling half a mind to give up
+all idea of sailing in a vessel so questionably owned and temporarily
+commanded, I stepped aside from the door to give egress to Bildad,
+who, I made no doubt, was all eagerness to vanish from before the
+awakened wrath of Peleg. But to my astonishment, he sat down again
+on the transom very quietly, and seemed to have not the slightest
+intention of withdrawing. He seemed quite used to impenitent Peleg
+and his ways. As for Peleg, after letting off his rage as he had,
+there seemed no more left in him, and he, too, sat down like a lamb,
+though he twitched a little as if still nervously agitated. "Whew!"
+he whistled at last--"the squall's gone off to leeward, I think.
+Bildad, thou used to be good at sharpening a lance, mend that pen,
+will ye. My jack-knife here needs the grindstone. That's he; thank
+ye, Bildad. Now then, my young man, Ishmael's thy name, didn't ye
+say? Well then, down ye go here, Ishmael, for the three hundredth
+lay."
+
+"Captain Peleg," said I, "I have a friend with me who wants to ship
+too--shall I bring him down to-morrow?"
+
+"To be sure," said Peleg. "Fetch him along, and we'll look at him."
+
+"What lay does he want?" groaned Bildad, glancing up from the book
+in which he had again been burying himself.
+
+"Oh! never thee mind about that, Bildad," said Peleg. "Has he ever
+whaled it any?" turning to me.
+
+"Killed more whales than I can count, Captain Peleg."
+
+"Well, bring him along then."
+
+And, after signing the papers, off I went; nothing doubting but that
+I had done a good morning's work, and that the Pequod was the
+identical ship that Yojo had provided to carry Queequeg and me round
+the Cape.
+
+But I had not proceeded far, when I began to bethink me that the
+Captain with whom I was to sail yet remained unseen by me; though,
+indeed, in many cases, a whale-ship will be completely fitted out,
+and receive all her crew on board, ere the captain makes himself
+visible by arriving to take command; for sometimes these voyages are
+so prolonged, and the shore intervals at home so exceedingly brief,
+that if the captain have a family, or any absorbing concernment of
+that sort, he does not trouble himself much about his ship in port,
+but leaves her to the owners till all is ready for sea. However, it
+is always as well to have a look at him before irrevocably committing
+yourself into his hands. Turning back I accosted Captain Peleg,
+inquiring where Captain Ahab was to be found.
+
+"And what dost thou want of Captain Ahab? It's all right enough;
+thou art shipped."
+
+"Yes, but I should like to see him."
+
+"But I don't think thou wilt be able to at present. I don't know
+exactly what's the matter with him; but he keeps close inside the
+house; a sort of sick, and yet he don't look so. In fact, he ain't
+sick; but no, he isn't well either. Any how, young man, he won't
+always see me, so I don't suppose he will thee. He's a queer man,
+Captain Ahab--so some think--but a good one. Oh, thou'lt like him
+well enough; no fear, no fear. He's a grand, ungodly, god-like man,
+Captain Ahab; doesn't speak much; but, when he does speak, then you
+may well listen. Mark ye, be forewarned; Ahab's above the common;
+Ahab's been in colleges, as well as 'mong the cannibals; been used to
+deeper wonders than the waves; fixed his fiery lance in mightier,
+stranger foes than whales. His lance! aye, the keenest and the surest
+that out of all our isle! Oh! he ain't Captain Bildad; no, and he
+ain't Captain Peleg; HE'S AHAB, boy; and Ahab of old, thou knowest,
+was a crowned king!"
+
+"And a very vile one. When that wicked king was slain, the dogs, did
+they not lick his blood?"
+
+"Come hither to me--hither, hither," said Peleg, with a significance
+in his eye that almost startled me. "Look ye, lad; never say that on
+board the Pequod. Never say it anywhere. Captain Ahab did not name
+himself. 'Twas a foolish, ignorant whim of his crazy, widowed
+mother, who died when he was only a twelvemonth old. And yet the old
+squaw Tistig, at Gayhead, said that the name would somehow prove
+prophetic. And, perhaps, other fools like her may tell thee the
+same. I wish to warn thee. It's a lie. I know Captain Ahab well;
+I've sailed with him as mate years ago; I know what he is--a good
+man--not a pious, good man, like Bildad, but a swearing good
+man--something like me--only there's a good deal more of him. Aye,
+aye, I know that he was never very jolly; and I know that on the
+passage home, he was a little out of his mind for a spell; but it was
+the sharp shooting pains in his bleeding stump that brought that
+about, as any one might see. I know, too, that ever since he lost
+his leg last voyage by that accursed whale, he's been a kind of
+moody--desperate moody, and savage sometimes; but that will all pass
+off. And once for all, let me tell thee and assure thee, young man,
+it's better to sail with a moody good captain than a laughing bad
+one. So good-bye to thee--and wrong not Captain Ahab, because he
+happens to have a wicked name. Besides, my boy, he has a wife--not
+three voyages wedded--a sweet, resigned girl. Think of that; by that
+sweet girl that old man has a child: hold ye then there can be any
+utter, hopeless harm in Ahab? No, no, my lad; stricken, blasted, if
+he be, Ahab has his humanities!"
+
+As I walked away, I was full of thoughtfulness; what had been
+incidentally revealed to me of Captain Ahab, filled me with a certain
+wild vagueness of painfulness concerning him. And somehow, at the
+time, I felt a sympathy and a sorrow for him, but for I don't know
+what, unless it was the cruel loss of his leg. And yet I also felt a
+strange awe of him; but that sort of awe, which I cannot at all
+describe, was not exactly awe; I do not know what it was. But I felt
+it; and it did not disincline me towards him; though I felt
+impatience at what seemed like mystery in him, so imperfectly as he
+was known to me then. However, my thoughts were at length carried in
+other directions, so that for the present dark Ahab slipped my mind.
+
+
+
+CHAPTER 17
+
+The Ramadan.
+
+
+As Queequeg's Ramadan, or Fasting and Humiliation, was to continue
+all day, I did not choose to disturb him till towards night-fall; for
+I cherish the greatest respect towards everybody's religious
+obligations, never mind how comical, and could not find it in my
+heart to undervalue even a congregation of ants worshipping a
+toad-stool; or those other creatures in certain parts of our earth,
+who with a degree of footmanism quite unprecedented in other planets,
+bow down before the torso of a deceased landed proprietor merely on
+account of the inordinate possessions yet owned and rented in his
+name.
+
+I say, we good Presbyterian Christians should be charitable in these
+things, and not fancy ourselves so vastly superior to other mortals,
+pagans and what not, because of their half-crazy conceits on these
+subjects. There was Queequeg, now, certainly entertaining the most
+absurd notions about Yojo and his Ramadan;--but what of that?
+Queequeg thought he knew what he was about, I suppose; he seemed to
+be content; and there let him rest. All our arguing with him would
+not avail; let him be, I say: and Heaven have mercy on us
+all--Presbyterians and Pagans alike--for we are all somehow
+dreadfully cracked about the head, and sadly need mending.
+
+Towards evening, when I felt assured that all his performances and
+rituals must be over, I went up to his room and knocked at the door;
+but no answer. I tried to open it, but it was fastened inside.
+"Queequeg," said I softly through the key-hole:--all silent. "I say,
+Queequeg! why don't you speak? It's I--Ishmael." But all remained
+still as before. I began to grow alarmed. I had allowed him such
+abundant time; I thought he might have had an apoplectic fit. I
+looked through the key-hole; but the door opening into an odd corner
+of the room, the key-hole prospect was but a crooked and sinister
+one. I could only see part of the foot-board of the bed and a line
+of the wall, but nothing more. I was surprised to behold resting
+against the wall the wooden shaft of Queequeg's harpoon, which the
+landlady the evening previous had taken from him, before our mounting
+to the chamber. That's strange, thought I; but at any rate, since
+the harpoon stands yonder, and he seldom or never goes abroad without
+it, therefore he must be inside here, and no possible mistake.
+
+"Queequeg!--Queequeg!"--all still. Something must have happened.
+Apoplexy! I tried to burst open the door; but it stubbornly
+resisted. Running down stairs, I quickly stated my suspicions to the
+first person I met--the chamber-maid. "La! la!" she cried, "I
+thought something must be the matter. I went to make the bed after
+breakfast, and the door was locked; and not a mouse to be heard; and
+it's been just so silent ever since. But I thought, may be, you had
+both gone off and locked your baggage in for safe keeping. La! la,
+ma'am!--Mistress! murder! Mrs. Hussey! apoplexy!"--and with these
+cries, she ran towards the kitchen, I following.
+
+Mrs. Hussey soon appeared, with a mustard-pot in one hand and a
+vinegar-cruet in the other, having just broken away from the
+occupation of attending to the castors, and scolding her little black
+boy meantime.
+
+"Wood-house!" cried I, "which way to it? Run for God's sake, and
+fetch something to pry open the door--the axe!--the axe! he's had a
+stroke; depend upon it!"--and so saying I was unmethodically rushing
+up stairs again empty-handed, when Mrs. Hussey interposed the
+mustard-pot and vinegar-cruet, and the entire castor of her
+countenance.
+
+"What's the matter with you, young man?"
+
+"Get the axe! For God's sake, run for the doctor, some one, while I
+pry it open!"
+
+"Look here," said the landlady, quickly putting down the
+vinegar-cruet, so as to have one hand free; "look here; are you
+talking about prying open any of my doors?"--and with that she seized
+my arm. "What's the matter with you? What's the matter with you,
+shipmate?"
+
+In as calm, but rapid a manner as possible, I gave her to understand
+the whole case. Unconsciously clapping the vinegar-cruet to one side
+of her nose, she ruminated for an instant; then exclaimed--"No! I
+haven't seen it since I put it there." Running to a little closet
+under the landing of the stairs, she glanced in, and returning, told
+me that Queequeg's harpoon was missing. "He's killed himself," she
+cried. "It's unfort'nate Stiggs done over again there goes another
+counterpane--God pity his poor mother!--it will be the ruin of my
+house. Has the poor lad a sister? Where's that girl?--there, Betty,
+go to Snarles the Painter, and tell him to paint me a sign, with--"no
+suicides permitted here, and no smoking in the parlor;"--might as
+well kill both birds at once. Kill? The Lord be merciful to his
+ghost! What's that noise there? You, young man, avast there!"
+
+And running up after me, she caught me as I was again trying to force
+open the door.
+
+"I don't allow it; I won't have my premises spoiled. Go for the
+locksmith, there's one about a mile from here. But avast!" putting
+her hand in her side-pocket, "here's a key that'll fit, I guess;
+let's see." And with that, she turned it in the lock; but, alas!
+Queequeg's supplemental bolt remained unwithdrawn within.
+
+"Have to burst it open," said I, and was running down the entry a
+little, for a good start, when the landlady caught at me, again
+vowing I should not break down her premises; but I tore from her, and
+with a sudden bodily rush dashed myself full against the mark.
+
+With a prodigious noise the door flew open, and the knob slamming
+against the wall, sent the plaster to the ceiling; and there, good
+heavens! there sat Queequeg, altogether cool and self-collected;
+right in the middle of the room; squatting on his hams, and holding
+Yojo on top of his head. He looked neither one way nor the other
+way, but sat like a carved image with scarce a sign of active life.
+
+"Queequeg," said I, going up to him, "Queequeg, what's the matter
+with you?"
+
+"He hain't been a sittin' so all day, has he?" said the landlady.
+
+But all we said, not a word could we drag out of him; I almost felt
+like pushing him over, so as to change his position, for it was
+almost intolerable, it seemed so painfully and unnaturally
+constrained; especially, as in all probability he had been sitting so
+for upwards of eight or ten hours, going too without his regular
+meals.
+
+"Mrs. Hussey," said I, "he's ALIVE at all events; so leave us, if you
+please, and I will see to this strange affair myself."
+
+Closing the door upon the landlady, I endeavored to prevail upon
+Queequeg to take a chair; but in vain. There he sat; and all he
+could do--for all my polite arts and blandishments--he would not move
+a peg, nor say a single word, nor even look at me, nor notice my
+presence in the slightest way.
+
+I wonder, thought I, if this can possibly be a part of his Ramadan;
+do they fast on their hams that way in his native island. It must be
+so; yes, it's part of his creed, I suppose; well, then, let him
+rest; he'll get up sooner or later, no doubt. It can't last for
+ever, thank God, and his Ramadan only comes once a year; and I don't
+believe it's very punctual then.
+
+I went down to supper. After sitting a long time listening to the
+long stories of some sailors who had just come from a plum-pudding
+voyage, as they called it (that is, a short whaling-voyage in a
+schooner or brig, confined to the north of the line, in the Atlantic
+Ocean only); after listening to these plum-puddingers till nearly
+eleven o'clock, I went up stairs to go to bed, feeling quite sure by
+this time Queequeg must certainly have brought his Ramadan to a
+termination. But no; there he was just where I had left him; he had
+not stirred an inch. I began to grow vexed with him; it seemed so
+downright senseless and insane to be sitting there all day and half
+the night on his hams in a cold room, holding a piece of wood on his
+head.
+
+"For heaven's sake, Queequeg, get up and shake yourself; get up and
+have some supper. You'll starve; you'll kill yourself, Queequeg."
+But not a word did he reply.
+
+Despairing of him, therefore, I determined to go to bed and to sleep;
+and no doubt, before a great while, he would follow me. But previous
+to turning in, I took my heavy bearskin jacket, and threw it over
+him, as it promised to be a very cold night; and he had nothing but
+his ordinary round jacket on. For some time, do all I would, I could
+not get into the faintest doze. I had blown out the candle; and the
+mere thought of Queequeg--not four feet off--sitting there in that
+uneasy position, stark alone in the cold and dark; this made me
+really wretched. Think of it; sleeping all night in the same room
+with a wide awake pagan on his hams in this dreary, unaccountable
+Ramadan!
+
+But somehow I dropped off at last, and knew nothing more till break
+of day; when, looking over the bedside, there squatted Queequeg, as
+if he had been screwed down to the floor. But as soon as the first
+glimpse of sun entered the window, up he got, with stiff and grating
+joints, but with a cheerful look; limped towards me where I lay;
+pressed his forehead again against mine; and said his Ramadan was
+over.
+
+Now, as I before hinted, I have no objection to any person's
+religion, be it what it may, so long as that person does not kill or
+insult any other person, because that other person don't believe it
+also. But when a man's religion becomes really frantic; when it is a
+positive torment to him; and, in fine, makes this earth of ours an
+uncomfortable inn to lodge in; then I think it high time to take that
+individual aside and argue the point with him.
+
+And just so I now did with Queequeg. "Queequeg," said I, "get into
+bed now, and lie and listen to me." I then went on, beginning with
+the rise and progress of the primitive religions, and coming down to
+the various religions of the present time, during which time I
+labored to show Queequeg that all these Lents, Ramadans, and
+prolonged ham-squattings in cold, cheerless rooms were stark
+nonsense; bad for the health; useless for the soul; opposed, in
+short, to the obvious laws of Hygiene and common sense. I told him,
+too, that he being in other things such an extremely sensible and
+sagacious savage, it pained me, very badly pained me, to see him now
+so deplorably foolish about this ridiculous Ramadan of his. Besides,
+argued I, fasting makes the body cave in; hence the spirit caves in;
+and all thoughts born of a fast must necessarily be half-starved.
+This is the reason why most dyspeptic religionists cherish such
+melancholy notions about their hereafters. In one word, Queequeg,
+said I, rather digressively; hell is an idea first born on an
+undigested apple-dumpling; and since then perpetuated through the
+hereditary dyspepsias nurtured by Ramadans.
+
+I then asked Queequeg whether he himself was ever troubled with
+dyspepsia; expressing the idea very plainly, so that he could take it
+in. He said no; only upon one memorable occasion. It was after a
+great feast given by his father the king, on the gaining of a great
+battle wherein fifty of the enemy had been killed by about two
+o'clock in the afternoon, and all cooked and eaten that very evening.
+
+"No more, Queequeg," said I, shuddering; "that will do;" for I knew
+the inferences without his further hinting them. I had seen a sailor
+who had visited that very island, and he told me that it was the
+custom, when a great battle had been gained there, to barbecue all
+the slain in the yard or garden of the victor; and then, one by one,
+they were placed in great wooden trenchers, and garnished round like
+a pilau, with breadfruit and cocoanuts; and with some parsley in
+their mouths, were sent round with the victor's compliments to all
+his friends, just as though these presents were so many Christmas
+turkeys.
+
+After all, I do not think that my remarks about religion made much
+impression upon Queequeg. Because, in the first place, he somehow
+seemed dull of hearing on that important subject, unless considered
+from his own point of view; and, in the second place, he did not more
+than one third understand me, couch my ideas simply as I would; and,
+finally, he no doubt thought he knew a good deal more about the true
+religion than I did. He looked at me with a sort of condescending
+concern and compassion, as though he thought it a great pity that
+such a sensible young man should be so hopelessly lost to evangelical
+pagan piety.
+
+At last we rose and dressed; and Queequeg, taking a prodigiously
+hearty breakfast of chowders of all sorts, so that the landlady
+should not make much profit by reason of his Ramadan, we sallied out
+to board the Pequod, sauntering along, and picking our teeth with
+halibut bones.
+
+
+
+CHAPTER 18
+
+His Mark.
+
+
+As we were walking down the end of the wharf towards the ship,
+Queequeg carrying his harpoon, Captain Peleg in his gruff voice
+loudly hailed us from his wigwam, saying he had not suspected my
+friend was a cannibal, and furthermore announcing that he let no
+cannibals on board that craft, unless they previously produced their
+papers.
+
+"What do you mean by that, Captain Peleg?" said I, now jumping on the
+bulwarks, and leaving my comrade standing on the wharf.
+
+"I mean," he replied, "he must show his papers."
+
+"Yes," said Captain Bildad in his hollow voice, sticking his head
+from behind Peleg's, out of the wigwam. "He must show that he's
+converted. Son of darkness," he added, turning to Queequeg, "art
+thou at present in communion with any Christian church?"
+
+"Why," said I, "he's a member of the first Congregational Church."
+Here be it said, that many tattooed savages sailing in Nantucket
+ships at last come to be converted into the churches.
+
+"First Congregational Church," cried Bildad, "what! that worships in
+Deacon Deuteronomy Coleman's meeting-house?" and so saying, taking
+out his spectacles, he rubbed them with his great yellow bandana
+handkerchief, and putting them on very carefully, came out of the
+wigwam, and leaning stiffly over the bulwarks, took a good long look
+at Queequeg.
+
+"How long hath he been a member?" he then said, turning to me; "not
+very long, I rather guess, young man."
+
+"No," said Peleg, "and he hasn't been baptized right either, or it
+would have washed some of that devil's blue off his face."
+
+"Do tell, now," cried Bildad, "is this Philistine a regular member of
+Deacon Deuteronomy's meeting? I never saw him going there, and I
+pass it every Lord's day."
+
+"I don't know anything about Deacon Deuteronomy or his meeting," said
+I; "all I know is, that Queequeg here is a born member of the First
+Congregational Church. He is a deacon himself, Queequeg is."
+
+"Young man," said Bildad sternly, "thou art skylarking with
+me--explain thyself, thou young Hittite. What church dost thee mean?
+answer me."
+
+Finding myself thus hard pushed, I replied. "I mean, sir, the same
+ancient Catholic Church to which you and I, and Captain Peleg there,
+and Queequeg here, and all of us, and every mother's son and soul of
+us belong; the great and everlasting First Congregation of this whole
+worshipping world; we all belong to that; only some of us cherish
+some queer crotchets no ways touching the grand belief; in THAT we
+all join hands."
+
+"Splice, thou mean'st SPLICE hands," cried Peleg, drawing nearer.
+"Young man, you'd better ship for a missionary, instead of a
+fore-mast hand; I never heard a better sermon. Deacon
+Deuteronomy--why Father Mapple himself couldn't beat it, and he's
+reckoned something. Come aboard, come aboard; never mind about the
+papers. I say, tell Quohog there--what's that you call him? tell
+Quohog to step along. By the great anchor, what a harpoon he's got
+there! looks like good stuff that; and he handles it about right. I
+say, Quohog, or whatever your name is, did you ever stand in the head
+of a whale-boat? did you ever strike a fish?"
+
+Without saying a word, Queequeg, in his wild sort of way, jumped upon
+the bulwarks, from thence into the bows of one of the whale-boats
+hanging to the side; and then bracing his left knee, and poising his
+harpoon, cried out in some such way as this:--
+
+"Cap'ain, you see him small drop tar on water dere? You see him?
+well, spose him one whale eye, well, den!" and taking sharp aim at
+it, he darted the iron right over old Bildad's broad brim, clean
+across the ship's decks, and struck the glistening tar spot out of
+sight.
+
+"Now," said Queequeg, quietly hauling in the line, "spos-ee him
+whale-e eye; why, dad whale dead."
+
+"Quick, Bildad," said Peleg, his partner, who, aghast at the close
+vicinity of the flying harpoon, had retreated towards the cabin
+gangway. "Quick, I say, you Bildad, and get the ship's papers. We
+must have Hedgehog there, I mean Quohog, in one of our boats. Look
+ye, Quohog, we'll give ye the ninetieth lay, and that's more than
+ever was given a harpooneer yet out of Nantucket."
+
+So down we went into the cabin, and to my great joy Queequeg was soon
+enrolled among the same ship's company to which I myself belonged.
+
+When all preliminaries were over and Peleg had got everything ready
+for signing, he turned to me and said, "I guess, Quohog there don't
+know how to write, does he? I say, Quohog, blast ye! dost thou sign
+thy name or make thy mark?
+
+But at this question, Queequeg, who had twice or thrice before taken
+part in similar ceremonies, looked no ways abashed; but taking the
+offered pen, copied upon the paper, in the proper place, an exact
+counterpart of a queer round figure which was tattooed upon his arm;
+so that through Captain Peleg's obstinate mistake touching his
+appellative, it stood something like this:--
+
+Quohog.
+his X mark.
+
+Meanwhile Captain Bildad sat earnestly and steadfastly eyeing
+Queequeg, and at last rising solemnly and fumbling in the huge
+pockets of his broad-skirted drab coat, took out a bundle of tracts,
+and selecting one entitled "The Latter Day Coming; or No Time to
+Lose," placed it in Queequeg's hands, and then grasping them and the
+book with both his, looked earnestly into his eyes, and said, "Son of
+darkness, I must do my duty by thee; I am part owner of this ship,
+and feel concerned for the souls of all its crew; if thou still
+clingest to thy Pagan ways, which I sadly fear, I beseech thee,
+remain not for aye a Belial bondsman. Spurn the idol Bell, and the
+hideous dragon; turn from the wrath to come; mind thine eye, I say;
+oh! goodness gracious! steer clear of the fiery pit!"
+
+Something of the salt sea yet lingered in old Bildad's language,
+heterogeneously mixed with Scriptural and domestic phrases.
+
+"Avast there, avast there, Bildad, avast now spoiling our
+harpooneer," Peleg. "Pious harpooneers never make good voyagers--it
+takes the shark out of 'em; no harpooneer is worth a straw who aint
+pretty sharkish. There was young Nat Swaine, once the bravest
+boat-header out of all Nantucket and the Vineyard; he joined the
+meeting, and never came to good. He got so frightened about his
+plaguy soul, that he shrinked and sheered away from whales, for fear
+of after-claps, in case he got stove and went to Davy Jones."
+
+"Peleg! Peleg!" said Bildad, lifting his eyes and hands, "thou
+thyself, as I myself, hast seen many a perilous time; thou knowest,
+Peleg, what it is to have the fear of death; how, then, can'st thou
+prate in this ungodly guise. Thou beliest thine own heart, Peleg.
+Tell me, when this same Pequod here had her three masts overboard in
+that typhoon on Japan, that same voyage when thou went mate with
+Captain Ahab, did'st thou not think of Death and the Judgment then?"
+
+"Hear him, hear him now," cried Peleg, marching across the cabin, and
+thrusting his hands far down into his pockets,--"hear him, all of ye.
+Think of that! When every moment we thought the ship would sink!
+Death and the Judgment then? What? With all three masts making such
+an everlasting thundering against the side; and every sea breaking
+over us, fore and aft. Think of Death and the Judgment then? No!
+no time to think about Death then. Life was what Captain Ahab and I
+was thinking of; and how to save all hands--how to rig
+jury-masts--how to get into the nearest port; that was what I was
+thinking of."
+
+Bildad said no more, but buttoning up his coat, stalked on deck,
+where we followed him. There he stood, very quietly overlooking some
+sailmakers who were mending a top-sail in the waist. Now and then he
+stooped to pick up a patch, or save an end of tarred twine, which
+otherwise might have been wasted.
+
+
+
+CHAPTER 19
+
+The Prophet.
+
+
+"Shipmates, have ye shipped in that ship?"
+
+Queequeg and I had just left the Pequod, and were sauntering away from
+the water, for the moment each occupied with his own thoughts, when
+the above words were put to us by a stranger, who, pausing before us,
+levelled his massive forefinger at the vessel in question. He was
+but shabbily apparelled in faded jacket and patched trowsers; a rag
+of a black handkerchief investing his neck. A confluent small-pox
+had in all directions flowed over his face, and left it like the
+complicated ribbed bed of a torrent, when the rushing waters have
+been dried up.
+
+"Have ye shipped in her?" he repeated.
+
+"You mean the ship Pequod, I suppose," said I, trying to gain a
+little more time for an uninterrupted look at him.
+
+"Aye, the Pequod--that ship there," he said, drawing back his whole
+arm, and then rapidly shoving it straight out from him, with the
+fixed bayonet of his pointed finger darted full at the object.
+
+"Yes," said I, "we have just signed the articles."
+
+"Anything down there about your souls?"
+
+"About what?"
+
+"Oh, perhaps you hav'n't got any," he said quickly. "No matter
+though, I know many chaps that hav'n't got any,--good luck to 'em;
+and they are all the better off for it. A soul's a sort of a fifth
+wheel to a wagon."
+
+"What are you jabbering about, shipmate?" said I.
+
+"HE'S got enough, though, to make up for all deficiencies of that
+sort in other chaps," abruptly said the stranger, placing a nervous
+emphasis upon the word HE.
+
+"Queequeg," said I, "let's go; this fellow has broken loose from
+somewhere; he's talking about something and somebody we don't know."
+
+"Stop!" cried the stranger. "Ye said true--ye hav'n't seen Old
+Thunder yet, have ye?"
+
+"Who's Old Thunder?" said I, again riveted with the insane
+earnestness of his manner.
+
+"Captain Ahab."
+
+"What! the captain of our ship, the Pequod?"
+
+"Aye, among some of us old sailor chaps, he goes by that name. Ye
+hav'n't seen him yet, have ye?"
+
+"No, we hav'n't. He's sick they say, but is getting better, and will
+be all right again before long."
+
+"All right again before long!" laughed the stranger, with a solemnly
+derisive sort of laugh. "Look ye; when Captain Ahab is all right,
+then this left arm of mine will be all right; not before."
+
+"What do you know about him?"
+
+"What did they TELL you about him? Say that!"
+
+"They didn't tell much of anything about him; only I've heard that
+he's a good whale-hunter, and a good captain to his crew."
+
+"That's true, that's true--yes, both true enough. But you must jump
+when he gives an order. Step and growl; growl and go--that's the
+word with Captain Ahab. But nothing about that thing that happened
+to him off Cape Horn, long ago, when he lay like dead for three days
+and nights; nothing about that deadly skrimmage with the Spaniard
+afore the altar in Santa?--heard nothing about that, eh? Nothing
+about the silver calabash he spat into? And nothing about his losing
+his leg last voyage, according to the prophecy. Didn't ye hear a
+word about them matters and something more, eh? No, I don't think ye
+did; how could ye? Who knows it? Not all Nantucket, I guess. But
+hows'ever, mayhap, ye've heard tell about the leg, and how he lost
+it; aye, ye have heard of that, I dare say. Oh yes, THAT every one
+knows a'most--I mean they know he's only one leg; and that a
+parmacetti took the other off."
+
+"My friend," said I, "what all this gibberish of yours is about, I
+don't know, and I don't much care; for it seems to me that you must
+be a little damaged in the head. But if you are speaking of Captain
+Ahab, of that ship there, the Pequod, then let me tell you, that I
+know all about the loss of his leg."
+
+"ALL about it, eh--sure you do?--all?"
+
+"Pretty sure."
+
+With finger pointed and eye levelled at the Pequod, the beggar-like
+stranger stood a moment, as if in a troubled reverie; then starting a
+little, turned and said:--"Ye've shipped, have ye? Names down on the
+papers? Well, well, what's signed, is signed; and what's to be, will
+be; and then again, perhaps it won't be, after all. Anyhow, it's
+all fixed and arranged a'ready; and some sailors or other must go
+with him, I suppose; as well these as any other men, God pity 'em!
+Morning to ye, shipmates, morning; the ineffable heavens bless ye;
+I'm sorry I stopped ye."
+
+"Look here, friend," said I, "if you have anything important to tell
+us, out with it; but if you are only trying to bamboozle us, you are
+mistaken in your game; that's all I have to say."
+
+"And it's said very well, and I like to hear a chap talk up that way;
+you are just the man for him--the likes of ye. Morning to ye,
+shipmates, morning! Oh! when ye get there, tell 'em I've concluded
+not to make one of 'em."
+
+"Ah, my dear fellow, you can't fool us that way--you can't fool us.
+It is the easiest thing in the world for a man to look as if he had a
+great secret in him."
+
+"Morning to ye, shipmates, morning."
+
+"Morning it is," said I. "Come along, Queequeg, let's leave this
+crazy man. But stop, tell me your name, will you?"
+
+"Elijah."
+
+Elijah! thought I, and we walked away, both commenting, after each
+other's fashion, upon this ragged old sailor; and agreed that he was
+nothing but a humbug, trying to be a bugbear. But we had not gone
+perhaps above a hundred yards, when chancing to turn a corner, and
+looking back as I did so, who should be seen but Elijah following us,
+though at a distance. Somehow, the sight of him struck me so, that I
+said nothing to Queequeg of his being behind, but passed on with my
+comrade, anxious to see whether the stranger would turn the same
+corner that we did. He did; and then it seemed to me that he was
+dogging us, but with what intent I could not for the life of me
+imagine. This circumstance, coupled with his ambiguous,
+half-hinting, half-revealing, shrouded sort of talk, now begat in me
+all kinds of vague wonderments and half-apprehensions, and all
+connected with the Pequod; and Captain Ahab; and the leg he had lost;
+and the Cape Horn fit; and the silver calabash; and what Captain
+Peleg had said of him, when I left the ship the day previous; and the
+prediction of the squaw Tistig; and the voyage we had bound ourselves
+to sail; and a hundred other shadowy things.
+
+I was resolved to satisfy myself whether this ragged Elijah was
+really dogging us or not, and with that intent crossed the way with
+Queequeg, and on that side of it retraced our steps. But Elijah
+passed on, without seeming to notice us. This relieved me; and once
+more, and finally as it seemed to me, I pronounced him in my heart, a
+humbug.
+
+
+
+CHAPTER 20
+
+All Astir.
+
+
+A day or two passed, and there was great activity aboard the Pequod.
+Not only were the old sails being mended, but new sails were coming
+on board, and bolts of canvas, and coils of rigging; in short,
+everything betokened that the ship's preparations were hurrying to a
+close. Captain Peleg seldom or never went ashore, but sat in his
+wigwam keeping a sharp look-out upon the hands: Bildad did all the
+purchasing and providing at the stores; and the men employed in the
+hold and on the rigging were working till long after night-fall.
+
+On the day following Queequeg's signing the articles, word was given
+at all the inns where the ship's company were stopping, that their
+chests must be on board before night, for there was no telling how
+soon the vessel might be sailing. So Queequeg and I got down our
+traps, resolving, however, to sleep ashore till the last. But it
+seems they always give very long notice in these cases, and the ship
+did not sail for several days. But no wonder; there was a good deal
+to be done, and there is no telling how many things to be thought of,
+before the Pequod was fully equipped.
+
+Every one knows what a multitude of things--beds, sauce-pans, knives
+and forks, shovels and tongs, napkins, nut-crackers, and what not,
+are indispensable to the business of housekeeping. Just so with
+whaling, which necessitates a three-years' housekeeping upon the wide
+ocean, far from all grocers, costermongers, doctors, bakers, and
+bankers. And though this also holds true of merchant vessels, yet
+not by any means to the same extent as with whalemen. For besides
+the great length of the whaling voyage, the numerous articles
+peculiar to the prosecution of the fishery, and the impossibility of
+replacing them at the remote harbors usually frequented, it must be
+remembered, that of all ships, whaling vessels are the most exposed
+to accidents of all kinds, and especially to the destruction and loss
+of the very things upon which the success of the voyage most depends.
+Hence, the spare boats, spare spars, and spare lines and harpoons,
+and spare everythings, almost, but a spare Captain and duplicate
+ship.
+
+At the period of our arrival at the Island, the heaviest storage of
+the Pequod had been almost completed; comprising her beef, bread,
+water, fuel, and iron hoops and staves. But, as before hinted, for
+some time there was a continual fetching and carrying on board of
+divers odds and ends of things, both large and small.
+
+Chief among those who did this fetching and carrying was Captain
+Bildad's sister, a lean old lady of a most determined and
+indefatigable spirit, but withal very kindhearted, who seemed
+resolved that, if SHE could help it, nothing should be found wanting
+in the Pequod, after once fairly getting to sea. At one time she
+would come on board with a jar of pickles for the steward's pantry;
+another time with a bunch of quills for the chief mate's desk, where
+he kept his log; a third time with a roll of flannel for the small of
+some one's rheumatic back. Never did any woman better deserve her
+name, which was Charity--Aunt Charity, as everybody called her. And
+like a sister of charity did this charitable Aunt Charity bustle
+about hither and thither, ready to turn her hand and heart to
+anything that promised to yield safety, comfort, and consolation to
+all on board a ship in which her beloved brother Bildad was
+concerned, and in which she herself owned a score or two of
+well-saved dollars.
+
+But it was startling to see this excellent hearted Quakeress coming
+on board, as she did the last day, with a long oil-ladle in one hand,
+and a still longer whaling lance in the other. Nor was Bildad himself
+nor Captain Peleg at all backward. As for Bildad, he carried about
+with him a long list of the articles needed, and at every fresh
+arrival, down went his mark opposite that article upon the paper.
+Every once in a while Peleg came hobbling out of his whalebone den,
+roaring at the men down the hatchways, roaring up to the riggers at
+the mast-head, and then concluded by roaring back into his wigwam.
+
+During these days of preparation, Queequeg and I often visited the
+craft, and as often I asked about Captain Ahab, and how he was, and
+when he was going to come on board his ship. To these questions they
+would answer, that he was getting better and better, and was expected
+aboard every day; meantime, the two captains, Peleg and Bildad, could
+attend to everything necessary to fit the vessel for the voyage. If
+I had been downright honest with myself, I would have seen very
+plainly in my heart that I did but half fancy being committed this
+way to so long a voyage, without once laying my eyes on the man who
+was to be the absolute dictator of it, so soon as the ship sailed out
+upon the open sea. But when a man suspects any wrong, it sometimes
+happens that if he be already involved in the matter, he insensibly
+strives to cover up his suspicions even from himself. And much this
+way it was with me. I said nothing, and tried to think nothing.
+
+At last it was given out that some time next day the ship would
+certainly sail. So next morning, Queequeg and I took a very early
+start.
+
+
+
+CHAPTER 21
+
+Going Aboard.
+
+
+It was nearly six o'clock, but only grey imperfect misty dawn, when
+we drew nigh the wharf.
+
+"There are some sailors running ahead there, if I see right," said I
+to Queequeg, "it can't be shadows; she's off by sunrise, I guess;
+come on!"
+
+"Avast!" cried a voice, whose owner at the same time coming close
+behind us, laid a hand upon both our shoulders, and then insinuating
+himself between us, stood stooping forward a little, in the uncertain
+twilight, strangely peering from Queequeg to me. It was Elijah.
+
+"Going aboard?"
+
+"Hands off, will you," said I.
+
+"Lookee here," said Queequeg, shaking himself, "go 'way!"
+
+"Ain't going aboard, then?"
+
+"Yes, we are," said I, "but what business is that of yours? Do you
+know, Mr. Elijah, that I consider you a little impertinent?"
+
+"No, no, no; I wasn't aware of that," said Elijah, slowly and
+wonderingly looking from me to Queequeg, with the most unaccountable
+glances.
+
+"Elijah," said I, "you will oblige my friend and me by withdrawing.
+We are going to the Indian and Pacific Oceans, and would prefer not
+to be detained."
+
+"Ye be, be ye? Coming back afore breakfast?"
+
+"He's cracked, Queequeg," said I, "come on."
+
+"Holloa!" cried stationary Elijah, hailing us when we had removed a
+few paces.
+
+"Never mind him," said I, "Queequeg, come on."
+
+But he stole up to us again, and suddenly clapping his hand on my
+shoulder, said--"Did ye see anything looking like men going towards
+that ship a while ago?"
+
+Struck by this plain matter-of-fact question, I answered, saying,
+"Yes, I thought I did see four or five men; but it was too dim to be
+sure."
+
+"Very dim, very dim," said Elijah. "Morning to ye."
+
+Once more we quitted him; but once more he came softly after us; and
+touching my shoulder again, said, "See if you can find 'em now, will
+ye?
+
+"Find who?"
+
+"Morning to ye! morning to ye!" he rejoined, again moving off. "Oh!
+I was going to warn ye against--but never mind, never mind--it's all
+one, all in the family too;--sharp frost this morning, ain't it?
+Good-bye to ye. Shan't see ye again very soon, I guess; unless it's
+before the Grand Jury." And with these cracked words he finally
+departed, leaving me, for the moment, in no small wonderment at his
+frantic impudence.
+
+At last, stepping on board the Pequod, we found everything in
+profound quiet, not a soul moving. The cabin entrance was locked
+within; the hatches were all on, and lumbered with coils of rigging.
+Going forward to the forecastle, we found the slide of the scuttle
+open. Seeing a light, we went down, and found only an old rigger
+there, wrapped in a tattered pea-jacket. He was thrown at whole
+length upon two chests, his face downwards and inclosed in his folded
+arms. The profoundest slumber slept upon him.
+
+"Those sailors we saw, Queequeg, where can they have gone to?" said
+I, looking dubiously at the sleeper. But it seemed that, when on the
+wharf, Queequeg had not at all noticed what I now alluded to; hence I
+would have thought myself to have been optically deceived in that
+matter, were it not for Elijah's otherwise inexplicable question.
+But I beat the thing down; and again marking the sleeper, jocularly
+hinted to Queequeg that perhaps we had best sit up with the body;
+telling him to establish himself accordingly. He put his hand upon
+the sleeper's rear, as though feeling if it was soft enough; and
+then, without more ado, sat quietly down there.
+
+"Gracious! Queequeg, don't sit there," said I.
+
+"Oh! perry dood seat," said Queequeg, "my country way; won't hurt
+him face."
+
+"Face!" said I, "call that his face? very benevolent countenance
+then; but how hard he breathes, he's heaving himself; get off,
+Queequeg, you are heavy, it's grinding the face of the poor. Get
+off, Queequeg! Look, he'll twitch you off soon. I wonder he don't
+wake."
+
+Queequeg removed himself to just beyond the head of the sleeper, and
+lighted his tomahawk pipe. I sat at the feet. We kept the pipe
+passing over the sleeper, from one to the other. Meanwhile, upon
+questioning him in his broken fashion, Queequeg gave me to understand
+that, in his land, owing to the absence of settees and sofas of all
+sorts, the king, chiefs, and great people generally, were in the
+custom of fattening some of the lower orders for ottomans; and to
+furnish a house comfortably in that respect, you had only to buy up
+eight or ten lazy fellows, and lay them round in the piers and
+alcoves. Besides, it was very convenient on an excursion; much
+better than those garden-chairs which are convertible into
+walking-sticks; upon occasion, a chief calling his attendant, and
+desiring him to make a settee of himself under a spreading tree,
+perhaps in some damp marshy place.
+
+While narrating these things, every time Queequeg received the
+tomahawk from me, he flourished the hatchet-side of it over the
+sleeper's head.
+
+"What's that for, Queequeg?"
+
+"Perry easy, kill-e; oh! perry easy!
+
+He was going on with some wild reminiscences about his tomahawk-pipe,
+which, it seemed, had in its two uses both brained his foes and
+soothed his soul, when we were directly attracted to the sleeping
+rigger. The strong vapour now completely filling the contracted hole,
+it began to tell upon him. He breathed with a sort of muffledness;
+then seemed troubled in the nose; then revolved over once or twice;
+then sat up and rubbed his eyes.
+
+"Holloa!" he breathed at last, "who be ye smokers?"
+
+"Shipped men," answered I, "when does she sail?"
+
+"Aye, aye, ye are going in her, be ye? She sails to-day. The
+Captain came aboard last night."
+
+"What Captain?--Ahab?"
+
+"Who but him indeed?"
+
+I was going to ask him some further questions concerning Ahab, when
+we heard a noise on deck.
+
+"Holloa! Starbuck's astir," said the rigger. "He's a lively chief
+mate, that; good man, and a pious; but all alive now, I must turn
+to." And so saying he went on deck, and we followed.
+
+It was now clear sunrise. Soon the crew came on board in twos and
+threes; the riggers bestirred themselves; the mates were actively
+engaged; and several of the shore people were busy in bringing
+various last things on board. Meanwhile Captain Ahab remained
+invisibly enshrined within his cabin.
+
+
+
+CHAPTER 22
+
+Merry Christmas.
+
+
+At length, towards noon, upon the final dismissal of the ship's
+riggers, and after the Pequod had been hauled out from the wharf, and
+after the ever-thoughtful Charity had come off in a whale-boat, with
+her last gift--a night-cap for Stubb, the second mate, her
+brother-in-law, and a spare Bible for the steward--after all this,
+the two Captains, Peleg and Bildad, issued from the cabin, and
+turning to the chief mate, Peleg said:
+
+"Now, Mr. Starbuck, are you sure everything is right? Captain Ahab
+is all ready--just spoke to him--nothing more to be got from shore,
+eh? Well, call all hands, then. Muster 'em aft here--blast 'em!"
+
+"No need of profane words, however great the hurry, Peleg," said
+Bildad, "but away with thee, friend Starbuck, and do our bidding."
+
+How now! Here upon the very point of starting for the voyage,
+Captain Peleg and Captain Bildad were going it with a high hand on
+the quarter-deck, just as if they were to be joint-commanders at sea,
+as well as to all appearances in port. And, as for Captain Ahab, no
+sign of him was yet to be seen; only, they said he was in the cabin.
+But then, the idea was, that his presence was by no means necessary
+in getting the ship under weigh, and steering her well out to sea.
+Indeed, as that was not at all his proper business, but the pilot's;
+and as he was not yet completely recovered--so they said--therefore,
+Captain Ahab stayed below. And all this seemed natural enough;
+especially as in the merchant service many captains never show
+themselves on deck for a considerable time after heaving up the
+anchor, but remain over the cabin table, having a farewell
+merry-making with their shore friends, before they quit the ship for
+good with the pilot.
+
+But there was not much chance to think over the matter, for Captain
+Peleg was now all alive. He seemed to do most of the talking and
+commanding, and not Bildad.
+
+"Aft here, ye sons of bachelors," he cried, as the sailors lingered
+at the main-mast. "Mr. Starbuck, drive'em aft."
+
+"Strike the tent there!"--was the next order. As I hinted before,
+this whalebone marquee was never pitched except in port; and on board
+the Pequod, for thirty years, the order to strike the tent was well
+known to be the next thing to heaving up the anchor.
+
+"Man the capstan! Blood and thunder!--jump!"--was the next command,
+and the crew sprang for the handspikes.
+
+Now in getting under weigh, the station generally occupied by the
+pilot is the forward part of the ship. And here Bildad, who, with
+Peleg, be it known, in addition to his other officers, was one of the
+licensed pilots of the port--he being suspected to have got himself
+made a pilot in order to save the Nantucket pilot-fee to all the
+ships he was concerned in, for he never piloted any other
+craft--Bildad, I say, might now be seen actively engaged in looking
+over the bows for the approaching anchor, and at intervals singing
+what seemed a dismal stave of psalmody, to cheer the hands at the
+windlass, who roared forth some sort of a chorus about the girls in
+Booble Alley, with hearty good will. Nevertheless, not three days
+previous, Bildad had told them that no profane songs would be allowed
+on board the Pequod, particularly in getting under weigh; and
+Charity, his sister, had placed a small choice copy of Watts in each
+seaman's berth.
+
+Meantime, overseeing the other part of the ship, Captain Peleg ripped
+and swore astern in the most frightful manner. I almost thought he
+would sink the ship before the anchor could be got up; involuntarily
+I paused on my handspike, and told Queequeg to do the same, thinking
+of the perils we both ran, in starting on the voyage with such a
+devil for a pilot. I was comforting myself, however, with the
+thought that in pious Bildad might be found some salvation, spite of
+his seven hundred and seventy-seventh lay; when I felt a sudden sharp
+poke in my rear, and turning round, was horrified at the apparition
+of Captain Peleg in the act of withdrawing his leg from my immediate
+vicinity. That was my first kick.
+
+"Is that the way they heave in the marchant service?" he roared.
+"Spring, thou sheep-head; spring, and break thy backbone! Why don't
+ye spring, I say, all of ye--spring! Quohog! spring, thou chap with
+the red whiskers; spring there, Scotch-cap; spring, thou green
+pants. Spring, I say, all of ye, and spring your eyes out!" And so
+saying, he moved along the windlass, here and there using his leg
+very freely, while imperturbable Bildad kept leading off with his
+psalmody. Thinks I, Captain Peleg must have been drinking something
+to-day.
+
+At last the anchor was up, the sails were set, and off we glided. It
+was a short, cold Christmas; and as the short northern day merged
+into night, we found ourselves almost broad upon the wintry ocean,
+whose freezing spray cased us in ice, as in polished armor. The long
+rows of teeth on the bulwarks glistened in the moonlight; and like
+the white ivory tusks of some huge elephant, vast curving icicles
+depended from the bows.
+
+Lank Bildad, as pilot, headed the first watch, and ever and anon, as
+the old craft deep dived into the green seas, and sent the shivering
+frost all over her, and the winds howled, and the cordage rang, his
+steady notes were heard,--
+
+"Sweet fields beyond the swelling flood,
+Stand dressed in living green.
+So to the Jews old Canaan stood,
+While Jordan rolled between."
+
+
+Never did those sweet words sound more sweetly to me than then. They
+were full of hope and fruition. Spite of this frigid winter night in
+the boisterous Atlantic, spite of my wet feet and wetter jacket,
+there was yet, it then seemed to me, many a pleasant haven in store;
+and meads and glades so eternally vernal, that the grass shot up by
+the spring, untrodden, unwilted, remains at midsummer.
+
+At last we gained such an offing, that the two pilots were needed no
+longer. The stout sail-boat that had accompanied us began ranging
+alongside.
+
+It was curious and not unpleasing, how Peleg and Bildad were affected
+at this juncture, especially Captain Bildad. For loath to depart,
+yet; very loath to leave, for good, a ship bound on so long and
+perilous a voyage--beyond both stormy Capes; a ship in which some
+thousands of his hard earned dollars were invested; a ship, in which
+an old shipmate sailed as captain; a man almost as old as he, once
+more starting to encounter all the terrors of the pitiless jaw; loath
+to say good-bye to a thing so every way brimful of every interest to
+him,--poor old Bildad lingered long; paced the deck with anxious
+strides; ran down into the cabin to speak another farewell word
+there; again came on deck, and looked to windward; looked towards the
+wide and endless waters, only bounded by the far-off unseen Eastern
+Continents; looked towards the land; looked aloft; looked right and
+left; looked everywhere and nowhere; and at last, mechanically
+coiling a rope upon its pin, convulsively grasped stout Peleg by the
+hand, and holding up a lantern, for a moment stood gazing heroically
+in his face, as much as to say, "Nevertheless, friend Peleg, I can
+stand it; yes, I can."
+
+As for Peleg himself, he took it more like a philosopher; but for all
+his philosophy, there was a tear twinkling in his eye, when the
+lantern came too near. And he, too, did not a little run from cabin
+to deck--now a word below, and now a word with Starbuck, the chief
+mate.
+
+But, at last, he turned to his comrade, with a final sort of look
+about him,--"Captain Bildad--come, old shipmate, we must go. Back
+the main-yard there! Boat ahoy! Stand by to come close alongside,
+now! Careful, careful!--come, Bildad, boy--say your last. Luck to
+ye, Starbuck--luck to ye, Mr. Stubb--luck to ye, Mr. Flask--good-bye
+and good luck to ye all--and this day three years I'll have a hot
+supper smoking for ye in old Nantucket. Hurrah and away!"
+
+"God bless ye, and have ye in His holy keeping, men," murmured old
+Bildad, almost incoherently. "I hope ye'll have fine weather now, so
+that Captain Ahab may soon be moving among ye--a pleasant sun is all
+he needs, and ye'll have plenty of them in the tropic voyage ye go.
+Be careful in the hunt, ye mates. Don't stave the boats needlessly,
+ye harpooneers; good white cedar plank is raised full three per cent.
+within the year. Don't forget your prayers, either. Mr. Starbuck,
+mind that cooper don't waste the spare staves. Oh! the sail-needles
+are in the green locker! Don't whale it too much a' Lord's days,
+men; but don't miss a fair chance either, that's rejecting Heaven's
+good gifts. Have an eye to the molasses tierce, Mr. Stubb; it was a
+little leaky, I thought. If ye touch at the islands, Mr. Flask,
+beware of fornication. Good-bye, good-bye! Don't keep that cheese
+too long down in the hold, Mr. Starbuck; it'll spoil. Be careful
+with the butter--twenty cents the pound it was, and mind ye, if--"
+
+"Come, come, Captain Bildad; stop palavering,--away!" and with that,
+Peleg hurried him over the side, and both dropt into the boat.
+
+Ship and boat diverged; the cold, damp night breeze blew between; a
+screaming gull flew overhead; the two hulls wildly rolled; we gave
+three heavy-hearted cheers, and blindly plunged like fate into the
+lone Atlantic.
+
+
+
+CHAPTER 23
+
+The Lee Shore.
+
+
+Some chapters back, one Bulkington was spoken of, a tall, newlanded
+mariner, encountered in New Bedford at the inn.
+
+When on that shivering winter's night, the Pequod thrust her
+vindictive bows into the cold malicious waves, who should I see
+standing at her helm but Bulkington! I looked with sympathetic awe
+and fearfulness upon the man, who in mid-winter just landed from a
+four years' dangerous voyage, could so unrestingly push off again for
+still another tempestuous term. The land seemed scorching to his
+feet. Wonderfullest things are ever the unmentionable; deep memories
+yield no epitaphs; this six-inch chapter is the stoneless grave of
+Bulkington. Let me only say that it fared with him as with the
+storm-tossed ship, that miserably drives along the leeward land. The
+port would fain give succor; the port is pitiful; in the port is
+safety, comfort, hearthstone, supper, warm blankets, friends, all
+that's kind to our mortalities. But in that gale, the port, the
+land, is that ship's direst jeopardy; she must fly all hospitality;
+one touch of land, though it but graze the keel, would make her
+shudder through and through. With all her might she crowds all sail
+off shore; in so doing, fights 'gainst the very winds that fain would
+blow her homeward; seeks all the lashed sea's landlessness again; for
+refuge's sake forlornly rushing into peril; her only friend her
+bitterest foe!
+
+Know ye now, Bulkington? Glimpses do ye seem to see of that mortally
+intolerable truth; that all deep, earnest thinking is but the
+intrepid effort of the soul to keep the open independence of her sea;
+while the wildest winds of heaven and earth conspire to cast her on
+the treacherous, slavish shore?
+
+But as in landlessness alone resides highest truth, shoreless,
+indefinite as God--so, better is it to perish in that howling
+infinite, than be ingloriously dashed upon the lee, even if that were
+safety! For worm-like, then, oh! who would craven crawl to land!
+Terrors of the terrible! is all this agony so vain? Take heart, take
+heart, O Bulkington! Bear thee grimly, demigod! Up from the spray
+of thy ocean-perishing--straight up, leaps thy apotheosis!
+
+
+
+CHAPTER 24
+
+The Advocate.
+
+
+As Queequeg and I are now fairly embarked in this business of
+whaling; and as this business of whaling has somehow come to be
+regarded among landsmen as a rather unpoetical and disreputable
+pursuit; therefore, I am all anxiety to convince ye, ye landsmen, of
+the injustice hereby done to us hunters of whales.
+
+In the first place, it may be deemed almost superfluous to establish
+the fact, that among people at large, the business of whaling is not
+accounted on a level with what are called the liberal professions.
+If a stranger were introduced into any miscellaneous metropolitan
+society, it would but slightly advance the general opinion of his
+merits, were he presented to the company as a harpooneer, say; and if
+in emulation of the naval officers he should append the initials
+S.W.F. (Sperm Whale Fishery) to his visiting card, such a procedure
+would be deemed pre-eminently presuming and ridiculous.
+
+Doubtless one leading reason why the world declines honouring us
+whalemen, is this: they think that, at best, our vocation amounts to
+a butchering sort of business; and that when actively engaged
+therein, we are surrounded by all manner of defilements. Butchers we
+are, that is true. But butchers, also, and butchers of the bloodiest
+badge have been all Martial Commanders whom the world invariably
+delights to honour. And as for the matter of the alleged
+uncleanliness of our business, ye shall soon be initiated into
+certain facts hitherto pretty generally unknown, and which, upon the
+whole, will triumphantly plant the sperm whale-ship at least among
+the cleanliest things of this tidy earth. But even granting the
+charge in question to be true; what disordered slippery decks of a
+whale-ship are comparable to the unspeakable carrion of those
+battle-fields from which so many soldiers return to drink in all
+ladies' plaudits? And if the idea of peril so much enhances the
+popular conceit of the soldier's profession; let me assure ye that
+many a veteran who has freely marched up to a battery, would quickly
+recoil at the apparition of the sperm whale's vast tail, fanning into
+eddies the air over his head. For what are the comprehensible
+terrors of man compared with the interlinked terrors and wonders of
+God!
+
+But, though the world scouts at us whale hunters, yet does it
+unwittingly pay us the profoundest homage; yea, an all-abounding
+adoration! for almost all the tapers, lamps, and candles that burn
+round the globe, burn, as before so many shrines, to our glory!
+
+But look at this matter in other lights; weigh it in all sorts of
+scales; see what we whalemen are, and have been.
+
+Why did the Dutch in De Witt's time have admirals of their whaling
+fleets? Why did Louis XVI. of France, at his own personal expense,
+fit out whaling ships from Dunkirk, and politely invite to that town
+some score or two of families from our own island of Nantucket? Why
+did Britain between the years 1750 and 1788 pay to her whalemen in
+bounties upwards of L1,000,000? And lastly, how comes it that we
+whalemen of America now outnumber all the rest of the banded whalemen
+in the world; sail a navy of upwards of seven hundred vessels; manned
+by eighteen thousand men; yearly consuming 4,000,000 of dollars; the
+ships worth, at the time of sailing, $20,000,000! and every year
+importing into our harbors a well reaped harvest of $7,000,000. How
+comes all this, if there be not something puissant in whaling?
+
+But this is not the half; look again.
+
+I freely assert, that the cosmopolite philosopher cannot, for his
+life, point out one single peaceful influence, which within the last
+sixty years has operated more potentially upon the whole broad world,
+taken in one aggregate, than the high and mighty business of whaling.
+One way and another, it has begotten events so remarkable in
+themselves, and so continuously momentous in their sequential issues,
+that whaling may well be regarded as that Egyptian mother, who bore
+offspring themselves pregnant from her womb. It would be a hopeless,
+endless task to catalogue all these things. Let a handful suffice.
+For many years past the whale-ship has been the pioneer in ferreting
+out the remotest and least known parts of the earth. She has
+explored seas and archipelagoes which had no chart, where no Cook or
+Vancouver had ever sailed. If American and European men-of-war
+now peacefully ride in once savage harbors, let them fire salutes to
+the honour and glory of the whale-ship, which originally showed them
+the way, and first interpreted between them and the savages. They
+may celebrate as they will the heroes of Exploring Expeditions, your
+Cooks, your Krusensterns; but I say that scores of anonymous
+Captains have sailed out of Nantucket, that were as great, and
+greater than your Cook and your Krusenstern. For in their
+succourless empty-handedness, they, in the heathenish sharked waters,
+and by the beaches of unrecorded, javelin islands, battled with
+virgin wonders and terrors that Cook with all his marines and
+muskets would not willingly have dared. All that is made such a
+flourish of in the old South Sea Voyages, those things were but the
+life-time commonplaces of our heroic Nantucketers. Often,
+adventures which Vancouver dedicates three chapters to, these men
+accounted unworthy of being set down in the ship's common log. Ah,
+the world! Oh, the world!
+
+Until the whale fishery rounded Cape Horn, no commerce but colonial,
+scarcely any intercourse but colonial, was carried on between Europe
+and the long line of the opulent Spanish provinces on the Pacific
+coast. It was the whaleman who first broke through the jealous
+policy of the Spanish crown, touching those colonies; and, if space
+permitted, it might be distinctly shown how from those whalemen at
+last eventuated the liberation of Peru, Chili, and Bolivia from the
+yoke of Old Spain, and the establishment of the eternal democracy in
+those parts.
+
+That great America on the other side of the sphere, Australia, was
+given to the enlightened world by the whaleman. After its first
+blunder-born discovery by a Dutchman, all other ships long shunned
+those shores as pestiferously barbarous; but the whale-ship touched
+there. The whale-ship is the true mother of that now mighty colony.
+Moreover, in the infancy of the first Australian settlement, the
+emigrants were several times saved from starvation by the benevolent
+biscuit of the whale-ship luckily dropping an anchor in their waters.
+The uncounted isles of all Polynesia confess the same truth, and do
+commercial homage to the whale-ship, that cleared the way for the
+missionary and the merchant, and in many cases carried the primitive
+missionaries to their first destinations. If that double-bolted
+land, Japan, is ever to become hospitable, it is the whale-ship alone
+to whom the credit will be due; for already she is on the threshold.
+
+But if, in the face of all this, you still declare that whaling has
+no aesthetically noble associations connected with it, then am I
+ready to shiver fifty lances with you there, and unhorse you with a
+split helmet every time.
+
+The whale has no famous author, and whaling no famous chronicler, you
+will say.
+
+THE WHALE NO FAMOUS AUTHOR, AND WHALING NO FAMOUS CHRONICLER? Who
+wrote the first account of our Leviathan? Who but mighty Job! And
+who composed the first narrative of a whaling-voyage? Who, but no
+less a prince than Alfred the Great, who, with his own royal pen,
+took down the words from Other, the Norwegian whale-hunter of those
+times! And who pronounced our glowing eulogy in Parliament? Who,
+but Edmund Burke!
+
+True enough, but then whalemen themselves are poor devils; they have
+no good blood in their veins.
+
+NO GOOD BLOOD IN THEIR VEINS? They have something better than royal
+blood there. The grandmother of Benjamin Franklin was Mary Morrel;
+afterwards, by marriage, Mary Folger, one of the old settlers of
+Nantucket, and the ancestress to a long line of Folgers and
+harpooneers--all kith and kin to noble Benjamin--this day darting the
+barbed iron from one side of the world to the other.
+
+Good again; but then all confess that somehow whaling is not
+respectable.
+
+WHALING NOT RESPECTABLE? Whaling is imperial! By old English
+statutory law, the whale is declared "a royal fish."*
+
+Oh, that's only nominal! The whale himself has never figured in any
+grand imposing way.
+
+THE WHALE NEVER FIGURED IN ANY GRAND IMPOSING WAY? In one of the
+mighty triumphs given to a Roman general upon his entering the
+world's capital, the bones of a whale, brought all the way from the
+Syrian coast, were the most conspicuous object in the cymballed
+procession.*
+
+
+*See subsequent chapters for something more on this head.
+
+
+Grant it, since you cite it; but, say what you will, there is no real
+dignity in whaling.
+
+NO DIGNITY IN WHALING? The dignity of our calling the very heavens
+attest. Cetus is a constellation in the South! No more! Drive
+down your hat in presence of the Czar, and take it off to Queequeg!
+No more! I know a man that, in his lifetime, has taken three hundred
+and fifty whales. I account that man more honourable than that great
+captain of antiquity who boasted of taking as many walled towns.
+
+And, as for me, if, by any possibility, there be any as yet
+undiscovered prime thing in me; if I shall ever deserve any real
+repute in that small but high hushed world which I might not be
+unreasonably ambitious of; if hereafter I shall do anything that, upon
+the whole, a man might rather have done than to have left undone; if,
+at my death, my executors, or more properly my creditors, find any
+precious MSS. in my desk, then here I prospectively ascribe all the
+honour and the glory to whaling; for a whale-ship was my Yale College
+and my Harvard.
+
+
+
+CHAPTER 25
+
+Postscript.
+
+
+In behalf of the dignity of whaling, I would fain advance naught but
+substantiated facts. But after embattling his facts, an advocate who
+should wholly suppress a not unreasonable surmise, which might tell
+eloquently upon his cause--such an advocate, would he not be
+blameworthy?
+
+It is well known that at the coronation of kings and queens, even
+modern ones, a certain curious process of seasoning them for their
+functions is gone through. There is a saltcellar of state, so
+called, and there may be a castor of state. How they use the salt,
+precisely--who knows? Certain I am, however, that a king's head is
+solemnly oiled at his coronation, even as a head of salad. Can it
+be, though, that they anoint it with a view of making its interior
+run well, as they anoint machinery? Much might be ruminated here,
+concerning the essential dignity of this regal process, because in
+common life we esteem but meanly and contemptibly a fellow who
+anoints his hair, and palpably smells of that anointing. In truth, a
+mature man who uses hair-oil, unless medicinally, that man has
+probably got a quoggy spot in him somewhere. As a general rule, he
+can't amount to much in his totality.
+
+But the only thing to be considered here, is this--what kind of oil
+is used at coronations? Certainly it cannot be olive oil, nor
+macassar oil, nor castor oil, nor bear's oil, nor train oil, nor
+cod-liver oil. What then can it possibly be, but sperm oil in
+its unmanufactured, unpolluted state, the sweetest of all oils?
+
+Think of that, ye loyal Britons! we whalemen supply your kings and
+queens with coronation stuff!
+
+
+
+CHAPTER 26
+
+Knights and Squires.
+
+
+The chief mate of the Pequod was Starbuck, a native of Nantucket, and
+a Quaker by descent. He was a long, earnest man, and though born on
+an icy coast, seemed well adapted to endure hot latitudes, his flesh
+being hard as twice-baked biscuit. Transported to the Indies, his
+live blood would not spoil like bottled ale. He must have been born
+in some time of general drought and famine, or upon one of those fast
+days for which his state is famous. Only some thirty arid summers
+had he seen; those summers had dried up all his physical
+superfluousness. But this, his thinness, so to speak, seemed no more
+the token of wasting anxieties and cares, than it seemed the
+indication of any bodily blight. It was merely the condensation of
+the man. He was by no means ill-looking; quite the contrary. His
+pure tight skin was an excellent fit; and closely wrapped up in it,
+and embalmed with inner health and strength, like a revivified
+Egyptian, this Starbuck seemed prepared to endure for long ages to
+come, and to endure always, as now; for be it Polar snow or torrid
+sun, like a patent chronometer, his interior vitality was warranted
+to do well in all climates. Looking into his eyes, you seemed to
+see there the yet lingering images of those thousand-fold perils he
+had calmly confronted through life. A staid, steadfast man, whose
+life for the most part was a telling pantomime of action, and not a
+tame chapter of sounds. Yet, for all his hardy sobriety and
+fortitude, there were certain qualities in him which at times
+affected, and in some cases seemed well nigh to overbalance all the
+rest. Uncommonly conscientious for a seaman, and endued with a deep
+natural reverence, the wild watery loneliness of his life did
+therefore strongly incline him to superstition; but to that sort of
+superstition, which in some organizations seems rather to spring,
+somehow, from intelligence than from ignorance. Outward portents and
+inward presentiments were his. And if at times these things bent the
+welded iron of his soul, much more did his far-away domestic memories
+of his young Cape wife and child, tend to bend him still more from
+the original ruggedness of his nature, and open him still further to
+those latent influences which, in some honest-hearted men, restrain
+the gush of dare-devil daring, so often evinced by others in the more
+perilous vicissitudes of the fishery. "I will have no man in my
+boat," said Starbuck, "who is not afraid of a whale." By this, he
+seemed to mean, not only that the most reliable and useful courage
+was that which arises from the fair estimation of the encountered
+peril, but that an utterly fearless man is a far more dangerous
+comrade than a coward.
+
+"Aye, aye," said Stubb, the second mate, "Starbuck, there, is as
+careful a man as you'll find anywhere in this fishery." But we shall
+ere long see what that word "careful" precisely means when used by a
+man like Stubb, or almost any other whale hunter.
+
+Starbuck was no crusader after perils; in him courage was not a
+sentiment; but a thing simply useful to him, and always at hand upon
+all mortally practical occasions. Besides, he thought, perhaps, that
+in this business of whaling, courage was one of the great staple
+outfits of the ship, like her beef and her bread, and not to be
+foolishly wasted. Wherefore he had no fancy for lowering for whales
+after sun-down; nor for persisting in fighting a fish that too much
+persisted in fighting him. For, thought Starbuck, I am here in this
+critical ocean to kill whales for my living, and not to be killed by
+them for theirs; and that hundreds of men had been so killed Starbuck
+well knew. What doom was his own father's? Where, in the bottomless
+deeps, could he find the torn limbs of his brother?
+
+With memories like these in him, and, moreover, given to a certain
+superstitiousness, as has been said; the courage of this Starbuck
+which could, nevertheless, still flourish, must indeed have been
+extreme. But it was not in reasonable nature that a man so
+organized, and with such terrible experiences and remembrances as he
+had; it was not in nature that these things should fail in latently
+engendering an element in him, which, under suitable circumstances,
+would break out from its confinement, and burn all his courage up.
+And brave as he might be, it was that sort of bravery chiefly,
+visible in some intrepid men, which, while generally abiding firm in
+the conflict with seas, or winds, or whales, or any of the ordinary
+irrational horrors of the world, yet cannot withstand those more
+terrific, because more spiritual terrors, which sometimes menace you
+from the concentrating brow of an enraged and mighty man.
+
+But were the coming narrative to reveal in any instance, the complete
+abasement of poor Starbuck's fortitude, scarce might I have the heart
+to write it; for it is a thing most sorrowful, nay shocking, to
+expose the fall of valour in the soul. Men may seem detestable as
+joint stock-companies and nations; knaves, fools, and murderers there
+may be; men may have mean and meagre faces; but man, in the ideal,
+is so noble and so sparkling, such a grand and glowing creature, that
+over any ignominious blemish in him all his fellows should run to
+throw their costliest robes. That immaculate manliness we feel
+within ourselves, so far within us, that it remains intact though all
+the outer character seem gone; bleeds with keenest anguish at the
+undraped spectacle of a valor-ruined man. Nor can piety itself, at
+such a shameful sight, completely stifle her upbraidings against the
+permitting stars. But this august dignity I treat of, is not the
+dignity of kings and robes, but that abounding dignity which has no
+robed investiture. Thou shalt see it shining in the arm that wields
+a pick or drives a spike; that democratic dignity which, on all
+hands, radiates without end from God; Himself! The great God
+absolute! The centre and circumference of all democracy! His
+omnipresence, our divine equality!
+
+If, then, to meanest mariners, and renegades and castaways, I shall
+hereafter ascribe high qualities, though dark; weave round them
+tragic graces; if even the most mournful, perchance the most abased,
+among them all, shall at times lift himself to the exalted mounts; if
+I shall touch that workman's arm with some ethereal light; if I shall
+spread a rainbow over his disastrous set of sun; then against all
+mortal critics bear me out in it, thou Just Spirit of Equality,
+which hast spread one royal mantle of humanity over all my kind!
+Bear me out in it, thou great democratic God! who didst not refuse to
+the swart convict, Bunyan, the pale, poetic pearl; Thou who didst
+clothe with doubly hammered leaves of finest gold, the stumped and
+paupered arm of old Cervantes; Thou who didst pick up Andrew Jackson
+from the pebbles; who didst hurl him upon a war-horse; who didst
+thunder him higher than a throne! Thou who, in all Thy mighty,
+earthly marchings, ever cullest Thy selectest champions from the
+kingly commons; bear me out in it, O God!
+
+
+
+CHAPTER 27
+
+Knights and Squires.
+
+
+Stubb was the second mate. He was a native of Cape Cod; and hence,
+according to local usage, was called a Cape-Cod-man. A
+happy-go-lucky; neither craven nor valiant; taking perils as they
+came with an indifferent air; and while engaged in the most imminent
+crisis of the chase, toiling away, calm and collected as a journeyman
+joiner engaged for the year. Good-humored, easy, and careless, he
+presided over his whale-boat as if the most deadly encounter were but
+a dinner, and his crew all invited guests. He was as particular
+about the comfortable arrangement of his part of the boat, as an
+old stage-driver is about the snugness of his box. When close to the
+whale, in the very death-lock of the fight, he handled his unpitying
+lance coolly and off-handedly, as a whistling tinker his hammer. He
+would hum over his old rigadig tunes while flank and flank with the
+most exasperated monster. Long usage had, for this Stubb, converted
+the jaws of death into an easy chair. What he thought of death
+itself, there is no telling. Whether he ever thought of it at all,
+might be a question; but, if he ever did chance to cast his mind that
+way after a comfortable dinner, no doubt, like a good sailor, he took
+it to be a sort of call of the watch to tumble aloft, and bestir
+themselves there, about something which he would find out when he
+obeyed the order, and not sooner.
+
+What, perhaps, with other things, made Stubb such an easy-going,
+unfearing man, so cheerily trudging off with the burden of life in a
+world full of grave pedlars, all bowed to the ground with their
+packs; what helped to bring about that almost impious good-humor of
+his; that thing must have been his pipe. For, like his nose, his
+short, black little pipe was one of the regular features of his face.
+You would almost as soon have expected him to turn out of his bunk
+without his nose as without his pipe. He kept a whole row of pipes
+there ready loaded, stuck in a rack, within easy reach of his hand;
+and, whenever he turned in, he smoked them all out in succession,
+lighting one from the other to the end of the chapter; then loading
+them again to be in readiness anew. For, when Stubb dressed, instead
+of first putting his legs into his trowsers, he put his pipe into his
+mouth.
+
+I say this continual smoking must have been one cause, at least, of
+his peculiar disposition; for every one knows that this earthly air,
+whether ashore or afloat, is terribly infected with the nameless
+miseries of the numberless mortals who have died exhaling it; and as
+in time of the cholera, some people go about with a camphorated
+handkerchief to their mouths; so, likewise, against all mortal
+tribulations, Stubb's tobacco smoke might have operated as a sort of
+disinfecting agent.
+
+The third mate was Flask, a native of Tisbury, in Martha's Vineyard.
+A short, stout, ruddy young fellow, very pugnacious concerning
+whales, who somehow seemed to think that the great leviathans had
+personally and hereditarily affronted him; and therefore it was a
+sort of point of honour with him, to destroy them whenever
+encountered. So utterly lost was he to all sense of reverence for
+the many marvels of their majestic bulk and mystic ways; and so dead
+to anything like an apprehension of any possible danger from
+encountering them; that in his poor opinion, the wondrous whale was
+but a species of magnified mouse, or at least water-rat, requiring
+only a little circumvention and some small application of time and
+trouble in order to kill and boil. This ignorant, unconscious
+fearlessness of his made him a little waggish in the matter of
+whales; he followed these fish for the fun of it; and a three years'
+voyage round Cape Horn was only a jolly joke that lasted that length
+of time. As a carpenter's nails are divided into wrought nails and
+cut nails; so mankind may be similarly divided. Little Flask was one
+of the wrought ones; made to clinch tight and last long. They called
+him King-Post on board of the Pequod; because, in form, he could be
+well likened to the short, square timber known by that name in Arctic
+whalers; and which by the means of many radiating side timbers
+inserted into it, serves to brace the ship against the icy
+concussions of those battering seas.
+
+Now these three mates--Starbuck, Stubb, and Flask, were momentous
+men. They it was who by universal prescription commanded three of the
+Pequod's boats as headsmen. In that grand order of battle in which
+Captain Ahab would probably marshal his forces to descend on the
+whales, these three headsmen were as captains of companies. Or,
+being armed with their long keen whaling spears, they were as a
+picked trio of lancers; even as the harpooneers were flingers of
+javelins.
+
+And since in this famous fishery, each mate or headsman, like a
+Gothic Knight of old, is always accompanied by his boat-steerer or
+harpooneer, who in certain conjunctures provides him with a fresh
+lance, when the former one has been badly twisted, or elbowed in the
+assault; and moreover, as there generally subsists between the two, a
+close intimacy and friendliness; it is therefore but meet, that in
+this place we set down who the Pequod's harpooneers were, and to what
+headsman each of them belonged.
+
+First of all was Queequeg, whom Starbuck, the chief mate, had
+selected for his squire. But Queequeg is already known.
+
+Next was Tashtego, an unmixed Indian from Gay Head, the most westerly
+promontory of Martha's Vineyard, where there still exists the last
+remnant of a village of red men, which has long supplied the
+neighboring island of Nantucket with many of her most daring
+harpooneers. In the fishery, they usually go by the generic name of
+Gay-Headers. Tashtego's long, lean, sable hair, his high cheek
+bones, and black rounding eyes--for an Indian, Oriental in their
+largeness, but Antarctic in their glittering expression--all this
+sufficiently proclaimed him an inheritor of the unvitiated blood of
+those proud warrior hunters, who, in quest of the great New England
+moose, had scoured, bow in hand, the aboriginal forests of the main.
+But no longer snuffing in the trail of the wild beasts of the
+woodland, Tashtego now hunted in the wake of the great whales of the
+sea; the unerring harpoon of the son fitly replacing the infallible
+arrow of the sires. To look at the tawny brawn of his lithe snaky
+limbs, you would almost have credited the superstitions of some of
+the earlier Puritans, and half-believed this wild Indian to be a son
+of the Prince of the Powers of the Air. Tashtego was Stubb the
+second mate's squire.
+
+Third among the harpooneers was Daggoo, a gigantic, coal-black
+negro-savage, with a lion-like tread--an Ahasuerus to behold.
+Suspended from his ears were two golden hoops, so large that the
+sailors called them ring-bolts, and would talk of securing the
+top-sail halyards to them. In his youth Daggoo had voluntarily
+shipped on board of a whaler, lying in a lonely bay on his native
+coast. And never having been anywhere in the world but in Africa,
+Nantucket, and the pagan harbors most frequented by whalemen; and
+having now led for many years the bold life of the fishery in the
+ships of owners uncommonly heedful of what manner of men they
+shipped; Daggoo retained all his barbaric virtues, and erect as a
+giraffe, moved about the decks in all the pomp of six feet five in
+his socks. There was a corporeal humility in looking up at him; and
+a white man standing before him seemed a white flag come to beg truce
+of a fortress. Curious to tell, this imperial negro, Ahasuerus
+Daggoo, was the Squire of little Flask, who looked like a chess-man
+beside him. As for the residue of the Pequod's company, be it said,
+that at the present day not one in two of the many thousand men
+before the mast employed in the American whale fishery, are Americans
+born, though pretty nearly all the officers are. Herein it is the
+same with the American whale fishery as with the American army and
+military and merchant navies, and the engineering forces employed in
+the construction of the American Canals and Railroads. The same, I
+say, because in all these cases the native American liberally
+provides the brains, the rest of the world as generously supplying
+the muscles. No small number of these whaling seamen belong to the
+Azores, where the outward bound Nantucket whalers frequently touch to
+augment their crews from the hardy peasants of those rocky shores.
+In like manner, the Greenland whalers sailing out of Hull or London,
+put in at the Shetland Islands, to receive the full complement of
+their crew. Upon the passage homewards, they drop them there again.
+How it is, there is no telling, but Islanders seem to make the best
+whalemen. They were nearly all Islanders in the Pequod, ISOLATOES
+too, I call such, not acknowledging the common continent of men, but
+each ISOLATO living on a separate continent of his own. Yet now,
+federated along one keel, what a set these Isolatoes were! An
+Anacharsis Clootz deputation from all the isles of the sea, and all
+the ends of the earth, accompanying Old Ahab in the Pequod to lay the
+world's grievances before that bar from which not very many of them
+ever come back. Black Little Pip--he never did--oh, no! he went
+before. Poor Alabama boy! On the grim Pequod's forecastle, ye shall
+ere long see him, beating his tambourine; prelusive of the eternal
+time, when sent for, to the great quarter-deck on high, he was bid
+strike in with angels, and beat his tambourine in glory; called a
+coward here, hailed a hero there!
+
+
+
+CHAPTER 28
+
+Ahab.
+
+
+For several days after leaving Nantucket, nothing above hatches was
+seen of Captain Ahab. The mates regularly relieved each other at the
+watches, and for aught that could be seen to the contrary, they
+seemed to be the only commanders of the ship; only they sometimes
+issued from the cabin with orders so sudden and peremptory, that
+after all it was plain they but commanded vicariously. Yes, their
+supreme lord and dictator was there, though hitherto unseen by any
+eyes not permitted to penetrate into the now sacred retreat of the
+cabin.
+
+Every time I ascended to the deck from my watches below, I instantly
+gazed aft to mark if any strange face were visible; for my first
+vague disquietude touching the unknown captain, now in the seclusion
+of the sea, became almost a perturbation. This was strangely
+heightened at times by the ragged Elijah's diabolical incoherences
+uninvitedly recurring to me, with a subtle energy I could not have
+before conceived of. But poorly could I withstand them, much as in
+other moods I was almost ready to smile at the solemn whimsicalities
+of that outlandish prophet of the wharves. But whatever it was of
+apprehensiveness or uneasiness--to call it so--which I felt, yet
+whenever I came to look about me in the ship, it seemed against all
+warrantry to cherish such emotions. For though the harpooneers, with
+the great body of the crew, were a far more barbaric, heathenish, and
+motley set than any of the tame merchant-ship companies which my
+previous experiences had made me acquainted with, still I ascribed
+this--and rightly ascribed it--to the fierce uniqueness of the very
+nature of that wild Scandinavian vocation in which I had so
+abandonedly embarked. But it was especially the aspect of the three
+chief officers of the ship, the mates, which was most forcibly
+calculated to allay these colourless misgivings, and induce confidence
+and cheerfulness in every presentment of the voyage. Three better,
+more likely sea-officers and men, each in his own different way,
+could not readily be found, and they were every one of them
+Americans; a Nantucketer, a Vineyarder, a Cape man. Now, it being
+Christmas when the ship shot from out her harbor, for a space we had
+biting Polar weather, though all the time running away from it to the
+southward; and by every degree and minute of latitude which we
+sailed, gradually leaving that merciless winter, and all its
+intolerable weather behind us. It was one of those less lowering,
+but still grey and gloomy enough mornings of the transition, when
+with a fair wind the ship was rushing through the water with a
+vindictive sort of leaping and melancholy rapidity, that as I mounted
+to the deck at the call of the forenoon watch, so soon as I levelled
+my glance towards the taffrail, foreboding shivers ran over me.
+Reality outran apprehension; Captain Ahab stood upon his
+quarter-deck.
+
+There seemed no sign of common bodily illness about him, nor of the
+recovery from any. He looked like a man cut away from the stake,
+when the fire has overrunningly wasted all the limbs without
+consuming them, or taking away one particle from their compacted aged
+robustness. His whole high, broad form, seemed made of solid bronze,
+and shaped in an unalterable mould, like Cellini's cast Perseus.
+Threading its way out from among his grey hairs, and continuing right
+down one side of his tawny scorched face and neck, till it
+disappeared in his clothing, you saw a slender rod-like mark, lividly
+whitish. It resembled that perpendicular seam sometimes made in the
+straight, lofty trunk of a great tree, when the upper lightning
+tearingly darts down it, and without wrenching a single twig, peels
+and grooves out the bark from top to bottom, ere running off into the
+soil, leaving the tree still greenly alive, but branded. Whether
+that mark was born with him, or whether it was the scar left by some
+desperate wound, no one could certainly say. By some tacit consent,
+throughout the voyage little or no allusion was made to it,
+especially by the mates. But once Tashtego's senior, an old Gay-Head
+Indian among the crew, superstitiously asserted that not till he was
+full forty years old did Ahab become that way branded, and then it
+came upon him, not in the fury of any mortal fray, but in an
+elemental strife at sea. Yet, this wild hint seemed inferentially
+negatived, by what a grey Manxman insinuated, an old sepulchral man,
+who, having never before sailed out of Nantucket, had never ere this
+laid eye upon wild Ahab. Nevertheless, the old sea-traditions, the
+immemorial credulities, popularly invested this old Manxman with
+preternatural powers of discernment. So that no white sailor
+seriously contradicted him when he said that if ever Captain Ahab
+should be tranquilly laid out--which might hardly come to pass, so he
+muttered--then, whoever should do that last office for the dead,
+would find a birth-mark on him from crown to sole.
+
+So powerfully did the whole grim aspect of Ahab affect me, and the
+livid brand which streaked it, that for the first few moments I
+hardly noted that not a little of this overbearing grimness was owing
+to the barbaric white leg upon which he partly stood. It had
+previously come to me that this ivory leg had at sea been fashioned
+from the polished bone of the sperm whale's jaw. "Aye, he was
+dismasted off Japan," said the old Gay-Head Indian once; "but like
+his dismasted craft, he shipped another mast without coming home for
+it. He has a quiver of 'em."
+
+I was struck with the singular posture he maintained. Upon each side
+of the Pequod's quarter deck, and pretty close to the mizzen shrouds,
+there was an auger hole, bored about half an inch or so, into the
+plank. His bone leg steadied in that hole; one arm elevated, and
+holding by a shroud; Captain Ahab stood erect, looking straight out
+beyond the ship's ever-pitching prow. There was an infinity of
+firmest fortitude, a determinate, unsurrenderable wilfulness, in the
+fixed and fearless, forward dedication of that glance. Not a word he
+spoke; nor did his officers say aught to him; though by all their
+minutest gestures and expressions, they plainly showed the uneasy, if
+not painful, consciousness of being under a troubled master-eye. And
+not only that, but moody stricken Ahab stood before them with a
+crucifixion in his face; in all the nameless regal overbearing
+dignity of some mighty woe.
+
+Ere long, from his first visit in the air, he withdrew into his
+cabin. But after that morning, he was every day visible to the crew;
+either standing in his pivot-hole, or seated upon an ivory stool he
+had; or heavily walking the deck. As the sky grew less gloomy;
+indeed, began to grow a little genial, he became still less and less
+a recluse; as if, when the ship had sailed from home, nothing but the
+dead wintry bleakness of the sea had then kept him so secluded. And,
+by and by, it came to pass, that he was almost continually in the
+air; but, as yet, for all that he said, or perceptibly did, on the at
+last sunny deck, he seemed as unnecessary there as another mast. But
+the Pequod was only making a passage now; not regularly cruising;
+nearly all whaling preparatives needing supervision the mates were
+fully competent to, so that there was little or nothing, out of
+himself, to employ or excite Ahab, now; and thus chase away, for that
+one interval, the clouds that layer upon layer were piled upon his
+brow, as ever all clouds choose the loftiest peaks to pile themselves
+upon.
+
+Nevertheless, ere long, the warm, warbling persuasiveness of the
+pleasant, holiday weather we came to, seemed gradually to charm him
+from his mood. For, as when the red-cheeked, dancing girls, April
+and May, trip home to the wintry, misanthropic woods; even the
+barest, ruggedest, most thunder-cloven old oak will at least send
+forth some few green sprouts, to welcome such glad-hearted visitants;
+so Ahab did, in the end, a little respond to the playful allurings of
+that girlish air. More than once did he put forth the faint blossom
+of a look, which, in any other man, would have soon flowered out in a
+smile.
+
+
+
+CHAPTER 29
+
+Enter Ahab; to Him, Stubb.
+
+
+Some days elapsed, and ice and icebergs all astern, the Pequod now
+went rolling through the bright Quito spring, which, at sea, almost
+perpetually reigns on the threshold of the eternal August of the
+Tropic. The warmly cool, clear, ringing, perfumed, overflowing,
+redundant days, were as crystal goblets of Persian sherbet, heaped
+up--flaked up, with rose-water snow. The starred and stately nights
+seemed haughty dames in jewelled velvets, nursing at home in lonely
+pride, the memory of their absent conquering Earls, the golden
+helmeted suns! For sleeping man, 'twas hard to choose between such
+winsome days and such seducing nights. But all the witcheries of
+that unwaning weather did not merely lend new spells and potencies to
+the outward world. Inward they turned upon the soul, especially when
+the still mild hours of eve came on; then, memory shot her crystals
+as the clear ice most forms of noiseless twilights. And all these
+subtle agencies, more and more they wrought on Ahab's texture.
+
+Old age is always wakeful; as if, the longer linked with life, the
+less man has to do with aught that looks like death. Among
+sea-commanders, the old greybeards will oftenest leave their berths
+to visit the night-cloaked deck. It was so with Ahab; only that now,
+of late, he seemed so much to live in the open air, that truly
+speaking, his visits were more to the cabin, than from the cabin to
+the planks. "It feels like going down into one's tomb,"--he would
+mutter to himself--"for an old captain like me to be descending this
+narrow scuttle, to go to my grave-dug berth."
+
+So, almost every twenty-four hours, when the watches of the night
+were set, and the band on deck sentinelled the slumbers of the band
+below; and when if a rope was to be hauled upon the forecastle, the
+sailors flung it not rudely down, as by day, but with some
+cautiousness dropt it to its place for fear of disturbing their
+slumbering shipmates; when this sort of steady quietude would begin
+to prevail, habitually, the silent steersman would watch the
+cabin-scuttle; and ere long the old man would emerge, gripping at the
+iron banister, to help his crippled way. Some considering touch of
+humanity was in him; for at times like these, he usually abstained
+from patrolling the quarter-deck; because to his wearied mates,
+seeking repose within six inches of his ivory heel, such would have
+been the reverberating crack and din of that bony step, that their
+dreams would have been on the crunching teeth of sharks. But once,
+the mood was on him too deep for common regardings; and as with
+heavy, lumber-like pace he was measuring the ship from taffrail to
+mainmast, Stubb, the old second mate, came up from below, with a
+certain unassured, deprecating humorousness, hinted that if Captain
+Ahab was pleased to walk the planks, then, no one could say nay; but
+there might be some way of muffling the noise; hinting something
+indistinctly and hesitatingly about a globe of tow, and the insertion
+into it, of the ivory heel. Ah! Stubb, thou didst not know Ahab
+then.
+
+"Am I a cannon-ball, Stubb," said Ahab, "that thou wouldst wad me
+that fashion? But go thy ways; I had forgot. Below to thy nightly
+grave; where such as ye sleep between shrouds, to use ye to the
+filling one at last.--Down, dog, and kennel!"
+
+Starting at the unforseen concluding exclamation of the so suddenly
+scornful old man, Stubb was speechless a moment; then said excitedly,
+"I am not used to be spoken to that way, sir; I do but less than half
+like it, sir."
+
+"Avast! gritted Ahab between his set teeth, and violently moving
+away, as if to avoid some passionate temptation.
+
+"No, sir; not yet," said Stubb, emboldened, "I will not tamely be
+called a dog, sir."
+
+"Then be called ten times a donkey, and a mule, and an ass, and
+begone, or I'll clear the world of thee!"
+
+As he said this, Ahab advanced upon him with such overbearing terrors
+in his aspect, that Stubb involuntarily retreated.
+
+"I was never served so before without giving a hard blow for it,"
+muttered Stubb, as he found himself descending the cabin-scuttle.
+"It's very queer. Stop, Stubb; somehow, now, I don't well know
+whether to go back and strike him, or--what's that?--down here on my
+knees and pray for him? Yes, that was the thought coming up in me;
+but it would be the first time I ever DID pray. It's queer; very
+queer; and he's queer too; aye, take him fore and aft, he's about the
+queerest old man Stubb ever sailed with. How he flashed at me!--his
+eyes like powder-pans! is he mad? Anyway there's something on his
+mind, as sure as there must be something on a deck when it cracks.
+He aint in his bed now, either, more than three hours out of the
+twenty-four; and he don't sleep then. Didn't that Dough-Boy, the
+steward, tell me that of a morning he always finds the old man's
+hammock clothes all rumpled and tumbled, and the sheets down at the
+foot, and the coverlid almost tied into knots, and the pillow a sort
+of frightful hot, as though a baked brick had been on it? A hot old
+man! I guess he's got what some folks ashore call a conscience; it's
+a kind of Tic-Dolly-row they say--worse nor a toothache. Well, well;
+I don't know what it is, but the Lord keep me from catching it. He's
+full of riddles; I wonder what he goes into the after hold for, every
+night, as Dough-Boy tells me he suspects; what's that for, I should
+like to know? Who's made appointments with him in the hold? Ain't
+that queer, now? But there's no telling, it's the old game--Here
+goes for a snooze. Damn me, it's worth a fellow's while to be born
+into the world, if only to fall right asleep. And now that I think
+of it, that's about the first thing babies do, and that's a sort of
+queer, too. Damn me, but all things are queer, come to think of 'em.
+But that's against my principles. Think not, is my eleventh
+commandment; and sleep when you can, is my twelfth--So here goes
+again. But how's that? didn't he call me a dog? blazes! he called me
+ten times a donkey, and piled a lot of jackasses on top of THAT! He
+might as well have kicked me, and done with it. Maybe he DID kick
+me, and I didn't observe it, I was so taken all aback with his brow,
+somehow. It flashed like a bleached bone. What the devil's the
+matter with me? I don't stand right on my legs. Coming afoul of
+that old man has a sort of turned me wrong side out. By the Lord, I
+must have been dreaming, though--How? how? how?--but the only way's
+to stash it; so here goes to hammock again; and in the morning, I'll
+see how this plaguey juggling thinks over by daylight."
+
+
+
+CHAPTER 30
+
+The Pipe.
+
+
+When Stubb had departed, Ahab stood for a while leaning over the
+bulwarks; and then, as had been usual with him of late, calling a
+sailor of the watch, he sent him below for his ivory stool, and also
+his pipe. Lighting the pipe at the binnacle lamp and planting the
+stool on the weather side of the deck, he sat and smoked.
+
+In old Norse times, the thrones of the sea-loving Danish kings were
+fabricated, saith tradition, of the tusks of the narwhale. How could
+one look at Ahab then, seated on that tripod of bones, without
+bethinking him of the royalty it symbolized? For a Khan of the
+plank, and a king of the sea, and a great lord of Leviathans was
+Ahab.
+
+Some moments passed, during which the thick vapour came from his mouth
+in quick and constant puffs, which blew back again into his face.
+"How now," he soliloquized at last, withdrawing the tube, "this
+smoking no longer soothes. Oh, my pipe! hard must it go with me if
+thy charm be gone! Here have I been unconsciously toiling, not
+pleasuring--aye, and ignorantly smoking to windward all the while; to
+windward, and with such nervous whiffs, as if, like the dying whale,
+my final jets were the strongest and fullest of trouble. What
+business have I with this pipe? This thing that is meant for
+sereneness, to send up mild white vapours among mild white hairs, not
+among torn iron-grey locks like mine. I'll smoke no more--"
+
+He tossed the still lighted pipe into the sea. The fire hissed in
+the waves; the same instant the ship shot by the bubble the sinking
+pipe made. With slouched hat, Ahab lurchingly paced the planks.
+
+
+
+CHAPTER 31
+
+Queen Mab.
+
+
+Next morning Stubb accosted Flask.
+
+"Such a queer dream, King-Post, I never had. You know the old man's
+ivory leg, well I dreamed he kicked me with it; and when I tried to
+kick back, upon my soul, my little man, I kicked my leg right off!
+And then, presto! Ahab seemed a pyramid, and I, like a blazing fool,
+kept kicking at it. But what was still more curious, Flask--you know
+how curious all dreams are--through all this rage that I was in, I
+somehow seemed to be thinking to myself, that after all, it was not
+much of an insult, that kick from Ahab. 'Why,' thinks I, 'what's the
+row? It's not a real leg, only a false leg.' And there's a mighty
+difference between a living thump and a dead thump. That's what
+makes a blow from the hand, Flask, fifty times more savage to bear
+than a blow from a cane. The living member--that makes the living
+insult, my little man. And thinks I to myself all the while, mind,
+while I was stubbing my silly toes against that cursed pyramid--so
+confoundedly contradictory was it all, all the while, I say, I was
+thinking to myself, 'what's his leg now, but a cane--a whalebone
+cane. Yes,' thinks I, 'it was only a playful cudgelling--in fact,
+only a whaleboning that he gave me--not a base kick. Besides,'
+thinks I, 'look at it once; why, the end of it--the foot part--what a
+small sort of end it is; whereas, if a broad footed farmer kicked me,
+THERE'S a devilish broad insult. But this insult is whittled down to
+a point only.' But now comes the greatest joke of the dream, Flask.
+While I was battering away at the pyramid, a sort of badger-haired
+old merman, with a hump on his back, takes me by the shoulders, and
+slews me round. 'What are you 'bout?' says he. Slid! man, but I was
+frightened. Such a phiz! But, somehow, next moment I was over the
+fright. 'What am I about?' says I at last. 'And what business is
+that of yours, I should like to know, Mr. Humpback? Do YOU want a
+kick?' By the lord, Flask, I had no sooner said that, than he turned
+round his stern to me, bent over, and dragging up a lot of seaweed he
+had for a clout--what do you think, I saw?--why thunder alive, man,
+his stern was stuck full of marlinspikes, with the points out. Says
+I, on second thoughts, 'I guess I won't kick you, old fellow.' 'Wise
+Stubb,' said he, 'wise Stubb;' and kept muttering it all the time, a
+sort of eating of his own gums like a chimney hag. Seeing he wasn't
+going to stop saying over his 'wise Stubb, wise Stubb,' I thought I
+might as well fall to kicking the pyramid again. But I had only just
+lifted my foot for it, when he roared out, 'Stop that kicking!'
+'Halloa,' says I, 'what's the matter now, old fellow?' 'Look ye
+here,' says he; 'let's argue the insult. Captain Ahab kicked ye,
+didn't he?' 'Yes, he did,' says I--'right HERE it was.' 'Very
+good,' says he--'he used his ivory leg, didn't he?' 'Yes, he did,'
+says I. 'Well then,' says he, 'wise Stubb, what have you to complain
+of? Didn't he kick with right good will? it wasn't a common pitch
+pine leg he kicked with, was it? No, you were kicked by a great man,
+and with a beautiful ivory leg, Stubb. It's an honour; I consider it
+an honour. Listen, wise Stubb. In old England the greatest lords
+think it great glory to be slapped by a queen, and made
+garter-knights of; but, be YOUR boast, Stubb, that ye were kicked by
+old Ahab, and made a wise man of. Remember what I say; BE kicked by
+him; account his kicks honours; and on no account kick back; for you
+can't help yourself, wise Stubb. Don't you see that pyramid?' With
+that, he all of a sudden seemed somehow, in some queer fashion, to
+swim off into the air. I snored; rolled over; and there I was in my
+hammock! Now, what do you think of that dream, Flask?"
+
+"I don't know; it seems a sort of foolish to me, tho.'"
+
+"May be; may be. But it's made a wise man of me, Flask. D'ye see
+Ahab standing there, sideways looking over the stern? Well, the best
+thing you can do, Flask, is to let the old man alone; never speak to
+him, whatever he says. Halloa! What's that he shouts? Hark!"
+
+"Mast-head, there! Look sharp, all of ye! There are whales
+hereabouts!
+
+If ye see a white one, split your lungs for him!
+
+"What do you think of that now, Flask? ain't there a small drop of
+something queer about that, eh? A white whale--did ye mark that,
+man? Look ye--there's something special in the wind. Stand by for
+it, Flask. Ahab has that that's bloody on his mind. But, mum; he
+comes this way."
+
+
+
+CHAPTER 32
+
+Cetology.
+
+
+Already we are boldly launched upon the deep; but soon we shall be
+lost in its unshored, harbourless immensities. Ere that come to pass;
+ere the Pequod's weedy hull rolls side by side with the barnacled
+hulls of the leviathan; at the outset it is but well to attend to a
+matter almost indispensable to a thorough appreciative understanding
+of the more special leviathanic revelations and allusions of all
+sorts which are to follow.
+
+It is some systematized exhibition of the whale in his broad genera,
+that I would now fain put before you. Yet is it no easy task. The
+classification of the constituents of a chaos, nothing less is here
+essayed. Listen to what the best and latest authorities have laid
+down.
+
+"No branch of Zoology is so much involved as that which is entitled
+Cetology," says Captain Scoresby, A.D. 1820.
+
+"It is not my intention, were it in my power, to enter into the
+inquiry as to the true method of dividing the cetacea into groups and
+families.... Utter confusion exists among the historians of this
+animal" (sperm whale), says Surgeon Beale, A.D. 1839.
+
+"Unfitness to pursue our research in the unfathomable waters."
+"Impenetrable veil covering our knowledge of the cetacea." "A field
+strewn with thorns." "All these incomplete indications but serve to
+torture us naturalists."
+
+Thus speak of the whale, the great Cuvier, and John Hunter, and
+Lesson, those lights of zoology and anatomy. Nevertheless, though of
+real knowledge there be little, yet of books there are a plenty; and
+so in some small degree, with cetology, or the science of whales.
+Many are the men, small and great, old and new, landsmen and seamen,
+who have at large or in little, written of the whale. Run over a
+few:--The Authors of the Bible; Aristotle; Pliny; Aldrovandi; Sir
+Thomas Browne; Gesner; Ray; Linnaeus; Rondeletius; Willoughby; Green;
+Artedi; Sibbald; Brisson; Marten; Lacepede; Bonneterre; Desmarest;
+Baron Cuvier; Frederick Cuvier; John Hunter; Owen; Scoresby; Beale;
+Bennett; J. Ross Browne; the Author of Miriam Coffin; Olmstead; and
+the Rev. T. Cheever. But to what ultimate generalizing purpose all
+these have written, the above cited extracts will show.
+
+Of the names in this list of whale authors, only those following Owen
+ever saw living whales; and but one of them was a real professional
+harpooneer and whaleman. I mean Captain Scoresby. On the separate
+subject of the Greenland or right-whale, he is the best existing
+authority. But Scoresby knew nothing and says nothing of the great
+sperm whale, compared with which the Greenland whale is almost
+unworthy mentioning. And here be it said, that the Greenland whale
+is an usurper upon the throne of the seas. He is not even by any
+means the largest of the whales. Yet, owing to the long priority of
+his claims, and the profound ignorance which, till some seventy years
+back, invested the then fabulous or utterly unknown sperm-whale, and
+which ignorance to this present day still reigns in all but some few
+scientific retreats and whale-ports; this usurpation has been every
+way complete. Reference to nearly all the leviathanic allusions in
+the great poets of past days, will satisfy you that the Greenland
+whale, without one rival, was to them the monarch of the seas. But
+the time has at last come for a new proclamation. This is Charing
+Cross; hear ye! good people all,--the Greenland whale is
+deposed,--the great sperm whale now reigneth!
+
+There are only two books in being which at all pretend to put the
+living sperm whale before you, and at the same time, in the remotest
+degree succeed in the attempt. Those books are Beale's and
+Bennett's; both in their time surgeons to English South-Sea
+whale-ships, and both exact and reliable men. The original matter
+touching the sperm whale to be found in their volumes is necessarily
+small; but so far as it goes, it is of excellent quality, though
+mostly confined to scientific description. As yet, however, the
+sperm whale, scientific or poetic, lives not complete in any
+literature. Far above all other hunted whales, his is an unwritten
+life.
+
+Now the various species of whales need some sort of popular
+comprehensive classification, if only an easy outline one for the
+present, hereafter to be filled in all its departments by subsequent
+laborers. As no better man advances to take this matter in hand, I
+hereupon offer my own poor endeavors. I promise nothing complete;
+because any human thing supposed to be complete, must for that very
+reason infallibly be faulty. I shall not pretend to a minute
+anatomical description of the various species, or--in this place at
+least--to much of any description. My object here is simply to
+project the draught of a systematization of cetology. I am the
+architect, not the builder.
+
+But it is a ponderous task; no ordinary letter-sorter in the
+Post-Office is equal to it. To grope down into the bottom of the sea
+after them; to have one's hands among the unspeakable foundations,
+ribs, and very pelvis of the world; this is a fearful thing. What am
+I that I should essay to hook the nose of this leviathan! The awful
+tauntings in Job might well appal me. "Will he the (leviathan) make
+a covenant with thee? Behold the hope of him is vain! But I have
+swam through libraries and sailed through oceans; I have had to do
+with whales with these visible hands; I am in earnest; and I will
+try. There are some preliminaries to settle.
+
+First: The uncertain, unsettled condition of this science of Cetology
+is in the very vestibule attested by the fact, that in some quarters
+it still remains a moot point whether a whale be a fish. In his
+System of Nature, A.D. 1776, Linnaeus declares, "I hereby separate
+the whales from the fish." But of my own knowledge, I know that down
+to the year 1850, sharks and shad, alewives and herring, against
+Linnaeus's express edict, were still found dividing the possession of
+the same seas with the Leviathan.
+
+The grounds upon which Linnaeus would fain have banished the whales
+from the waters, he states as follows: "On account of their warm
+bilocular heart, their lungs, their movable eyelids, their hollow
+ears, penem intrantem feminam mammis lactantem," and finally, "ex
+lege naturae jure meritoque." I submitted all this to my friends
+Simeon Macey and Charley Coffin, of Nantucket, both messmates of mine
+in a certain voyage, and they united in the opinion that the reasons
+set forth were altogether insufficient. Charley profanely hinted
+they were humbug.
+
+Be it known that, waiving all argument, I take the good old fashioned
+ground that the whale is a fish, and call upon holy Jonah to back me.
+This fundamental thing settled, the next point is, in what internal
+respect does the whale differ from other fish. Above, Linnaeus has
+given you those items. But in brief, they are these: lungs and warm
+blood; whereas, all other fish are lungless and cold blooded.
+
+Next: how shall we define the whale, by his obvious externals, so as
+conspicuously to label him for all time to come? To be short, then,
+a whale is A SPOUTING FISH WITH A HORIZONTAL TAIL. There you have
+him. However contracted, that definition is the result of expanded
+meditation. A walrus spouts much like a whale, but the walrus is not
+a fish, because he is amphibious. But the last term of the
+definition is still more cogent, as coupled with the first. Almost
+any one must have noticed that all the fish familiar to landsmen have
+not a flat, but a vertical, or up-and-down tail. Whereas, among
+spouting fish the tail, though it may be similarly shaped, invariably
+assumes a horizontal position.
+
+By the above definition of what a whale is, I do by no means exclude
+from the leviathanic brotherhood any sea creature hitherto identified
+with the whale by the best informed Nantucketers; nor, on the other
+hand, link with it any fish hitherto authoritatively regarded as
+alien.* Hence, all the smaller, spouting, and horizontal tailed fish
+must be included in this ground-plan of Cetology. Now, then, come
+the grand divisions of the entire whale host.
+
+
+*I am aware that down to the present time, the fish styled Lamatins
+and Dugongs (Pig-fish and Sow-fish of the Coffins of Nantucket) are
+included by many naturalists among the whales. But as these pig-fish
+are a noisy, contemptible set, mostly lurking in the mouths of
+rivers, and feeding on wet hay, and especially as they do not spout,
+I deny their credentials as whales; and have presented them with
+their passports to quit the Kingdom of Cetology.
+
+
+First: According to magnitude I divide the whales into three primary
+BOOKS (subdivisible into CHAPTERS), and these shall comprehend them
+all, both small and large.
+
+I. THE FOLIO WHALE; II. the OCTAVO WHALE; III. the DUODECIMO WHALE.
+
+As the type of the FOLIO I present the SPERM WHALE; of the OCTAVO,
+the GRAMPUS; of the DUODECIMO, the PORPOISE.
+
+FOLIOS. Among these I here include the following chapters:--I. The
+SPERM WHALE; II. the RIGHT WHALE; III. the FIN-BACK WHALE; IV. the
+HUMP-BACKED WHALE; V. the RAZOR-BACK WHALE; VI. the SULPHUR-BOTTOM
+WHALE.
+
+BOOK I. (FOLIO), CHAPTER I. (SPERM WHALE).--This whale, among the
+English of old vaguely known as the Trumpa whale, and the Physeter
+whale, and the Anvil Headed whale, is the present Cachalot of the
+French, and the Pottsfich of the Germans, and the Macrocephalus of
+the Long Words. He is, without doubt, the largest inhabitant of the
+globe; the most formidable of all whales to encounter; the most
+majestic in aspect; and lastly, by far the most valuable in commerce;
+he being the only creature from which that valuable substance,
+spermaceti, is obtained. All his peculiarities will, in many other
+places, be enlarged upon. It is chiefly with his name that I now
+have to do. Philologically considered, it is absurd. Some centuries
+ago, when the Sperm whale was almost wholly unknown in his own
+proper individuality, and when his oil was only accidentally obtained
+from the stranded fish; in those days spermaceti, it would seem, was
+popularly supposed to be derived from a creature identical with the
+one then known in England as the Greenland or Right Whale. It was
+the idea also, that this same spermaceti was that quickening humor of
+the Greenland Whale which the first syllable of the word literally
+expresses. In those times, also, spermaceti was exceedingly scarce,
+not being used for light, but only as an ointment and medicament. It
+was only to be had from the druggists as you nowadays buy an ounce of
+rhubarb. When, as I opine, in the course of time, the true nature of
+spermaceti became known, its original name was still retained by the
+dealers; no doubt to enhance its value by a notion so strangely
+significant of its scarcity. And so the appellation must at last
+have come to be bestowed upon the whale from which this spermaceti
+was really derived.
+
+BOOK I. (FOLIO), CHAPTER II. (RIGHT WHALE).--In one respect this is
+the most venerable of the leviathans, being the one first regularly
+hunted by man. It yields the article commonly known as whalebone or
+baleen; and the oil specially known as "whale oil," an inferior
+article in commerce. Among the fishermen, he is indiscriminately
+designated by all the following titles: The Whale; the Greenland
+Whale; the Black Whale; the Great Whale; the True Whale; the Right
+Whale. There is a deal of obscurity concerning the identity of the
+species thus multitudinously baptised. What then is the whale, which
+I include in the second species of my Folios? It is the Great
+Mysticetus of the English naturalists; the Greenland Whale of the
+English whalemen; the Baliene Ordinaire of the French whalemen; the
+Growlands Walfish of the Swedes. It is the whale which for more than
+two centuries past has been hunted by the Dutch and English in the
+Arctic seas; it is the whale which the American fishermen have long
+pursued in the Indian ocean, on the Brazil Banks, on the Nor' West
+Coast, and various other parts of the world, designated by them Right
+Whale Cruising Grounds.
+
+Some pretend to see a difference between the Greenland whale of the
+English and the right whale of the Americans. But they precisely
+agree in all their grand features; nor has there yet been presented a
+single determinate fact upon which to ground a radical distinction.
+It is by endless subdivisions based upon the most inconclusive
+differences, that some departments of natural history become so
+repellingly intricate. The right whale will be elsewhere treated of
+at some length, with reference to elucidating the sperm whale.
+
+BOOK I. (FOLIO), CHAPTER III. (FIN-BACK).--Under this head I reckon a
+monster which, by the various names of Fin-Back, Tall-Spout, and
+Long-John, has been seen almost in every sea and is commonly the
+whale whose distant jet is so often descried by passengers crossing
+the Atlantic, in the New York packet-tracks. In the length he
+attains, and in his baleen, the Fin-back resembles the right whale,
+but is of a less portly girth, and a lighter colour, approaching to
+olive. His great lips present a cable-like aspect, formed by the
+intertwisting, slanting folds of large wrinkles. His grand
+distinguishing feature, the fin, from which he derives his name, is
+often a conspicuous object. This fin is some three or four feet
+long, growing vertically from the hinder part of the back, of an
+angular shape, and with a very sharp pointed end. Even if not the
+slightest other part of the creature be visible, this isolated fin
+will, at times, be seen plainly projecting from the surface. When
+the sea is moderately calm, and slightly marked with spherical
+ripples, and this gnomon-like fin stands up and casts shadows upon
+the wrinkled surface, it may well be supposed that the watery circle
+surrounding it somewhat resembles a dial, with its style and wavy
+hour-lines graved on it. On that Ahaz-dial the shadow often goes
+back. The Fin-Back is not gregarious. He seems a whale-hater, as
+some men are man-haters. Very shy; always going solitary;
+unexpectedly rising to the surface in the remotest and most sullen
+waters; his straight and single lofty jet rising like a tall
+misanthropic spear upon a barren plain; gifted with such wondrous
+power and velocity in swimming, as to defy all present pursuit from
+man; this leviathan seems the banished and unconquerable Cain of his
+race, bearing for his mark that style upon his back. From having the
+baleen in his mouth, the Fin-Back is sometimes included with the
+right whale, among a theoretic species denominated WHALEBONE WHALES,
+that is, whales with baleen. Of these so called Whalebone whales,
+there would seem to be several varieties, most of which, however, are
+little known. Broad-nosed whales and beaked whales; pike-headed
+whales; bunched whales; under-jawed whales and rostrated whales, are
+the fishermen's names for a few sorts.
+
+In connection with this appellative of "Whalebone whales," it is of
+great importance to mention, that however such a nomenclature may be
+convenient in facilitating allusions to some kind of whales, yet it
+is in vain to attempt a clear classification of the Leviathan,
+founded upon either his baleen, or hump, or fin, or teeth;
+notwithstanding that those marked parts or features very obviously
+seem better adapted to afford the basis for a regular system of
+Cetology than any other detached bodily distinctions, which the
+whale, in his kinds, presents. How then? The baleen, hump,
+back-fin, and teeth; these are things whose peculiarities are
+indiscriminately dispersed among all sorts of whales, without any
+regard to what may be the nature of their structure in other and
+more essential particulars. Thus, the sperm whale and the humpbacked
+whale, each has a hump; but there the similitude ceases. Then, this
+same humpbacked whale and the Greenland whale, each of these has
+baleen; but there again the similitude ceases. And it is just the
+same with the other parts above mentioned. In various sorts of
+whales, they form such irregular combinations; or, in the case of any
+one of them detached, such an irregular isolation; as utterly to defy
+all general methodization formed upon such a basis. On this rock
+every one of the whale-naturalists has split.
+
+But it may possibly be conceived that, in the internal parts of the
+whale, in his anatomy--there, at least, we shall be able to hit the
+right classification. Nay; what thing, for example, is there in the
+Greenland whale's anatomy more striking than his baleen? Yet we have
+seen that by his baleen it is impossible correctly to classify the
+Greenland whale. And if you descend into the bowels of the various
+leviathans, why there you will not find distinctions a fiftieth part
+as available to the systematizer as those external ones already
+enumerated. What then remains? nothing but to take hold of the
+whales bodily, in their entire liberal volume, and boldly sort them
+that way. And this is the Bibliographical system here adopted; and
+it is the only one that can possibly succeed, for it alone is
+practicable. To proceed.
+
+BOOK I. (FOLIO) CHAPTER IV. (HUMP-BACK).--This whale is often seen on
+the northern American coast. He has been frequently captured there,
+and towed into harbor. He has a great pack on him like a peddler; or
+you might call him the Elephant and Castle whale. At any rate, the
+popular name for him does not sufficiently distinguish him, since the
+sperm whale also has a hump though a smaller one. His oil is not
+very valuable. He has baleen. He is the most gamesome and
+light-hearted of all the whales, making more gay foam and white water
+generally than any other of them.
+
+BOOK I. (FOLIO), CHAPTER V. (RAZOR-BACK).--Of this whale little is
+known but his name. I have seen him at a distance off Cape Horn. Of
+a retiring nature, he eludes both hunters and philosophers. Though
+no coward, he has never yet shown any part of him but his back, which
+rises in a long sharp ridge. Let him go. I know little more of him,
+nor does anybody else.
+
+BOOK I. (FOLIO), CHAPTER VI. (SULPHUR-BOTTOM).--Another retiring
+gentleman, with a brimstone belly, doubtless got by scraping along
+the Tartarian tiles in some of his profounder divings. He is seldom
+seen; at least I have never seen him except in the remoter southern
+seas, and then always at too great a distance to study his
+countenance. He is never chased; he would run away with rope-walks
+of line. Prodigies are told of him. Adieu, Sulphur Bottom! I can
+say nothing more that is true of ye, nor can the oldest Nantucketer.
+
+Thus ends BOOK I. (FOLIO), and now begins BOOK II. (OCTAVO).
+
+OCTAVOES.*--These embrace the whales of middling magnitude, among
+which present may be numbered:--I., the GRAMPUS; II., the BLACK FISH;
+III., the NARWHALE; IV., the THRASHER; V., the KILLER.
+
+
+*Why this book of whales is not denominated the Quarto is very plain.
+Because, while the whales of this order, though smaller than those
+of the former order, nevertheless retain a proportionate likeness to
+them in figure, yet the bookbinder's Quarto volume in its dimensioned
+form does not preserve the shape of the Folio volume, but the Octavo
+volume does.
+
+
+BOOK II. (OCTAVO), CHAPTER I. (GRAMPUS).--Though this fish, whose
+loud sonorous breathing, or rather blowing, has furnished a proverb
+to landsmen, is so well known a denizen of the deep, yet is he not
+popularly classed among whales. But possessing all the grand
+distinctive features of the leviathan, most naturalists have
+recognised him for one. He is of moderate octavo size, varying from
+fifteen to twenty-five feet in length, and of corresponding
+dimensions round the waist. He swims in herds; he is never regularly
+hunted, though his oil is considerable in quantity, and pretty good
+for light. By some fishermen his approach is regarded as premonitory
+of the advance of the great sperm whale.
+
+BOOK II. (OCTAVO), CHAPTER II. (BLACK FISH).--I give the popular
+fishermen's names for all these fish, for generally they are the
+best. Where any name happens to be vague or inexpressive, I shall
+say so, and suggest another. I do so now, touching the Black Fish,
+so-called, because blackness is the rule among almost all whales.
+So, call him the Hyena Whale, if you please. His voracity is well
+known, and from the circumstance that the inner angles of his lips
+are curved upwards, he carries an everlasting Mephistophelean grin on
+his face. This whale averages some sixteen or eighteen feet in
+length. He is found in almost all latitudes. He has a peculiar way
+of showing his dorsal hooked fin in swimming, which looks something
+like a Roman nose. When not more profitably employed, the sperm
+whale hunters sometimes capture the Hyena whale, to keep up the
+supply of cheap oil for domestic employment--as some frugal
+housekeepers, in the absence of company, and quite alone by
+themselves, burn unsavory tallow instead of odorous wax. Though
+their blubber is very thin, some of these whales will yield you
+upwards of thirty gallons of oil.
+
+BOOK II. (OCTAVO), CHAPTER III. (NARWHALE), that is, NOSTRIL
+WHALE.--Another instance of a curiously named whale, so named I
+suppose from his peculiar horn being originally mistaken for a peaked
+nose. The creature is some sixteen feet in length, while its horn
+averages five feet, though some exceed ten, and even attain to
+fifteen feet. Strictly speaking, this horn is but a lengthened tusk,
+growing out from the jaw in a line a little depressed from the
+horizontal. But it is only found on the sinister side, which has an
+ill effect, giving its owner something analogous to the aspect of a
+clumsy left-handed man. What precise purpose this ivory horn or
+lance answers, it would be hard to say. It does not seem to be used
+like the blade of the sword-fish and bill-fish; though some sailors
+tell me that the Narwhale employs it for a rake in turning over the
+bottom of the sea for food. Charley Coffin said it was used for an
+ice-piercer; for the Narwhale, rising to the surface of the Polar
+Sea, and finding it sheeted with ice, thrusts his horn up, and so
+breaks through. But you cannot prove either of these surmises to be
+correct. My own opinion is, that however this one-sided horn may
+really be used by the Narwhale--however that may be--it would
+certainly be very convenient to him for a folder in reading
+pamphlets. The Narwhale I have heard called the Tusked whale, the
+Horned whale, and the Unicorn whale. He is certainly a curious
+example of the Unicornism to be found in almost every kingdom of
+animated nature. From certain cloistered old authors I have gathered
+that this same sea-unicorn's horn was in ancient days regarded as the
+great antidote against poison, and as such, preparations of it
+brought immense prices. It was also distilled to a volatile salts
+for fainting ladies, the same way that the horns of the male deer are
+manufactured into hartshorn. Originally it was in itself accounted
+an object of great curiosity. Black Letter tells me that Sir Martin
+Frobisher on his return from that voyage, when Queen Bess did
+gallantly wave her jewelled hand to him from a window of Greenwich
+Palace, as his bold ship sailed down the Thames; "when Sir Martin
+returned from that voyage," saith Black Letter, "on bended knees he
+presented to her highness a prodigious long horn of the Narwhale,
+which for a long period after hung in the castle at Windsor." An
+Irish author avers that the Earl of Leicester, on bended knees, did
+likewise present to her highness another horn, pertaining to a land
+beast of the unicorn nature.
+
+The Narwhale has a very picturesque, leopard-like look, being of a
+milk-white ground colour, dotted with round and oblong spots of black.
+His oil is very superior, clear and fine; but there is little of it,
+and he is seldom hunted. He is mostly found in the circumpolar seas.
+
+BOOK II. (OCTAVO), CHAPTER IV. (KILLER).--Of this whale little is
+precisely known to the Nantucketer, and nothing at all to the
+professed naturalist. From what I have seen of him at a distance,
+I should say that he was about the bigness of a grampus. He is very
+savage--a sort of Feegee fish. He sometimes takes the great Folio
+whales by the lip, and hangs there like a leech, till the mighty
+brute is worried to death. The Killer is never hunted. I never
+heard what sort of oil he has. Exception might be taken to the name
+bestowed upon this whale, on the ground of its indistinctness. For
+we are all killers, on land and on sea; Bonapartes and Sharks
+included.
+
+BOOK II. (OCTAVO), CHAPTER V. (THRASHER).--This gentleman is famous
+for his tail, which he uses for a ferule in thrashing his foes. He
+mounts the Folio whale's back, and as he swims, he works his passage
+by flogging him; as some schoolmasters get along in the world by a
+similar process. Still less is known of the Thrasher than of the
+Killer. Both are outlaws, even in the lawless seas.
+
+Thus ends BOOK II. (OCTAVO), and begins BOOK III. (DUODECIMO).
+
+DUODECIMOES.--These include the smaller whales. I. The Huzza
+Porpoise. II. The Algerine Porpoise. III. The Mealy-mouthed
+Porpoise.
+
+To those who have not chanced specially to study the subject, it may
+possibly seem strange, that fishes not commonly exceeding four or
+five feet should be marshalled among WHALES--a word, which, in the
+popular sense, always conveys an idea of hugeness. But the creatures
+set down above as Duodecimoes are infallibly whales, by the terms of
+my definition of what a whale is--i.e. a spouting fish, with a
+horizontal tail.
+
+BOOK III. (DUODECIMO), CHAPTER 1. (HUZZA PORPOISE).--This is the
+common porpoise found almost all over the globe. The name is of my
+own bestowal; for there are more than one sort of porpoises, and
+something must be done to distinguish them. I call him thus, because
+he always swims in hilarious shoals, which upon the broad sea keep
+tossing themselves to heaven like caps in a Fourth-of-July crowd.
+Their appearance is generally hailed with delight by the mariner.
+Full of fine spirits, they invariably come from the breezy billows to
+windward. They are the lads that always live before the wind. They
+are accounted a lucky omen. If you yourself can withstand three
+cheers at beholding these vivacious fish, then heaven help ye; the
+spirit of godly gamesomeness is not in ye. A well-fed, plump Huzza
+Porpoise will yield you one good gallon of good oil. But the fine
+and delicate fluid extracted from his jaws is exceedingly valuable.
+It is in request among jewellers and watchmakers. Sailors put it on
+their hones. Porpoise meat is good eating, you know. It may never
+have occurred to you that a porpoise spouts. Indeed, his spout is so
+small that it is not very readily discernible. But the next time you
+have a chance, watch him; and you will then see the great Sperm whale
+himself in miniature.
+
+BOOK III. (DUODECIMO), CHAPTER II. (ALGERINE PORPOISE).--A pirate.
+Very savage. He is only found, I think, in the Pacific. He is
+somewhat larger than the Huzza Porpoise, but much of the same general
+make. Provoke him, and he will buckle to a shark. I have lowered
+for him many times, but never yet saw him captured.
+
+BOOK III. (DUODECIMO), CHAPTER III. (MEALY-MOUTHED PORPOISE).--The
+largest kind of Porpoise; and only found in the Pacific, so far as it
+is known. The only English name, by which he has hitherto been
+designated, is that of the fishers--Right-Whale Porpoise, from the
+circumstance that he is chiefly found in the vicinity of that Folio.
+In shape, he differs in some degree from the Huzza Porpoise, being of
+a less rotund and jolly girth; indeed, he is of quite a neat and
+gentleman-like figure. He has no fins on his back (most other
+porpoises have), he has a lovely tail, and sentimental Indian eyes of
+a hazel hue. But his mealy-mouth spoils all. Though his entire
+back down to his side fins is of a deep sable, yet a boundary line,
+distinct as the mark in a ship's hull, called the "bright waist,"
+that line streaks him from stem to stern, with two separate colours,
+black above and white below. The white comprises part of his head,
+and the whole of his mouth, which makes him look as if he had just
+escaped from a felonious visit to a meal-bag. A most mean and mealy
+aspect! His oil is much like that of the common porpoise.
+
+
+Beyond the DUODECIMO, this system does not proceed, inasmuch as the
+Porpoise is the smallest of the whales. Above, you have all the
+Leviathans of note. But there are a rabble of uncertain, fugitive,
+half-fabulous whales, which, as an American whaleman, I know by
+reputation, but not personally. I shall enumerate them by their
+fore-castle appellations; for possibly such a list may be valuable to
+future investigators, who may complete what I have here but begun.
+If any of the following whales, shall hereafter be caught and marked,
+then he can readily be incorporated into this System, according to
+his Folio, Octavo, or Duodecimo magnitude:--The Bottle-Nose Whale;
+the Junk Whale; the Pudding-Headed Whale; the Cape Whale; the Leading
+Whale; the Cannon Whale; the Scragg Whale; the Coppered Whale; the
+Elephant Whale; the Iceberg Whale; the Quog Whale; the Blue Whale; etc.
+From Icelandic, Dutch, and old English authorities, there might
+be quoted other lists of uncertain whales, blessed with all manner of
+uncouth names. But I omit them as altogether obsolete; and can
+hardly help suspecting them for mere sounds, full of Leviathanism,
+but signifying nothing.
+
+Finally: It was stated at the outset, that this system would not be
+here, and at once, perfected. You cannot but plainly see that I have
+kept my word. But I now leave my cetological System standing thus
+unfinished, even as the great Cathedral of Cologne was left, with the
+crane still standing upon the top of the uncompleted tower. For
+small erections may be finished by their first architects; grand
+ones, true ones, ever leave the copestone to posterity. God keep me
+from ever completing anything. This whole book is but a
+draught--nay, but the draught of a draught. Oh, Time, Strength,
+Cash, and Patience!
+
+
+
+CHAPTER 33
+
+The Specksynder.
+
+
+Concerning the officers of the whale-craft, this seems as good a
+place as any to set down a little domestic peculiarity on ship-board,
+arising from the existence of the harpooneer class of officers, a
+class unknown of course in any other marine than the whale-fleet.
+
+The large importance attached to the harpooneer's vocation is evinced
+by the fact, that originally in the old Dutch Fishery, two centuries
+and more ago, the command of a whale ship was not wholly lodged in
+the person now called the captain, but was divided between him and an
+officer called the Specksynder. Literally this word means
+Fat-Cutter; usage, however, in time made it equivalent to Chief
+Harpooneer. In those days, the captain's authority was restricted to
+the navigation and general management of the vessel; while over the
+whale-hunting department and all its concerns, the Specksynder or
+Chief Harpooneer reigned supreme. In the British Greenland Fishery,
+under the corrupted title of Specksioneer, this old Dutch official is
+still retained, but his former dignity is sadly abridged. At present
+he ranks simply as senior Harpooneer; and as such, is but one of the
+captain's more inferior subalterns. Nevertheless, as upon the good
+conduct of the harpooneers the success of a whaling voyage largely
+depends, and since in the American Fishery he is not only an
+important officer in the boat, but under certain circumstances (night
+watches on a whaling ground) the command of the ship's deck is also
+his; therefore the grand political maxim of the sea demands, that he
+should nominally live apart from the men before the mast, and be in
+some way distinguished as their professional superior; though always,
+by them, familiarly regarded as their social equal.
+
+Now, the grand distinction drawn between officer and man at sea, is
+this--the first lives aft, the last forward. Hence, in whale-ships
+and merchantmen alike, the mates have their quarters with the
+captain; and so, too, in most of the American whalers the harpooneers
+are lodged in the after part of the ship. That is to say, they take
+their meals in the captain's cabin, and sleep in a place indirectly
+communicating with it.
+
+Though the long period of a Southern whaling voyage (by far the
+longest of all voyages now or ever made by man), the peculiar perils
+of it, and the community of interest prevailing among a company, all
+of whom, high or low, depend for their profits, not upon fixed wages,
+but upon their common luck, together with their common vigilance,
+intrepidity, and hard work; though all these things do in some cases
+tend to beget a less rigorous discipline than in merchantmen
+generally; yet, never mind how much like an old Mesopotamian family
+these whalemen may, in some primitive instances, live together; for
+all that, the punctilious externals, at least, of the quarter-deck
+are seldom materially relaxed, and in no instance done away. Indeed,
+many are the Nantucket ships in which you will see the skipper
+parading his quarter-deck with an elated grandeur not surpassed in
+any military navy; nay, extorting almost as much outward homage as if
+he wore the imperial purple, and not the shabbiest of pilot-cloth.
+
+And though of all men the moody captain of the Pequod was the least
+given to that sort of shallowest assumption; and though the only
+homage he ever exacted, was implicit, instantaneous obedience; though
+he required no man to remove the shoes from his feet ere stepping
+upon the quarter-deck; and though there were times when, owing to
+peculiar circumstances connected with events hereafter to be
+detailed, he addressed them in unusual terms, whether of
+condescension or IN TERROREM, or otherwise; yet even Captain Ahab was
+by no means unobservant of the paramount forms and usages of the sea.
+
+Nor, perhaps, will it fail to be eventually perceived, that behind
+those forms and usages, as it were, he sometimes masked himself;
+incidentally making use of them for other and more private ends than
+they were legitimately intended to subserve. That certain sultanism
+of his brain, which had otherwise in a good degree remained
+unmanifested; through those forms that same sultanism became
+incarnate in an irresistible dictatorship. For be a man's
+intellectual superiority what it will, it can never assume the
+practical, available supremacy over other men, without the aid of
+some sort of external arts and entrenchments, always, in themselves,
+more or less paltry and base. This it is, that for ever keeps God's
+true princes of the Empire from the world's hustings; and leaves the
+highest honours that this air can give, to those men who become famous
+more through their infinite inferiority to the choice hidden handful
+of the Divine Inert, than through their undoubted superiority over
+the dead level of the mass. Such large virtue lurks in these small
+things when extreme political superstitions invest them, that in some
+royal instances even to idiot imbecility they have imparted potency.
+But when, as in the case of Nicholas the Czar, the ringed crown of
+geographical empire encircles an imperial brain; then, the plebeian
+herds crouch abased before the tremendous centralization. Nor, will
+the tragic dramatist who would depict mortal indomitableness in its
+fullest sweep and direct swing, ever forget a hint, incidentally so
+important in his art, as the one now alluded to.
+
+But Ahab, my Captain, still moves before me in all his Nantucket
+grimness and shagginess; and in this episode touching Emperors and
+Kings, I must not conceal that I have only to do with a poor old
+whale-hunter like him; and, therefore, all outward majestical
+trappings and housings are denied me. Oh, Ahab! what shall be grand
+in thee, it must needs be plucked at from the skies, and dived for in
+the deep, and featured in the unbodied air!
+
+
+
+CHAPTER 34
+
+The Cabin-Table.
+
+
+It is noon; and Dough-Boy, the steward, thrusting his pale
+loaf-of-bread face from the cabin-scuttle, announces dinner to his
+lord and master; who, sitting in the lee quarter-boat, has just been
+taking an observation of the sun; and is now mutely reckoning the
+latitude on the smooth, medallion-shaped tablet, reserved for that
+daily purpose on the upper part of his ivory leg. From his complete
+inattention to the tidings, you would think that moody Ahab had not
+heard his menial. But presently, catching hold of the mizen shrouds,
+he swings himself to the deck, and in an even, unexhilarated voice,
+saying, "Dinner, Mr. Starbuck," disappears into the cabin.
+
+When the last echo of his sultan's step has died away, and Starbuck,
+the first Emir, has every reason to suppose that he is seated, then
+Starbuck rouses from his quietude, takes a few turns along the
+planks, and, after a grave peep into the binnacle, says, with some
+touch of pleasantness, "Dinner, Mr. Stubb," and descends the scuttle.
+The second Emir lounges about the rigging awhile, and then slightly
+shaking the main brace, to see whether it will be all right with
+that important rope, he likewise takes up the old burden, and with a
+rapid "Dinner, Mr. Flask," follows after his predecessors.
+
+But the third Emir, now seeing himself all alone on the quarter-deck,
+seems to feel relieved from some curious restraint; for, tipping all
+sorts of knowing winks in all sorts of directions, and kicking off
+his shoes, he strikes into a sharp but noiseless squall of a hornpipe
+right over the Grand Turk's head; and then, by a dexterous sleight,
+pitching his cap up into the mizentop for a shelf, he goes down
+rollicking so far at least as he remains visible from the deck,
+reversing all other processions, by bringing up the rear with music.
+But ere stepping into the cabin doorway below, he pauses, ships a new
+face altogether, and, then, independent, hilarious little Flask
+enters King Ahab's presence, in the character of Abjectus, or the
+Slave.
+
+It is not the least among the strange things bred by the intense
+artificialness of sea-usages, that while in the open air of the deck
+some officers will, upon provocation, bear themselves boldly and
+defyingly enough towards their commander; yet, ten to one, let those
+very officers the next moment go down to their customary dinner in
+that same commander's cabin, and straightway their inoffensive, not
+to say deprecatory and humble air towards him, as he sits at the head
+of the table; this is marvellous, sometimes most comical. Wherefore
+this difference? A problem? Perhaps not. To have been Belshazzar,
+King of Babylon; and to have been Belshazzar, not haughtily but
+courteously, therein certainly must have been some touch of mundane
+grandeur. But he who in the rightly regal and intelligent spirit
+presides over his own private dinner-table of invited guests, that
+man's unchallenged power and dominion of individual influence for the
+time; that man's royalty of state transcends Belshazzar's, for
+Belshazzar was not the greatest. Who has but once dined his friends,
+has tasted what it is to be Caesar. It is a witchery of social
+czarship which there is no withstanding. Now, if to this
+consideration you superadd the official supremacy of a ship-master,
+then, by inference, you will derive the cause of that peculiarity of
+sea-life just mentioned.
+
+Over his ivory-inlaid table, Ahab presided like a mute, maned
+sea-lion on the white coral beach, surrounded by his warlike but
+still deferential cubs. In his own proper turn, each officer waited
+to be served. They were as little children before Ahab; and yet, in
+Ahab, there seemed not to lurk the smallest social arrogance. With
+one mind, their intent eyes all fastened upon the old man's knife, as
+he carved the chief dish before him. I do not suppose that for the
+world they would have profaned that moment with the slightest
+observation, even upon so neutral a topic as the weather. No! And
+when reaching out his knife and fork, between which the slice of beef
+was locked, Ahab thereby motioned Starbuck's plate towards him, the
+mate received his meat as though receiving alms; and cut it tenderly;
+and a little started if, perchance, the knife grazed against the
+plate; and chewed it noiselessly; and swallowed it, not without
+circumspection. For, like the Coronation banquet at Frankfort, where
+the German Emperor profoundly dines with the seven Imperial
+Electors, so these cabin meals were somehow solemn meals, eaten in
+awful silence; and yet at table old Ahab forbade not conversation;
+only he himself was dumb. What a relief it was to choking Stubb,
+when a rat made a sudden racket in the hold below. And poor little
+Flask, he was the youngest son, and little boy of this weary family
+party. His were the shinbones of the saline beef; his would have
+been the drumsticks. For Flask to have presumed to help himself,
+this must have seemed to him tantamount to larceny in the first
+degree. Had he helped himself at that table, doubtless, never more
+would he have been able to hold his head up in this honest world;
+nevertheless, strange to say, Ahab never forbade him. And had Flask
+helped himself, the chances were Ahab had never so much as noticed
+it. Least of all, did Flask presume to help himself to butter.
+Whether he thought the owners of the ship denied it to him, on
+account of its clotting his clear, sunny complexion; or whether he
+deemed that, on so long a voyage in such marketless waters, butter
+was at a premium, and therefore was not for him, a subaltern; however
+it was, Flask, alas! was a butterless man!
+
+Another thing. Flask was the last person down at the dinner, and
+Flask is the first man up. Consider! For hereby Flask's dinner was
+badly jammed in point of time. Starbuck and Stubb both had the start
+of him; and yet they also have the privilege of lounging in the rear.
+If Stubb even, who is but a peg higher than Flask, happens to have
+but a small appetite, and soon shows symptoms of concluding his
+repast, then Flask must bestir himself, he will not get more than
+three mouthfuls that day; for it is against holy usage for Stubb to
+precede Flask to the deck. Therefore it was that Flask once admitted
+in private, that ever since he had arisen to the dignity of an
+officer, from that moment he had never known what it was to be
+otherwise than hungry, more or less. For what he ate did not so much
+relieve his hunger, as keep it immortal in him. Peace and
+satisfaction, thought Flask, have for ever departed from my stomach.
+I am an officer; but, how I wish I could fish a bit of old-fashioned
+beef in the forecastle, as I used to when I was before the mast.
+There's the fruits of promotion now; there's the vanity of glory:
+there's the insanity of life! Besides, if it were so that any mere
+sailor of the Pequod had a grudge against Flask in Flask's official
+capacity, all that sailor had to do, in order to obtain ample
+vengeance, was to go aft at dinner-time, and get a peep at Flask
+through the cabin sky-light, sitting silly and dumfoundered before
+awful Ahab.
+
+Now, Ahab and his three mates formed what may be called the first
+table in the Pequod's cabin. After their departure, taking place in
+inverted order to their arrival, the canvas cloth was cleared, or
+rather was restored to some hurried order by the pallid steward. And
+then the three harpooneers were bidden to the feast, they being its
+residuary legatees. They made a sort of temporary servants' hall of
+the high and mighty cabin.
+
+In strange contrast to the hardly tolerable constraint and nameless
+invisible domineerings of the captain's table, was the entire
+care-free license and ease, the almost frantic democracy of those
+inferior fellows the harpooneers. While their masters, the mates,
+seemed afraid of the sound of the hinges of their own jaws, the
+harpooneers chewed their food with such a relish that there was a
+report to it. They dined like lords; they filled their bellies like
+Indian ships all day loading with spices. Such portentous appetites
+had Queequeg and Tashtego, that to fill out the vacancies made by the
+previous repast, often the pale Dough-Boy was fain to bring on a
+great baron of salt-junk, seemingly quarried out of the solid ox.
+And if he were not lively about it, if he did not go with a nimble
+hop-skip-and-jump, then Tashtego had an ungentlemanly way of
+accelerating him by darting a fork at his back, harpoon-wise. And
+once Daggoo, seized with a sudden humor, assisted Dough-Boy's memory
+by snatching him up bodily, and thrusting his head into a great empty
+wooden trencher, while Tashtego, knife in hand, began laying out the
+circle preliminary to scalping him. He was naturally a very nervous,
+shuddering sort of little fellow, this bread-faced steward; the
+progeny of a bankrupt baker and a hospital nurse. And what with the
+standing spectacle of the black terrific Ahab, and the periodical
+tumultuous visitations of these three savages, Dough-Boy's whole life
+was one continual lip-quiver. Commonly, after seeing the harpooneers
+furnished with all things they demanded, he would escape from their
+clutches into his little pantry adjoining, and fearfully peep out at
+them through the blinds of its door, till all was over.
+
+It was a sight to see Queequeg seated over against Tashtego, opposing
+his filed teeth to the Indian's: crosswise to them, Daggoo seated on
+the floor, for a bench would have brought his hearse-plumed head to
+the low carlines; at every motion of his colossal limbs, making the
+low cabin framework to shake, as when an African elephant goes
+passenger in a ship. But for all this, the great negro was
+wonderfully abstemious, not to say dainty. It seemed hardly possible
+that by such comparatively small mouthfuls he could keep up the
+vitality diffused through so broad, baronial, and superb a person.
+But, doubtless, this noble savage fed strong and drank deep of the
+abounding element of air; and through his dilated nostrils snuffed in
+the sublime life of the worlds. Not by beef or by bread, are giants
+made or nourished. But Queequeg, he had a mortal, barbaric smack of
+the lip in eating--an ugly sound enough--so much so, that the
+trembling Dough-Boy almost looked to see whether any marks of teeth
+lurked in his own lean arms. And when he would hear Tashtego singing
+out for him to produce himself, that his bones might be picked, the
+simple-witted steward all but shattered the crockery hanging round
+him in the pantry, by his sudden fits of the palsy. Nor did the
+whetstone which the harpooneers carried in their pockets, for their
+lances and other weapons; and with which whetstones, at dinner, they
+would ostentatiously sharpen their knives; that grating sound did not
+at all tend to tranquillize poor Dough-Boy. How could he forget that
+in his Island days, Queequeg, for one, must certainly have been
+guilty of some murderous, convivial indiscretions. Alas! Dough-Boy!
+hard fares the white waiter who waits upon cannibals. Not a napkin
+should he carry on his arm, but a buckler. In good time, though, to
+his great delight, the three salt-sea warriors would rise and depart;
+to his credulous, fable-mongering ears, all their martial bones
+jingling in them at every step, like Moorish scimetars in scabbards.
+
+But, though these barbarians dined in the cabin, and nominally lived
+there; still, being anything but sedentary in their habits, they were
+scarcely ever in it except at mealtimes, and just before
+sleeping-time, when they passed through it to their own peculiar
+quarters.
+
+In this one matter, Ahab seemed no exception to most American whale
+captains, who, as a set, rather incline to the opinion that by rights
+the ship's cabin belongs to them; and that it is by courtesy alone
+that anybody else is, at any time, permitted there. So that, in real
+truth, the mates and harpooneers of the Pequod might more properly be
+said to have lived out of the cabin than in it. For when they did
+enter it, it was something as a street-door enters a house; turning
+inwards for a moment, only to be turned out the next; and, as a
+permanent thing, residing in the open air. Nor did they lose much
+hereby; in the cabin was no companionship; socially, Ahab was
+inaccessible. Though nominally included in the census of
+Christendom, he was still an alien to it. He lived in the world, as
+the last of the Grisly Bears lived in settled Missouri. And as when
+Spring and Summer had departed, that wild Logan of the woods, burying
+himself in the hollow of a tree, lived out the winter there, sucking
+his own paws; so, in his inclement, howling old age, Ahab's soul,
+shut up in the caved trunk of his body, there fed upon the sullen
+paws of its gloom!
+
+
+
+CHAPTER 35
+
+The Mast-Head.
+
+
+It was during the more pleasant weather, that in due rotation with
+the other seamen my first mast-head came round.
+
+In most American whalemen the mast-heads are manned almost
+simultaneously with the vessel's leaving her port; even though she
+may have fifteen thousand miles, and more, to sail ere reaching her
+proper cruising ground. And if, after a three, four, or five years'
+voyage she is drawing nigh home with anything empty in her--say, an
+empty vial even--then, her mast-heads are kept manned to the last;
+and not till her skysail-poles sail in among the spires of the port,
+does she altogether relinquish the hope of capturing one whale more.
+
+Now, as the business of standing mast-heads, ashore or afloat, is a
+very ancient and interesting one, let us in some measure expatiate
+here. I take it, that the earliest standers of mast-heads were the
+old Egyptians; because, in all my researches, I find none prior to
+them. For though their progenitors, the builders of Babel, must
+doubtless, by their tower, have intended to rear the loftiest
+mast-head in all Asia, or Africa either; yet (ere the final truck was
+put to it) as that great stone mast of theirs may be said to have
+gone by the board, in the dread gale of God's wrath; therefore, we
+cannot give these Babel builders priority over the Egyptians. And
+that the Egyptians were a nation of mast-head standers, is an
+assertion based upon the general belief among archaeologists, that
+the first pyramids were founded for astronomical purposes: a theory
+singularly supported by the peculiar stair-like formation of all four
+sides of those edifices; whereby, with prodigious long upliftings of
+their legs, those old astronomers were wont to mount to the apex, and
+sing out for new stars; even as the look-outs of a modern ship sing
+out for a sail, or a whale just bearing in sight. In Saint Stylites,
+the famous Christian hermit of old times, who built him a lofty stone
+pillar in the desert and spent the whole latter portion of his life
+on its summit, hoisting his food from the ground with a tackle; in
+him we have a remarkable instance of a dauntless
+stander-of-mast-heads; who was not to be driven from his place by
+fogs or frosts, rain, hail, or sleet; but valiantly facing everything
+out to the last, literally died at his post. Of modern
+standers-of-mast-heads we have but a lifeless set; mere stone, iron,
+and bronze men; who, though well capable of facing out a stiff gale,
+are still entirely incompetent to the business of singing out upon
+discovering any strange sight. There is Napoleon; who, upon the top
+of the column of Vendome, stands with arms folded, some one hundred
+and fifty feet in the air; careless, now, who rules the decks below;
+whether Louis Philippe, Louis Blanc, or Louis the Devil. Great
+Washington, too, stands high aloft on his towering main-mast in
+Baltimore, and like one of Hercules' pillars, his column marks that
+point of human grandeur beyond which few mortals will go. Admiral
+Nelson, also, on a capstan of gun-metal, stands his mast-head in
+Trafalgar Square; and ever when most obscured by that London smoke,
+token is yet given that a hidden hero is there; for where there is
+smoke, must be fire. But neither great Washington, nor Napoleon, nor
+Nelson, will answer a single hail from below, however madly invoked
+to befriend by their counsels the distracted decks upon which they
+gaze; however it may be surmised, that their spirits penetrate
+through the thick haze of the future, and descry what shoals and what
+rocks must be shunned.
+
+It may seem unwarrantable to couple in any respect the mast-head
+standers of the land with those of the sea; but that in truth it is
+not so, is plainly evinced by an item for which Obed Macy, the sole
+historian of Nantucket, stands accountable. The worthy Obed tells
+us, that in the early times of the whale fishery, ere ships were
+regularly launched in pursuit of the game, the people of that island
+erected lofty spars along the sea-coast, to which the look-outs
+ascended by means of nailed cleats, something as fowls go upstairs in
+a hen-house. A few years ago this same plan was adopted by the Bay
+whalemen of New Zealand, who, upon descrying the game, gave notice to
+the ready-manned boats nigh the beach. But this custom has now
+become obsolete; turn we then to the one proper mast-head, that of a
+whale-ship at sea. The three mast-heads are kept manned from
+sun-rise to sun-set; the seamen taking their regular turns (as at the
+helm), and relieving each other every two hours. In the serene
+weather of the tropics it is exceedingly pleasant the mast-head; nay,
+to a dreamy meditative man it is delightful. There you stand, a
+hundred feet above the silent decks, striding along the deep, as if
+the masts were gigantic stilts, while beneath you and between your
+legs, as it were, swim the hugest monsters of the sea, even as ships
+once sailed between the boots of the famous Colossus at old Rhodes.
+There you stand, lost in the infinite series of the sea, with nothing
+ruffled but the waves. The tranced ship indolently rolls; the drowsy
+trade winds blow; everything resolves you into languor. For the most
+part, in this tropic whaling life, a sublime uneventfulness invests
+you; you hear no news; read no gazettes; extras with startling
+accounts of commonplaces never delude you into unnecessary
+excitements; you hear of no domestic afflictions; bankrupt
+securities; fall of stocks; are never troubled with the thought of
+what you shall have for dinner--for all your meals for three years
+and more are snugly stowed in casks, and your bill of fare is
+immutable.
+
+In one of those southern whalesmen, on a long three or four years'
+voyage, as often happens, the sum of the various hours you spend at
+the mast-head would amount to several entire months. And it is much
+to be deplored that the place to which you devote so considerable a
+portion of the whole term of your natural life, should be so sadly
+destitute of anything approaching to a cosy inhabitiveness, or
+adapted to breed a comfortable localness of feeling, such as pertains
+to a bed, a hammock, a hearse, a sentry box, a pulpit, a coach, or
+any other of those small and snug contrivances in which men
+temporarily isolate themselves. Your most usual point of perch is
+the head of the t' gallant-mast, where you stand upon two thin
+parallel sticks (almost peculiar to whalemen) called the t' gallant
+cross-trees. Here, tossed about by the sea, the beginner feels about
+as cosy as he would standing on a bull's horns. To be sure, in cold
+weather you may carry your house aloft with you, in the shape of a
+watch-coat; but properly speaking the thickest watch-coat is no more
+of a house than the unclad body; for as the soul is glued inside of
+its fleshy tabernacle, and cannot freely move about in it, nor even
+move out of it, without running great risk of perishing (like an
+ignorant pilgrim crossing the snowy Alps in winter); so a watch-coat
+is not so much of a house as it is a mere envelope, or additional
+skin encasing you. You cannot put a shelf or chest of drawers in
+your body, and no more can you make a convenient closet of your
+watch-coat.
+
+Concerning all this, it is much to be deplored that the mast-heads of
+a southern whale ship are unprovided with those enviable little tents
+or pulpits, called CROW'S-NESTS, in which the look-outs of a
+Greenland whaler are protected from the inclement weather of the
+frozen seas. In the fireside narrative of Captain Sleet, entitled
+"A Voyage among the Icebergs, in quest of the Greenland Whale, and
+incidentally for the re-discovery of the Lost Icelandic Colonies of
+Old Greenland;" in this admirable volume, all standers of mast-heads
+are furnished with a charmingly circumstantial account of the then
+recently invented CROW'S-NEST of the Glacier, which was the name of
+Captain Sleet's good craft. He called it the SLEET'S CROW'S-NEST, in
+honour of himself; he being the original inventor and patentee, and
+free from all ridiculous false delicacy, and holding that if we call
+our own children after our own names (we fathers being the original
+inventors and patentees), so likewise should we denominate after
+ourselves any other apparatus we may beget. In shape, the Sleet's
+crow's-nest is something like a large tierce or pipe; it is open
+above, however, where it is furnished with a movable side-screen to
+keep to windward of your head in a hard gale. Being fixed on the
+summit of the mast, you ascend into it through a little trap-hatch in
+the bottom. On the after side, or side next the stern of the ship,
+is a comfortable seat, with a locker underneath for umbrellas,
+comforters, and coats. In front is a leather rack, in which to keep
+your speaking trumpet, pipe, telescope, and other nautical
+conveniences. When Captain Sleet in person stood his mast-head in
+this crow's-nest of his, he tells us that he always had a rifle with
+him (also fixed in the rack), together with a powder flask and shot,
+for the purpose of popping off the stray narwhales, or vagrant sea
+unicorns infesting those waters; for you cannot successfully shoot at
+them from the deck owing to the resistance of the water, but to shoot
+down upon them is a very different thing. Now, it was plainly a
+labor of love for Captain Sleet to describe, as he does, all the
+little detailed conveniences of his crow's-nest; but though he so
+enlarges upon many of these, and though he treats us to a very
+scientific account of his experiments in this crow's-nest, with a
+small compass he kept there for the purpose of counteracting the
+errors resulting from what is called the "local attraction" of all
+binnacle magnets; an error ascribable to the horizontal vicinity of
+the iron in the ship's planks, and in the Glacier's case, perhaps, to
+there having been so many broken-down blacksmiths among her crew; I
+say, that though the Captain is very discreet and scientific here,
+yet, for all his learned "binnacle deviations," "azimuth compass
+observations," and "approximate errors," he knows very well, Captain
+Sleet, that he was not so much immersed in those profound magnetic
+meditations, as to fail being attracted occasionally towards that
+well replenished little case-bottle, so nicely tucked in on one side
+of his crow's nest, within easy reach of his hand. Though, upon the
+whole, I greatly admire and even love the brave, the honest, and
+learned Captain; yet I take it very ill of him that he should so
+utterly ignore that case-bottle, seeing what a faithful friend and
+comforter it must have been, while with mittened fingers and hooded
+head he was studying the mathematics aloft there in that bird's nest
+within three or four perches of the pole.
+
+But if we Southern whale-fishers are not so snugly housed aloft as
+Captain Sleet and his Greenlandmen were; yet that disadvantage is
+greatly counter-balanced by the widely contrasting serenity of those
+seductive seas in which we South fishers mostly float. For one, I
+used to lounge up the rigging very leisurely, resting in the top to
+have a chat with Queequeg, or any one else off duty whom I might find
+there; then ascending a little way further, and throwing a lazy leg
+over the top-sail yard, take a preliminary view of the watery
+pastures, and so at last mount to my ultimate destination.
+
+Let me make a clean breast of it here, and frankly admit that I kept
+but sorry guard. With the problem of the universe revolving in me,
+how could I--being left completely to myself at such a
+thought-engendering altitude--how could I but lightly hold my
+obligations to observe all whale-ships' standing orders, "Keep your
+weather eye open, and sing out every time."
+
+And let me in this place movingly admonish you, ye ship-owners of
+Nantucket! Beware of enlisting in your vigilant fisheries any lad
+with lean brow and hollow eye; given to unseasonable meditativeness;
+and who offers to ship with the Phaedon instead of Bowditch in his
+head. Beware of such an one, I say; your whales must be seen before
+they can be killed; and this sunken-eyed young Platonist will tow you
+ten wakes round the world, and never make you one pint of sperm the
+richer. Nor are these monitions at all unneeded. For nowadays, the
+whale-fishery furnishes an asylum for many romantic, melancholy, and
+absent-minded young men, disgusted with the carking cares of earth,
+and seeking sentiment in tar and blubber. Childe Harold not
+unfrequently perches himself upon the mast-head of some luckless
+disappointed whale-ship, and in moody phrase ejaculates:--
+
+"Roll on, thou deep and dark blue ocean, roll! Ten thousand
+blubber-hunters sweep over thee in vain."
+
+Very often do the captains of such ships take those absent-minded
+young philosophers to task, upbraiding them with not feeling
+sufficient "interest" in the voyage; half-hinting that they are so
+hopelessly lost to all honourable ambition, as that in their secret
+souls they would rather not see whales than otherwise. But all in
+vain; those young Platonists have a notion that their vision is
+imperfect; they are short-sighted; what use, then, to strain the
+visual nerve? They have left their opera-glasses at home.
+
+"Why, thou monkey," said a harpooneer to one of these lads, "we've
+been cruising now hard upon three years, and thou hast not raised a
+whale yet. Whales are scarce as hen's teeth whenever thou art up
+here." Perhaps they were; or perhaps there might have been shoals of
+them in the far horizon; but lulled into such an opium-like
+listlessness of vacant, unconscious reverie is this absent-minded
+youth by the blending cadence of waves with thoughts, that at last he
+loses his identity; takes the mystic ocean at his feet for the
+visible image of that deep, blue, bottomless soul, pervading mankind
+and nature; and every strange, half-seen, gliding, beautiful thing
+that eludes him; every dimly-discovered, uprising fin of some
+undiscernible form, seems to him the embodiment of those elusive
+thoughts that only people the soul by continually flitting through
+it. In this enchanted mood, thy spirit ebbs away to whence it came;
+becomes diffused through time and space; like Crammer's sprinkled
+Pantheistic ashes, forming at last a part of every shore the round
+globe over.
+
+There is no life in thee, now, except that rocking life imparted by a
+gently rolling ship; by her, borrowed from the sea; by the sea, from
+the inscrutable tides of God. But while this sleep, this dream is on
+ye, move your foot or hand an inch; slip your hold at all; and your
+identity comes back in horror. Over Descartian vortices you hover.
+And perhaps, at mid-day, in the fairest weather, with one
+half-throttled shriek you drop through that transparent air into the
+summer sea, no more to rise for ever. Heed it well, ye Pantheists!
+
+
+
+CHAPTER 36
+
+The Quarter-Deck.
+
+
+(ENTER AHAB: THEN, ALL)
+
+
+It was not a great while after the affair of the pipe, that one
+morning shortly after breakfast, Ahab, as was his wont, ascended the
+cabin-gangway to the deck. There most sea-captains usually walk at
+that hour, as country gentlemen, after the same meal, take a few
+turns in the garden.
+
+Soon his steady, ivory stride was heard, as to and fro he paced his
+old rounds, upon planks so familiar to his tread, that they were all
+over dented, like geological stones, with the peculiar mark of his
+walk. Did you fixedly gaze, too, upon that ribbed and dented brow;
+there also, you would see still stranger foot-prints--the foot-prints
+of his one unsleeping, ever-pacing thought.
+
+But on the occasion in question, those dents looked deeper, even as
+his nervous step that morning left a deeper mark. And, so full of
+his thought was Ahab, that at every uniform turn that he made, now at
+the main-mast and now at the binnacle, you could almost see that
+thought turn in him as he turned, and pace in him as he paced; so
+completely possessing him, indeed, that it all but seemed the inward
+mould of every outer movement.
+
+"D'ye mark him, Flask?" whispered Stubb; "the chick that's in him
+pecks the shell. 'Twill soon be out."
+
+The hours wore on;--Ahab now shut up within his cabin; anon, pacing
+the deck, with the same intense bigotry of purpose in his aspect.
+
+It drew near the close of day. Suddenly he came to a halt by the
+bulwarks, and inserting his bone leg into the auger-hole there, and
+with one hand grasping a shroud, he ordered Starbuck to send
+everybody aft.
+
+"Sir!" said the mate, astonished at an order seldom or never given on
+ship-board except in some extraordinary case.
+
+"Send everybody aft," repeated Ahab. "Mast-heads, there! come down!"
+
+When the entire ship's company were assembled, and with curious and
+not wholly unapprehensive faces, were eyeing him, for he looked not
+unlike the weather horizon when a storm is coming up, Ahab, after
+rapidly glancing over the bulwarks, and then darting his eyes among
+the crew, started from his standpoint; and as though not a soul were
+nigh him resumed his heavy turns upon the deck. With bent head and
+half-slouched hat he continued to pace, unmindful of the wondering
+whispering among the men; till Stubb cautiously whispered to Flask,
+that Ahab must have summoned them there for the purpose of witnessing
+a pedestrian feat. But this did not last long. Vehemently pausing,
+he cried:--
+
+"What do ye do when ye see a whale, men?"
+
+"Sing out for him!" was the impulsive rejoinder from a score of
+clubbed voices.
+
+"Good!" cried Ahab, with a wild approval in his tones; observing the
+hearty animation into which his unexpected question had so
+magnetically thrown them.
+
+"And what do ye next, men?"
+
+"Lower away, and after him!"
+
+"And what tune is it ye pull to, men?"
+
+"A dead whale or a stove boat!"
+
+More and more strangely and fiercely glad and approving, grew the
+countenance of the old man at every shout; while the mariners began
+to gaze curiously at each other, as if marvelling how it was that
+they themselves became so excited at such seemingly purposeless
+questions.
+
+But, they were all eagerness again, as Ahab, now half-revolving in
+his pivot-hole, with one hand reaching high up a shroud, and tightly,
+almost convulsively grasping it, addressed them thus:--
+
+"All ye mast-headers have before now heard me give orders about a
+white whale. Look ye! d'ye see this Spanish ounce of gold?"--holding
+up a broad bright coin to the sun--"it is a sixteen dollar piece,
+men. D'ye see it? Mr. Starbuck, hand me yon top-maul."
+
+While the mate was getting the hammer, Ahab, without speaking, was
+slowly rubbing the gold piece against the skirts of his jacket, as if
+to heighten its lustre, and without using any words was meanwhile
+lowly humming to himself, producing a sound so strangely muffled and
+inarticulate that it seemed the mechanical humming of the wheels of
+his vitality in him.
+
+Receiving the top-maul from Starbuck, he advanced towards the
+main-mast with the hammer uplifted in one hand, exhibiting the gold
+with the other, and with a high raised voice exclaiming: "Whosoever
+of ye raises me a white-headed whale with a wrinkled brow and a
+crooked jaw; whosoever of ye raises me that white-headed whale, with
+three holes punctured in his starboard fluke--look ye, whosoever of
+ye raises me that same white whale, he shall have this gold ounce, my
+boys!"
+
+"Huzza! huzza!" cried the seamen, as with swinging tarpaulins they
+hailed the act of nailing the gold to the mast.
+
+"It's a white whale, I say," resumed Ahab, as he threw down the
+topmaul: "a white whale. Skin your eyes for him, men; look sharp for
+white water; if ye see but a bubble, sing out."
+
+All this while Tashtego, Daggoo, and Queequeg had looked on with even
+more intense interest and surprise than the rest, and at the mention
+of the wrinkled brow and crooked jaw they had started as if each was
+separately touched by some specific recollection.
+
+"Captain Ahab," said Tashtego, "that white whale must be the same
+that some call Moby Dick."
+
+"Moby Dick?" shouted Ahab. "Do ye know the white whale then, Tash?"
+
+"Does he fan-tail a little curious, sir, before he goes down?" said
+the Gay-Header deliberately.
+
+"And has he a curious spout, too," said Daggoo, "very bushy, even for
+a parmacetty, and mighty quick, Captain Ahab?"
+
+"And he have one, two, three--oh! good many iron in him hide, too,
+Captain," cried Queequeg disjointedly, "all twiske-tee be-twisk, like
+him--him--" faltering hard for a word, and screwing his hand round
+and round as though uncorking a bottle--"like him--him--"
+
+"Corkscrew!" cried Ahab, "aye, Queequeg, the harpoons lie all twisted
+and wrenched in him; aye, Daggoo, his spout is a big one, like a
+whole shock of wheat, and white as a pile of our Nantucket wool after
+the great annual sheep-shearing; aye, Tashtego, and he fan-tails like
+a split jib in a squall. Death and devils! men, it is Moby Dick ye
+have seen--Moby Dick--Moby Dick!"
+
+"Captain Ahab," said Starbuck, who, with Stubb and Flask, had thus
+far been eyeing his superior with increasing surprise, but at last
+seemed struck with a thought which somewhat explained all the wonder.
+"Captain Ahab, I have heard of Moby Dick--but it was not Moby Dick
+that took off thy leg?"
+
+"Who told thee that?" cried Ahab; then pausing, "Aye, Starbuck; aye,
+my hearties all round; it was Moby Dick that dismasted me; Moby Dick
+that brought me to this dead stump I stand on now. Aye, aye," he
+shouted with a terrific, loud, animal sob, like that of a
+heart-stricken moose; "Aye, aye! it was that accursed white whale
+that razeed me; made a poor pegging lubber of me for ever and a day!"
+Then tossing both arms, with measureless imprecations he shouted
+out: "Aye, aye! and I'll chase him round Good Hope, and round the
+Horn, and round the Norway Maelstrom, and round perdition's flames
+before I give him up. And this is what ye have shipped for, men! to
+chase that white whale on both sides of land, and over all sides of
+earth, till he spouts black blood and rolls fin out. What say ye,
+men, will ye splice hands on it, now? I think ye do look brave."
+
+"Aye, aye!" shouted the harpooneers and seamen, running closer to the
+excited old man: "A sharp eye for the white whale; a sharp lance for
+Moby Dick!"
+
+"God bless ye," he seemed to half sob and half shout. "God bless ye,
+men. Steward! go draw the great measure of grog. But what's this
+long face about, Mr. Starbuck; wilt thou not chase the white whale?
+art not game for Moby Dick?"
+
+"I am game for his crooked jaw, and for the jaws of Death too,
+Captain Ahab, if it fairly comes in the way of the business we
+follow; but I came here to hunt whales, not my commander's vengeance.
+How many barrels will thy vengeance yield thee even if thou gettest
+it, Captain Ahab? it will not fetch thee much in our Nantucket
+market."
+
+"Nantucket market! Hoot! But come closer, Starbuck; thou requirest
+a little lower layer. If money's to be the measurer, man, and the
+accountants have computed their great counting-house the globe, by
+girdling it with guineas, one to every three parts of an inch; then,
+let me tell thee, that my vengeance will fetch a great premium HERE!"
+
+"He smites his chest," whispered Stubb, "what's that for? methinks it
+rings most vast, but hollow."
+
+"Vengeance on a dumb brute!" cried Starbuck, "that simply smote thee
+from blindest instinct! Madness! To be enraged with a dumb thing,
+Captain Ahab, seems blasphemous."
+
+"Hark ye yet again--the little lower layer. All visible objects,
+man, are but as pasteboard masks. But in each event--in the living
+act, the undoubted deed--there, some unknown but still reasoning
+thing puts forth the mouldings of its features from behind the
+unreasoning mask. If man will strike, strike through the mask! How
+can the prisoner reach outside except by thrusting through the wall?
+To me, the white whale is that wall, shoved near to me. Sometimes I
+think there's naught beyond. But 'tis enough. He tasks me; he heaps
+me; I see in him outrageous strength, with an inscrutable malice
+sinewing it. That inscrutable thing is chiefly what I hate; and be
+the white whale agent, or be the white whale principal, I will wreak
+that hate upon him. Talk not to me of blasphemy, man; I'd strike the
+sun if it insulted me. For could the sun do that, then could I do
+the other; since there is ever a sort of fair play herein, jealousy
+presiding over all creations. But not my master, man, is even that
+fair play. Who's over me? Truth hath no confines. Take off thine
+eye! more intolerable than fiends' glarings is a doltish stare! So,
+so; thou reddenest and palest; my heat has melted thee to anger-glow.
+But look ye, Starbuck, what is said in heat, that thing unsays
+itself. There are men from whom warm words are small indignity. I
+meant not to incense thee. Let it go. Look! see yonder Turkish
+cheeks of spotted tawn--living, breathing pictures painted by the
+sun. The Pagan leopards--the unrecking and unworshipping things,
+that live; and seek, and give no reasons for the torrid life they
+feel! The crew, man, the crew! Are they not one and all with Ahab,
+in this matter of the whale? See Stubb! he laughs! See yonder
+Chilian! he snorts to think of it. Stand up amid the general
+hurricane, thy one tost sapling cannot, Starbuck! And what is it?
+Reckon it. 'Tis but to help strike a fin; no wondrous feat for
+Starbuck. What is it more? From this one poor hunt, then, the best
+lance out of all Nantucket, surely he will not hang back, when every
+foremast-hand has clutched a whetstone? Ah! constrainings seize
+thee; I see! the billow lifts thee! Speak, but speak!--Aye, aye! thy
+silence, then, THAT voices thee. (ASIDE) Something shot from my
+dilated nostrils, he has inhaled it in his lungs. Starbuck now is
+mine; cannot oppose me now, without rebellion."
+
+"God keep me!--keep us all!" murmured Starbuck, lowly.
+
+But in his joy at the enchanted, tacit acquiescence of the mate, Ahab
+did not hear his foreboding invocation; nor yet the low laugh from
+the hold; nor yet the presaging vibrations of the winds in the
+cordage; nor yet the hollow flap of the sails against the masts, as
+for a moment their hearts sank in. For again Starbuck's downcast
+eyes lighted up with the stubbornness of life; the subterranean laugh
+died away; the winds blew on; the sails filled out; the ship heaved
+and rolled as before. Ah, ye admonitions and warnings! why stay ye
+not when ye come? But rather are ye predictions than warnings, ye
+shadows! Yet not so much predictions from without, as verifications
+of the foregoing things within. For with little external to
+constrain us, the innermost necessities in our being, these still
+drive us on.
+
+"The measure! the measure!" cried Ahab.
+
+Receiving the brimming pewter, and turning to the harpooneers, he
+ordered them to produce their weapons. Then ranging them before him
+near the capstan, with their harpoons in their hands, while his three
+mates stood at his side with their lances, and the rest of the ship's
+company formed a circle round the group; he stood for an instant
+searchingly eyeing every man of his crew. But those wild eyes met
+his, as the bloodshot eyes of the prairie wolves meet the eye of
+their leader, ere he rushes on at their head in the trail of the
+bison; but, alas! only to fall into the hidden snare of the Indian.
+
+"Drink and pass!" he cried, handing the heavy charged flagon to the
+nearest seaman. "The crew alone now drink. Round with it, round!
+Short draughts--long swallows, men; 'tis hot as Satan's hoof. So,
+so; it goes round excellently. It spiralizes in ye; forks out at the
+serpent-snapping eye. Well done; almost drained. That way it went,
+this way it comes. Hand it me--here's a hollow! Men, ye seem the
+years; so brimming life is gulped and gone. Steward, refill!
+
+"Attend now, my braves. I have mustered ye all round this capstan;
+and ye mates, flank me with your lances; and ye harpooneers, stand
+there with your irons; and ye, stout mariners, ring me in, that I may
+in some sort revive a noble custom of my fisherman fathers before
+me. O men, you will yet see that--Ha! boy, come back? bad pennies
+come not sooner. Hand it me. Why, now, this pewter had run brimming
+again, were't not thou St. Vitus' imp--away, thou ague!
+
+"Advance, ye mates! Cross your lances full before me. Well done!
+Let me touch the axis." So saying, with extended arm, he grasped the
+three level, radiating lances at their crossed centre; while so
+doing, suddenly and nervously twitched them; meanwhile, glancing
+intently from Starbuck to Stubb; from Stubb to Flask. It seemed as
+though, by some nameless, interior volition, he would fain have
+shocked into them the same fiery emotion accumulated within the
+Leyden jar of his own magnetic life. The three mates quailed before
+his strong, sustained, and mystic aspect. Stubb and Flask looked
+sideways from him; the honest eye of Starbuck fell downright.
+
+"In vain!" cried Ahab; "but, maybe, 'tis well. For did ye three but
+once take the full-forced shock, then mine own electric thing, THAT
+had perhaps expired from out me. Perchance, too, it would have
+dropped ye dead. Perchance ye need it not. Down lances! And now,
+ye mates, I do appoint ye three cupbearers to my three pagan kinsmen
+there--yon three most honourable gentlemen and noblemen, my valiant
+harpooneers. Disdain the task? What, when the great Pope washes the
+feet of beggars, using his tiara for ewer? Oh, my sweet cardinals!
+your own condescension, THAT shall bend ye to it. I do not order ye;
+ye will it. Cut your seizings and draw the poles, ye harpooneers!"
+
+Silently obeying the order, the three harpooneers now stood with the
+detached iron part of their harpoons, some three feet long, held,
+barbs up, before him.
+
+"Stab me not with that keen steel! Cant them; cant them over! know
+ye not the goblet end? Turn up the socket! So, so; now, ye
+cup-bearers, advance. The irons! take them; hold them while I fill!"
+Forthwith, slowly going from one officer to the other, he brimmed
+the harpoon sockets with the fiery waters from the pewter.
+
+"Now, three to three, ye stand. Commend the murderous chalices!
+Bestow them, ye who are now made parties to this indissoluble league.
+Ha! Starbuck! but the deed is done! Yon ratifying sun now waits to
+sit upon it. Drink, ye harpooneers! drink and swear, ye men that man
+the deathful whaleboat's bow--Death to Moby Dick! God hunt us all,
+if we do not hunt Moby Dick to his death!" The long, barbed steel
+goblets were lifted; and to cries and maledictions against the white
+whale, the spirits were simultaneously quaffed down with a hiss.
+Starbuck paled, and turned, and shivered. Once more, and finally,
+the replenished pewter went the rounds among the frantic crew; when,
+waving his free hand to them, they all dispersed; and Ahab retired
+within his cabin.
+
+
+
+CHAPTER 37
+
+Sunset.
+
+
+THE CABIN; BY THE STERN WINDOWS; AHAB SITTING ALONE, AND GAZING OUT.
+
+
+I leave a white and turbid wake; pale waters, paler cheeks, where'er
+I sail. The envious billows sidelong swell to whelm my track; let
+them; but first I pass.
+
+Yonder, by ever-brimming goblet's rim, the warm waves blush like
+wine. The gold brow plumbs the blue. The diver sun--slow dived from
+noon--goes down; my soul mounts up! she wearies with her endless
+hill. Is, then, the crown too heavy that I wear? this Iron Crown of
+Lombardy. Yet is it bright with many a gem; I the wearer, see not
+its far flashings; but darkly feel that I wear that, that dazzlingly
+confounds. 'Tis iron--that I know--not gold. 'Tis split, too--that
+I feel; the jagged edge galls me so, my brain seems to beat against
+the solid metal; aye, steel skull, mine; the sort that needs no
+helmet in the most brain-battering fight!
+
+Dry heat upon my brow? Oh! time was, when as the sunrise nobly
+spurred me, so the sunset soothed. No more. This lovely light, it
+lights not me; all loveliness is anguish to me, since I can ne'er
+enjoy. Gifted with the high perception, I lack the low, enjoying
+power; damned, most subtly and most malignantly! damned in the midst
+of Paradise! Good night--good night! (WAVING HIS HAND, HE MOVES FROM
+THE WINDOW.)
+
+'Twas not so hard a task. I thought to find one stubborn, at the
+least; but my one cogged circle fits into all their various wheels,
+and they revolve. Or, if you will, like so many ant-hills of powder,
+they all stand before me; and I their match. Oh, hard! that to fire
+others, the match itself must needs be wasting! What I've dared,
+I've willed; and what I've willed, I'll do! They think me
+mad--Starbuck does; but I'm demoniac, I am madness maddened! That
+wild madness that's only calm to comprehend itself! The prophecy was
+that I should be dismembered; and--Aye! I lost this leg. I now
+prophesy that I will dismember my dismemberer. Now, then, be the
+prophet and the fulfiller one. That's more than ye, ye great gods,
+ever were. I laugh and hoot at ye, ye cricket-players, ye pugilists,
+ye deaf Burkes and blinded Bendigoes! I will not say as schoolboys
+do to bullies--Take some one of your own size; don't pommel ME! No,
+ye've knocked me down, and I am up again; but YE have run and hidden.
+Come forth from behind your cotton bags! I have no long gun to
+reach ye. Come, Ahab's compliments to ye; come and see if ye can
+swerve me. Swerve me? ye cannot swerve me, else ye swerve
+yourselves! man has ye there. Swerve me? The path to my fixed
+purpose is laid with iron rails, whereon my soul is grooved to run.
+Over unsounded gorges, through the rifled hearts of mountains, under
+torrents' beds, unerringly I rush! Naught's an obstacle, naught's an
+angle to the iron way!
+
+
+
+CHAPTER 38
+
+Dusk.
+
+
+BY THE MAINMAST; STARBUCK LEANING AGAINST IT.
+
+
+My soul is more than matched; she's overmanned; and by a madman!
+Insufferable sting, that sanity should ground arms on such a field!
+But he drilled deep down, and blasted all my reason out of me! I
+think I see his impious end; but feel that I must help him to it.
+Will I, nill I, the ineffable thing has tied me to him; tows me with
+a cable I have no knife to cut. Horrible old man! Who's over him,
+he cries;--aye, he would be a democrat to all above; look, how he
+lords it over all below! Oh! I plainly see my miserable office,--to
+obey, rebelling; and worse yet, to hate with touch of pity! For in
+his eyes I read some lurid woe would shrivel me up, had I it. Yet is
+there hope. Time and tide flow wide. The hated whale has the round
+watery world to swim in, as the small gold-fish has its glassy globe.
+His heaven-insulting purpose, God may wedge aside. I would up
+heart, were it not like lead. But my whole clock's run down; my
+heart the all-controlling weight, I have no key to lift again.
+
+
+[A BURST OF REVELRY FROM THE FORECASTLE.]
+
+
+Oh, God! to sail with such a heathen crew that have small touch of
+human mothers in them! Whelped somewhere by the sharkish sea. The
+white whale is their demigorgon. Hark! the infernal orgies! that
+revelry is forward! mark the unfaltering silence aft! Methinks it
+pictures life. Foremost through the sparkling sea shoots on the gay,
+embattled, bantering bow, but only to drag dark Ahab after it, where
+he broods within his sternward cabin, builded over the dead water of
+the wake, and further on, hunted by its wolfish gurglings. The long
+howl thrills me through! Peace! ye revellers, and set the watch!
+Oh, life! 'tis in an hour like this, with soul beat down and held to
+knowledge,--as wild, untutored things are forced to feed--Oh, life!
+'tis now that I do feel the latent horror in thee! but 'tis not me!
+that horror's out of me! and with the soft feeling of the human in
+me, yet will I try to fight ye, ye grim, phantom futures! Stand by
+me, hold me, bind me, O ye blessed influences!
+
+
+
+CHAPTER 39
+
+First Night Watch.
+
+Fore-Top.
+
+(STUBB SOLUS, AND MENDING A BRACE.)
+
+
+Ha! ha! ha! ha! hem! clear my throat!--I've been thinking over it
+ever since, and that ha, ha's the final consequence. Why so?
+Because a laugh's the wisest, easiest answer to all that's queer; and
+come what will, one comfort's always left--that unfailing comfort is,
+it's all predestinated. I heard not all his talk with Starbuck; but
+to my poor eye Starbuck then looked something as I the other evening
+felt. Be sure the old Mogul has fixed him, too. I twigged it, knew
+it; had had the gift, might readily have prophesied it--for when I
+clapped my eye upon his skull I saw it. Well, Stubb, WISE
+Stubb--that's my title--well, Stubb, what of it, Stubb? Here's a
+carcase. I know not all that may be coming, but be it what it will,
+I'll go to it laughing. Such a waggish leering as lurks in all your
+horribles! I feel funny. Fa, la! lirra, skirra! What's my juicy
+little pear at home doing now? Crying its eyes out?--Giving a party
+to the last arrived harpooneers, I dare say, gay as a frigate's
+pennant, and so am I--fa, la! lirra, skirra! Oh--
+
+We'll drink to-night with hearts as light,
+To love, as gay and fleeting
+As bubbles that swim, on the beaker's brim,
+And break on the lips while meeting.
+
+
+A brave stave that--who calls? Mr. Starbuck? Aye, aye, sir--(ASIDE)
+he's my superior, he has his too, if I'm not mistaken.--Aye, aye,
+sir, just through with this job--coming.
+
+
+
+CHAPTER 40
+
+Midnight, Forecastle.
+
+HARPOONEERS AND SAILORS.
+
+(FORESAIL RISES AND DISCOVERS THE WATCH STANDING, LOUNGING, LEANING,
+AND LYING IN VARIOUS ATTITUDES, ALL SINGING IN CHORUS.)
+
+Farewell and adieu to you, Spanish ladies!
+Farewell and adieu to you, ladies of Spain!
+Our captain's commanded.--
+
+1ST NANTUCKET SAILOR.
+Oh, boys, don't be sentimental; it's bad for the digestion! Take a
+tonic, follow me!
+(SINGS, AND ALL FOLLOW)
+
+Our captain stood upon the deck,
+A spy-glass in his hand,
+A viewing of those gallant whales
+That blew at every strand.
+Oh, your tubs in your boats, my boys,
+And by your braces stand,
+And we'll have one of those fine whales,
+Hand, boys, over hand!
+So, be cheery, my lads! may your hearts never fail!
+While the bold harpooner is striking the whale!
+
+MATE'S VOICE FROM THE QUARTER-DECK.
+Eight bells there, forward!
+
+2ND NANTUCKET SAILOR.
+Avast the chorus! Eight bells there! d'ye hear, bell-boy? Strike
+the bell eight, thou Pip! thou blackling! and let me call the watch.
+I've the sort of mouth for that--the hogshead mouth. So, so,
+(THRUSTS HIS HEAD DOWN THE SCUTTLE,) Star-bo-l-e-e-n-s, a-h-o-y!
+Eight bells there below! Tumble up!
+
+DUTCH SAILOR.
+Grand snoozing to-night, maty; fat night for that. I mark this in
+our old Mogul's wine; it's quite as deadening to some as filliping to
+others. We sing; they sleep--aye, lie down there, like ground-tier
+butts. At 'em again! There, take this copper-pump, and hail 'em
+through it. Tell 'em to avast dreaming of their lasses. Tell 'em
+it's the resurrection; they must kiss their last, and come to
+judgment. That's the way--THAT'S it; thy throat ain't spoiled with
+eating Amsterdam butter.
+
+FRENCH SAILOR.
+Hist, boys! let's have a jig or two before we ride to anchor in
+Blanket Bay. What say ye? There comes the other watch. Stand by
+all legs! Pip! little Pip! hurrah with your tambourine!
+
+PIP.
+(SULKY AND SLEEPY)
+Don't know where it is.
+
+FRENCH SAILOR.
+Beat thy belly, then, and wag thy ears. Jig it, men, I say; merry's
+the word; hurrah! Damn me, won't you dance? Form, now, Indian-file,
+and gallop into the double-shuffle? Throw yourselves! Legs! legs!
+
+ICELAND SAILOR.
+I don't like your floor, maty; it's too springy to my taste. I'm
+used to ice-floors. I'm sorry to throw cold water on the subject;
+but excuse me.
+
+MALTESE SAILOR.
+Me too; where's your girls? Who but a fool would take his left hand
+by his right, and say to himself, how d'ye do? Partners! I must
+have partners!
+
+SICILIAN SAILOR.
+Aye; girls and a green!--then I'll hop with ye; yea, turn
+grasshopper!
+
+LONG-ISLAND SAILOR.
+Well, well, ye sulkies, there's plenty more of us. Hoe corn when you
+may, say I. All legs go to harvest soon. Ah! here comes the music;
+now for it!
+
+AZORE SAILOR.
+(ASCENDING, AND PITCHING THE TAMBOURINE UP THE SCUTTLE.)
+Here you are, Pip; and there's the windlass-bitts; up you mount!
+Now, boys!
+(THE HALF OF THEM DANCE TO THE TAMBOURINE; SOME GO BELOW; SOME SLEEP
+OR LIE AMONG THE COILS OF RIGGING. OATHS A-PLENTY.)
+
+AZORE SAILOR.
+(DANCING)
+Go it, Pip! Bang it, bell-boy! Rig it, dig it, stig it, quig it,
+bell-boy! Make fire-flies; break the jinglers!
+
+PIP.
+Jinglers, you say?--there goes another, dropped off; I pound it so.
+
+CHINA SAILOR.
+Rattle thy teeth, then, and pound away; make a pagoda of thyself.
+
+
+FRENCH SAILOR.
+Merry-mad! Hold up thy hoop, Pip, till I jump through it! Split
+jibs! tear yourselves!
+
+TASHTEGO.
+(QUIETLY SMOKING)
+That's a white man; he calls that fun: humph! I save my sweat.
+
+OLD MANX SAILOR.
+I wonder whether those jolly lads bethink them of what they are
+dancing over. I'll dance over your grave, I will--that's the
+bitterest threat of your night-women, that beat head-winds round
+corners. O Christ! to think of the green navies and the
+green-skulled crews! Well, well; belike the whole world's a ball, as
+you scholars have it; and so 'tis right to make one ballroom of it.
+Dance on, lads, you're young; I was once.
+
+3D NANTUCKET SAILOR.
+Spell oh!--whew! this is worse than pulling after whales in a
+calm--give us a whiff, Tash.
+
+(THEY CEASE DANCING, AND GATHER IN CLUSTERS. MEANTIME THE SKY
+DARKENS--THE WIND RISES.)
+
+LASCAR SAILOR.
+By Brahma! boys, it'll be douse sail soon. The sky-born, high-tide
+Ganges turned to wind! Thou showest thy black brow, Seeva!
+
+MALTESE SAILOR.
+(RECLINING AND SHAKING HIS CAP.)
+It's the waves--the snow's caps turn to jig it now. They'll shake
+their tassels soon. Now would all the waves were women, then I'd go
+drown, and chassee with them evermore! There's naught so sweet on
+earth--heaven may not match it!--as those swift glances of warm, wild
+bosoms in the dance, when the over-arboring arms hide such ripe,
+bursting grapes.
+
+SICILIAN SAILOR.
+(RECLINING.)
+Tell me not of it! Hark ye, lad--fleet interlacings of the
+limbs--lithe swayings--coyings--flutterings! lip! heart! hip! all
+graze: unceasing touch and go! not taste, observe ye, else come
+satiety. Eh, Pagan? (NUDGING.)
+
+TAHITAN SAILOR.
+(RECLINING ON A MAT.)
+Hail, holy nakedness of our dancing girls!--the Heeva-Heeva! Ah! low
+veiled, high palmed Tahiti! I still rest me on thy mat, but the soft
+soil has slid! I saw thee woven in the wood, my mat! green the first
+day I brought ye thence; now worn and wilted quite. Ah me!--not thou
+nor I can bear the change! How then, if so be transplanted to yon
+sky? Hear I the roaring streams from Pirohitee's peak of spears,
+when they leap down the crags and drown the villages?--The blast! the
+blast! Up, spine, and meet it! (LEAPS TO HIS FEET.)
+
+PORTUGUESE SAILOR.
+How the sea rolls swashing 'gainst the side! Stand by for reefing,
+hearties! the winds are just crossing swords, pell-mell they'll go
+lunging presently.
+
+DANISH SAILOR.
+Crack, crack, old ship! so long as thou crackest, thou holdest! Well
+done! The mate there holds ye to it stiffly. He's no more afraid
+than the isle fort at Cattegat, put there to fight the Baltic with
+storm-lashed guns, on which the sea-salt cakes!
+
+4TH NANTUCKET SAILOR.
+He has his orders, mind ye that. I heard old Ahab tell him he must
+always kill a squall, something as they burst a waterspout with a
+pistol--fire your ship right into it!
+
+ENGLISH SAILOR.
+Blood! but that old man's a grand old cove! We are the lads to hunt
+him up his whale!
+
+ALL.
+Aye! aye!
+
+OLD MANX SAILOR.
+How the three pines shake! Pines are the hardest sort of tree to
+live when shifted to any other soil, and here there's none but the
+crew's cursed clay. Steady, helmsman! steady. This is the sort of
+weather when brave hearts snap ashore, and keeled hulls split at sea.
+Our captain has his birthmark; look yonder, boys, there's another in
+the sky--lurid-like, ye see, all else pitch black.
+
+DAGGOO.
+What of that? Who's afraid of black's afraid of me! I'm quarried
+out of it!
+
+SPANISH SAILOR.
+(ASIDE.) He wants to bully, ah!--the old grudge makes me touchy
+(ADVANCING.) Aye, harpooneer, thy race is the undeniable dark side of
+mankind--devilish dark at that. No offence.
+
+DAGGOO (GRIMLY).
+None.
+
+ST. JAGO'S SAILOR.
+That Spaniard's mad or drunk. But that can't be, or else in his one
+case our old Mogul's fire-waters are somewhat long in working.
+
+5TH NANTUCKET SAILOR.
+What's that I saw--lightning? Yes.
+
+SPANISH SAILOR.
+No; Daggoo showing his teeth.
+
+DAGGOO (SPRINGING).
+Swallow thine, mannikin! White skin, white liver!
+
+SPANISH SAILOR (MEETING HIM).
+Knife thee heartily! big frame, small spirit!
+
+ALL.
+A row! a row! a row!
+
+TASHTEGO (WITH A WHIFF).
+A row a'low, and a row aloft--Gods and men--both brawlers! Humph!
+
+BELFAST SAILOR.
+A row! arrah a row! The Virgin be blessed, a row! Plunge in with
+ye!
+
+ENGLISH SAILOR.
+Fair play! Snatch the Spaniard's knife! A ring, a ring!
+
+OLD MANX SAILOR.
+Ready formed. There! the ringed horizon. In that ring Cain struck
+Abel. Sweet work, right work! No? Why then, God, mad'st thou the
+ring?
+
+MATE'S VOICE FROM THE QUARTER-DECK.
+Hands by the halyards! in top-gallant sails! Stand by to reef
+topsails!
+
+ALL.
+The squall! the squall! jump, my jollies! (THEY SCATTER.)
+
+
+PIP (SHRINKING UNDER THE WINDLASS).
+Jollies? Lord help such jollies! Crish, crash! there goes the
+jib-stay! Blang-whang! God! Duck lower, Pip, here comes the royal
+yard! It's worse than being in the whirled woods, the last day of
+the year! Who'd go climbing after chestnuts now? But there they
+go, all cursing, and here I don't. Fine prospects to 'em; they're on
+the road to heaven. Hold on hard! Jimmini, what a squall! But
+those chaps there are worse yet--they are your white squalls, they.
+White squalls? white whale, shirr! shirr! Here have I heard all
+their chat just now, and the white whale--shirr! shirr!--but spoken
+of once! and only this evening--it makes me jingle all over like my
+tambourine--that anaconda of an old man swore 'em in to hunt him!
+Oh, thou big white God aloft there somewhere in yon darkness, have
+mercy on this small black boy down here; preserve him from all men
+that have no bowels to feel fear!
+
+
+
+CHAPTER 41
+
+Moby Dick.
+
+
+I, Ishmael, was one of that crew; my shouts had gone up with the
+rest; my oath had been welded with theirs; and stronger I shouted,
+and more did I hammer and clinch my oath, because of the dread in my
+soul. A wild, mystical, sympathetical feeling was in me; Ahab's
+quenchless feud seemed mine. With greedy ears I learned the history
+of that murderous monster against whom I and all the others had taken
+our oaths of violence and revenge.
+
+For some time past, though at intervals only, the unaccompanied,
+secluded White Whale had haunted those uncivilized seas mostly
+frequented by the Sperm Whale fishermen. But not all of them knew of
+his existence; only a few of them, comparatively, had knowingly seen
+him; while the number who as yet had actually and knowingly given
+battle to him, was small indeed. For, owing to the large number of
+whale-cruisers; the disorderly way they were sprinkled over the
+entire watery circumference, many of them adventurously pushing their
+quest along solitary latitudes, so as seldom or never for a whole
+twelvemonth or more on a stretch, to encounter a single news-telling
+sail of any sort; the inordinate length of each separate voyage; the
+irregularity of the times of sailing from home; all these, with other
+circumstances, direct and indirect, long obstructed the spread
+through the whole world-wide whaling-fleet of the special
+individualizing tidings concerning Moby Dick. It was hardly to be
+doubted, that several vessels reported to have encountered, at such
+or such a time, or on such or such a meridian, a Sperm Whale of
+uncommon magnitude and malignity, which whale, after doing great
+mischief to his assailants, had completely escaped them; to some
+minds it was not an unfair presumption, I say, that the whale in
+question must have been no other than Moby Dick. Yet as of late the
+Sperm Whale fishery had been marked by various and not unfrequent
+instances of great ferocity, cunning, and malice in the monster
+attacked; therefore it was, that those who by accident ignorantly
+gave battle to Moby Dick; such hunters, perhaps, for the most part,
+were content to ascribe the peculiar terror he bred, more, as it
+were, to the perils of the Sperm Whale fishery at large, than to the
+individual cause. In that way, mostly, the disastrous encounter
+between Ahab and the whale had hitherto been popularly regarded.
+
+And as for those who, previously hearing of the White Whale, by
+chance caught sight of him; in the beginning of the thing they had
+every one of them, almost, as boldly and fearlessly lowered for him,
+as for any other whale of that species. But at length, such
+calamities did ensue in these assaults--not restricted to sprained
+wrists and ankles, broken limbs, or devouring amputations--but fatal
+to the last degree of fatality; those repeated disastrous repulses,
+all accumulating and piling their terrors upon Moby Dick; those
+things had gone far to shake the fortitude of many brave hunters, to
+whom the story of the White Whale had eventually come.
+
+Nor did wild rumors of all sorts fail to exaggerate, and still the
+more horrify the true histories of these deadly encounters. For not
+only do fabulous rumors naturally grow out of the very body of all
+surprising terrible events,--as the smitten tree gives birth to its
+fungi; but, in maritime life, far more than in that of terra firma,
+wild rumors abound, wherever there is any adequate reality for them
+to cling to. And as the sea surpasses the land in this matter, so
+the whale fishery surpasses every other sort of maritime life, in the
+wonderfulness and fearfulness of the rumors which sometimes circulate
+there. For not only are whalemen as a body unexempt from that
+ignorance and superstitiousness hereditary to all sailors; but of all
+sailors, they are by all odds the most directly brought into contact
+with whatever is appallingly astonishing in the sea; face to face
+they not only eye its greatest marvels, but, hand to jaw, give battle
+to them. Alone, in such remotest waters, that though you sailed a
+thousand miles, and passed a thousand shores, you would not come to
+any chiseled hearth-stone, or aught hospitable beneath that part of
+the sun; in such latitudes and longitudes, pursuing too such a
+calling as he does, the whaleman is wrapped by influences all tending
+to make his fancy pregnant with many a mighty birth.
+
+No wonder, then, that ever gathering volume from the mere transit
+over the widest watery spaces, the outblown rumors of the White Whale
+did in the end incorporate with themselves all manner of morbid
+hints, and half-formed foetal suggestions of supernatural agencies,
+which eventually invested Moby Dick with new terrors unborrowed from
+anything that visibly appears. So that in many cases such a panic
+did he finally strike, that few who by those rumors, at least, had
+heard of the White Whale, few of those hunters were willing to
+encounter the perils of his jaw.
+
+But there were still other and more vital practical influences at
+work. Not even at the present day has the original prestige of the
+Sperm Whale, as fearfully distinguished from all other species of the
+leviathan, died out of the minds of the whalemen as a body. There
+are those this day among them, who, though intelligent and courageous
+enough in offering battle to the Greenland or Right whale, would
+perhaps--either from professional inexperience, or incompetency, or
+timidity, decline a contest with the Sperm Whale; at any rate, there
+are plenty of whalemen, especially among those whaling nations not
+sailing under the American flag, who have never hostilely encountered
+the Sperm Whale, but whose sole knowledge of the leviathan is
+restricted to the ignoble monster primitively pursued in the North;
+seated on their hatches, these men will hearken with a childish
+fireside interest and awe, to the wild, strange tales of Southern
+whaling. Nor is the pre-eminent tremendousness of the great Sperm
+Whale anywhere more feelingly comprehended, than on board of those
+prows which stem him.
+
+And as if the now tested reality of his might had in former legendary
+times thrown its shadow before it; we find some book
+naturalists--Olassen and Povelson--declaring the Sperm Whale not only
+to be a consternation to every other creature in the sea, but also to
+be so incredibly ferocious as continually to be athirst for human
+blood. Nor even down to so late a time as Cuvier's, were these or
+almost similar impressions effaced. For in his Natural History, the
+Baron himself affirms that at sight of the Sperm Whale, all fish
+(sharks included) are "struck with the most lively terrors," and
+"often in the precipitancy of their flight dash themselves against
+the rocks with such violence as to cause instantaneous death." And
+however the general experiences in the fishery may amend such reports
+as these; yet in their full terribleness, even to the bloodthirsty
+item of Povelson, the superstitious belief in them is, in some
+vicissitudes of their vocation, revived in the minds of the hunters.
+
+So that overawed by the rumors and portents concerning him, not a few
+of the fishermen recalled, in reference to Moby Dick, the earlier
+days of the Sperm Whale fishery, when it was oftentimes hard to
+induce long practised Right whalemen to embark in the perils of this
+new and daring warfare; such men protesting that although other
+leviathans might be hopefully pursued, yet to chase and point lance
+at such an apparition as the Sperm Whale was not for mortal man.
+That to attempt it, would be inevitably to be torn into a quick
+eternity. On this head, there are some remarkable documents that may
+be consulted.
+
+Nevertheless, some there were, who even in the face of these things
+were ready to give chase to Moby Dick; and a still greater number
+who, chancing only to hear of him distantly and vaguely, without the
+specific details of any certain calamity, and without superstitious
+accompaniments, were sufficiently hardy not to flee from the battle
+if offered.
+
+One of the wild suggestions referred to, as at last coming to be
+linked with the White Whale in the minds of the superstitiously
+inclined, was the unearthly conceit that Moby Dick was ubiquitous;
+that he had actually been encountered in opposite latitudes at one
+and the same instant of time.
+
+Nor, credulous as such minds must have been, was this conceit
+altogether without some faint show of superstitious probability. For
+as the secrets of the currents in the seas have never yet been
+divulged, even to the most erudite research; so the hidden ways of
+the Sperm Whale when beneath the surface remain, in great part,
+unaccountable to his pursuers; and from time to time have originated
+the most curious and contradictory speculations regarding them,
+especially concerning the mystic modes whereby, after sounding to a
+great depth, he transports himself with such vast swiftness to the
+most widely distant points.
+
+It is a thing well known to both American and English whale-ships,
+and as well a thing placed upon authoritative record years ago by
+Scoresby, that some whales have been captured far north in the
+Pacific, in whose bodies have been found the barbs of harpoons darted
+in the Greenland seas. Nor is it to be gainsaid, that in some of
+these instances it has been declared that the interval of time
+between the two assaults could not have exceeded very many days.
+Hence, by inference, it has been believed by some whalemen, that the
+Nor' West Passage, so long a problem to man, was never a problem to
+the whale. So that here, in the real living experience of living
+men, the prodigies related in old times of the inland Strello
+mountain in Portugal (near whose top there was said to be a lake in
+which the wrecks of ships floated up to the surface); and that still
+more wonderful story of the Arethusa fountain near Syracuse (whose
+waters were believed to have come from the Holy Land by an
+underground passage); these fabulous narrations are almost fully
+equalled by the realities of the whalemen.
+
+Forced into familiarity, then, with such prodigies as these; and
+knowing that after repeated, intrepid assaults, the White Whale had
+escaped alive; it cannot be much matter of surprise that some
+whalemen should go still further in their superstitions; declaring
+Moby Dick not only ubiquitous, but immortal (for immortality is but
+ubiquity in time); that though groves of spears should be planted in
+his flanks, he would still swim away unharmed; or if indeed he should
+ever be made to spout thick blood, such a sight would be but a
+ghastly deception; for again in unensanguined billows hundreds of
+leagues away, his unsullied jet would once more be seen.
+
+But even stripped of these supernatural surmisings, there was enough
+in the earthly make and incontestable character of the monster to
+strike the imagination with unwonted power. For, it was not so much
+his uncommon bulk that so much distinguished him from other sperm
+whales, but, as was elsewhere thrown out--a peculiar snow-white
+wrinkled forehead, and a high, pyramidical white hump. These were
+his prominent features; the tokens whereby, even in the limitless,
+uncharted seas, he revealed his identity, at a long distance, to
+those who knew him.
+
+The rest of his body was so streaked, and spotted, and marbled with
+the same shrouded hue, that, in the end, he had gained his
+distinctive appellation of the White Whale; a name, indeed, literally
+justified by his vivid aspect, when seen gliding at high noon through
+a dark blue sea, leaving a milky-way wake of creamy foam, all
+spangled with golden gleamings.
+
+Nor was it his unwonted magnitude, nor his remarkable hue, nor yet
+his deformed lower jaw, that so much invested the whale with natural
+terror, as that unexampled, intelligent malignity which, according to
+specific accounts, he had over and over again evinced in his
+assaults. More than all, his treacherous retreats struck more of
+dismay than perhaps aught else. For, when swimming before his
+exulting pursuers, with every apparent symptom of alarm, he had
+several times been known to turn round suddenly, and, bearing down
+upon them, either stave their boats to splinters, or drive them back
+in consternation to their ship.
+
+Already several fatalities had attended his chase. But though
+similar disasters, however little bruited ashore, were by no means
+unusual in the fishery; yet, in most instances, such seemed the White
+Whale's infernal aforethought of ferocity, that every dismembering or
+death that he caused, was not wholly regarded as having been
+inflicted by an unintelligent agent.
+
+Judge, then, to what pitches of inflamed, distracted fury the minds
+of his more desperate hunters were impelled, when amid the chips of
+chewed boats, and the sinking limbs of torn comrades, they swam out
+of the white curds of the whale's direful wrath into the serene,
+exasperating sunlight, that smiled on, as if at a birth or a bridal.
+
+His three boats stove around him, and oars and men both whirling in
+the eddies; one captain, seizing the line-knife from his broken prow,
+had dashed at the whale, as an Arkansas duellist at his foe, blindly
+seeking with a six inch blade to reach the fathom-deep life of the
+whale. That captain was Ahab. And then it was, that suddenly
+sweeping his sickle-shaped lower jaw beneath him, Moby Dick had
+reaped away Ahab's leg, as a mower a blade of grass in the field. No
+turbaned Turk, no hired Venetian or Malay, could have smote him with
+more seeming malice. Small reason was there to doubt, then, that
+ever since that almost fatal encounter, Ahab had cherished a wild
+vindictiveness against the whale, all the more fell for that in his
+frantic morbidness he at last came to identify with him, not only all
+his bodily woes, but all his intellectual and spiritual
+exasperations. The White Whale swam before him as the monomaniac
+incarnation of all those malicious agencies which some deep men feel
+eating in them, till they are left living on with half a heart and
+half a lung. That intangible malignity which has been from the
+beginning; to whose dominion even the modern Christians ascribe
+one-half of the worlds; which the ancient Ophites of the east
+reverenced in their statue devil;--Ahab did not fall down and worship
+it like them; but deliriously transferring its idea to the abhorred
+white whale, he pitted himself, all mutilated, against it. All that
+most maddens and torments; all that stirs up the lees of things; all
+truth with malice in it; all that cracks the sinews and cakes the
+brain; all the subtle demonisms of life and thought; all evil, to
+crazy Ahab, were visibly personified, and made practically assailable
+in Moby Dick. He piled upon the whale's white hump the sum of all
+the general rage and hate felt by his whole race from Adam down; and
+then, as if his chest had been a mortar, he burst his hot heart's
+shell upon it.
+
+It is not probable that this monomania in him took its instant rise
+at the precise time of his bodily dismemberment. Then, in darting at
+the monster, knife in hand, he had but given loose to a sudden,
+passionate, corporal animosity; and when he received the stroke that
+tore him, he probably but felt the agonizing bodily laceration, but
+nothing more. Yet, when by this collision forced to turn towards
+home, and for long months of days and weeks, Ahab and anguish lay
+stretched together in one hammock, rounding in mid winter that
+dreary, howling Patagonian Cape; then it was, that his torn body and
+gashed soul bled into one another; and so interfusing, made him mad.
+That it was only then, on the homeward voyage, after the encounter,
+that the final monomania seized him, seems all but certain from the
+fact that, at intervals during the passage, he was a raving lunatic;
+and, though unlimbed of a leg, yet such vital strength yet lurked in
+his Egyptian chest, and was moreover intensified by his delirium,
+that his mates were forced to lace him fast, even there, as he
+sailed, raving in his hammock. In a strait-jacket, he swung to the
+mad rockings of the gales. And, when running into more sufferable
+latitudes, the ship, with mild stun'sails spread, floated across the
+tranquil tropics, and, to all appearances, the old man's delirium
+seemed left behind him with the Cape Horn swells, and he came forth
+from his dark den into the blessed light and air; even then, when he
+bore that firm, collected front, however pale, and issued his calm
+orders once again; and his mates thanked God the direful madness was
+now gone; even then, Ahab, in his hidden self, raved on. Human
+madness is oftentimes a cunning and most feline thing. When you
+think it fled, it may have but become transfigured into some still
+subtler form. Ahab's full lunacy subsided not, but deepeningly
+contracted; like the unabated Hudson, when that noble Northman flows
+narrowly, but unfathomably through the Highland gorge. But, as in
+his narrow-flowing monomania, not one jot of Ahab's broad madness had
+been left behind; so in that broad madness, not one jot of his great
+natural intellect had perished. That before living agent, now became
+the living instrument. If such a furious trope may stand, his
+special lunacy stormed his general sanity, and carried it, and turned
+all its concentred cannon upon its own mad mark; so that far from
+having lost his strength, Ahab, to that one end, did now possess a
+thousand fold more potency than ever he had sanely brought to bear
+upon any one reasonable object.
+
+This is much; yet Ahab's larger, darker, deeper part remains
+unhinted. But vain to popularize profundities, and all truth is
+profound. Winding far down from within the very heart of this spiked
+Hotel de Cluny where we here stand--however grand and wonderful, now
+quit it;--and take your way, ye nobler, sadder souls, to those vast
+Roman halls of Thermes; where far beneath the fantastic towers of
+man's upper earth, his root of grandeur, his whole awful essence sits
+in bearded state; an antique buried beneath antiquities, and throned
+on torsoes! So with a broken throne, the great gods mock that
+captive king; so like a Caryatid, he patient sits, upholding on his
+frozen brow the piled entablatures of ages. Wind ye down there, ye
+prouder, sadder souls! question that proud, sad king! A family
+likeness! aye, he did beget ye, ye young exiled royalties; and from
+your grim sire only will the old State-secret come.
+
+Now, in his heart, Ahab had some glimpse of this, namely: all my
+means are sane, my motive and my object mad. Yet without power to
+kill, or change, or shun the fact; he likewise knew that to mankind
+he did long dissemble; in some sort, did still. But that thing of
+his dissembling was only subject to his perceptibility, not to his
+will determinate. Nevertheless, so well did he succeed in that
+dissembling, that when with ivory leg he stepped ashore at last, no
+Nantucketer thought him otherwise than but naturally grieved, and
+that to the quick, with the terrible casualty which had overtaken
+him.
+
+The report of his undeniable delirium at sea was likewise popularly
+ascribed to a kindred cause. And so too, all the added moodiness
+which always afterwards, to the very day of sailing in the Pequod on
+the present voyage, sat brooding on his brow. Nor is it so very
+unlikely, that far from distrusting his fitness for another whaling
+voyage, on account of such dark symptoms, the calculating people of
+that prudent isle were inclined to harbor the conceit, that for those
+very reasons he was all the better qualified and set on edge, for a
+pursuit so full of rage and wildness as the bloody hunt of whales.
+Gnawed within and scorched without, with the infixed, unrelenting
+fangs of some incurable idea; such an one, could he be found, would
+seem the very man to dart his iron and lift his lance against the
+most appalling of all brutes. Or, if for any reason thought to be
+corporeally incapacitated for that, yet such an one would seem
+superlatively competent to cheer and howl on his underlings to the
+attack. But be all this as it may, certain it is, that with the mad
+secret of his unabated rage bolted up and keyed in him, Ahab had
+purposely sailed upon the present voyage with the one only and
+all-engrossing object of hunting the White Whale. Had any one of his
+old acquaintances on shore but half dreamed of what was lurking in
+him then, how soon would their aghast and righteous souls have
+wrenched the ship from such a fiendish man! They were bent on
+profitable cruises, the profit to be counted down in dollars from the
+mint. He was intent on an audacious, immitigable, and supernatural
+revenge.
+
+Here, then, was this grey-headed, ungodly old man, chasing with
+curses a Job's whale round the world, at the head of a crew, too,
+chiefly made up of mongrel renegades, and castaways, and
+cannibals--morally enfeebled also, by the incompetence of mere
+unaided virtue or right-mindedness in Starbuck, the invunerable
+jollity of indifference and recklessness in Stubb, and the pervading
+mediocrity in Flask. Such a crew, so officered, seemed specially
+picked and packed by some infernal fatality to help him to his
+monomaniac revenge. How it was that they so aboundingly responded to
+the old man's ire--by what evil magic their souls were possessed,
+that at times his hate seemed almost theirs; the White Whale as much
+their insufferable foe as his; how all this came to be--what the
+White Whale was to them, or how to their unconscious understandings,
+also, in some dim, unsuspected way, he might have seemed the gliding
+great demon of the seas of life,--all this to explain, would be to
+dive deeper than Ishmael can go. The subterranean miner that works
+in us all, how can one tell whither leads his shaft by the ever
+shifting, muffled sound of his pick? Who does not feel the
+irresistible arm drag? What skiff in tow of a seventy-four can stand
+still? For one, I gave myself up to the abandonment of the time and
+the place; but while yet all a-rush to encounter the whale, could see
+naught in that brute but the deadliest ill.
+
+
+
+CHAPTER 42
+
+The Whiteness of The Whale.
+
+
+What the white whale was to Ahab, has been hinted; what, at times, he
+was to me, as yet remains unsaid.
+
+Aside from those more obvious considerations touching Moby Dick,
+which could not but occasionally awaken in any man's soul some alarm,
+there was another thought, or rather vague, nameless horror
+concerning him, which at times by its intensity completely
+overpowered all the rest; and yet so mystical and well nigh ineffable
+was it, that I almost despair of putting it in a comprehensible form.
+It was the whiteness of the whale that above all things appalled me.
+But how can I hope to explain myself here; and yet, in some dim,
+random way, explain myself I must, else all these chapters might be
+naught.
+
+Though in many natural objects, whiteness refiningly enhances beauty,
+as if imparting some special virtue of its own, as in marbles,
+japonicas, and pearls; and though various nations have in some way
+recognised a certain royal preeminence in this hue; even the
+barbaric, grand old kings of Pegu placing the title "Lord of the
+White Elephants" above all their other magniloquent ascriptions of
+dominion; and the modern kings of Siam unfurling the same snow-white
+quadruped in the royal standard; and the Hanoverian flag bearing the
+one figure of a snow-white charger; and the great Austrian Empire,
+Caesarian, heir to overlording Rome, having for the imperial colour
+the same imperial hue; and though this pre-eminence in it applies to
+the human race itself, giving the white man ideal mastership over
+every dusky tribe; and though, besides, all this, whiteness has been
+even made significant of gladness, for among the Romans a white stone
+marked a joyful day; and though in other mortal sympathies and
+symbolizings, this same hue is made the emblem of many touching,
+noble things--the innocence of brides, the benignity of age; though
+among the Red Men of America the giving of the white belt of wampum
+was the deepest pledge of honour; though in many climes, whiteness
+typifies the majesty of Justice in the ermine of the Judge, and
+contributes to the daily state of kings and queens drawn by
+milk-white steeds; though even in the higher mysteries of the most
+august religions it has been made the symbol of the divine
+spotlessness and power; by the Persian fire worshippers, the white
+forked flame being held the holiest on the altar; and in the Greek
+mythologies, Great Jove himself being made incarnate in a snow-white
+bull; and though to the noble Iroquois, the midwinter sacrifice of
+the sacred White Dog was by far the holiest festival of their
+theology, that spotless, faithful creature being held the purest
+envoy they could send to the Great Spirit with the annual tidings of
+their own fidelity; and though directly from the Latin word for
+white, all Christian priests derive the name of one part of their
+sacred vesture, the alb or tunic, worn beneath the cassock; and
+though among the holy pomps of the Romish faith, white is specially
+employed in the celebration of the Passion of our Lord; though in the
+Vision of St. John, white robes are given to the redeemed, and the
+four-and-twenty elders stand clothed in white before the great-white
+throne, and the Holy One that sitteth there white like wool; yet for
+all these accumulated associations, with whatever is sweet, and
+honourable, and sublime, there yet lurks an elusive something in the
+innermost idea of this hue, which strikes more of panic to the soul
+than that redness which affrights in blood.
+
+This elusive quality it is, which causes the thought of whiteness,
+when divorced from more kindly associations, and coupled with any
+object terrible in itself, to heighten that terror to the furthest
+bounds. Witness the white bear of the poles, and the white shark of
+the tropics; what but their smooth, flaky whiteness makes them the
+transcendent horrors they are? That ghastly whiteness it is which
+imparts such an abhorrent mildness, even more loathsome than
+terrific, to the dumb gloating of their aspect. So that not the
+fierce-fanged tiger in his heraldic coat can so stagger courage as
+the white-shrouded bear or shark.*
+
+
+*With reference to the Polar bear, it may possibly be urged by him
+who would fain go still deeper into this matter, that it is not the
+whiteness, separately regarded, which heightens the intolerable
+hideousness of that brute; for, analysed, that heightened
+hideousness, it might be said, only rises from the circumstance, that
+the irresponsible ferociousness of the creature stands invested in
+the fleece of celestial innocence and love; and hence, by bringing
+together two such opposite emotions in our minds, the Polar bear
+frightens us with so unnatural a contrast. But even assuming all
+this to be true; yet, were it not for the whiteness, you would not
+have that intensified terror.
+
+As for the white shark, the white gliding ghostliness of repose in
+that creature, when beheld in his ordinary moods, strangely tallies
+with the same quality in the Polar quadruped. This peculiarity is
+most vividly hit by the French in the name they bestow upon that
+fish. The Romish mass for the dead begins with "Requiem eternam"
+(eternal rest), whence REQUIEM denominating the mass itself, and any
+other funeral music. Now, in allusion to the white, silent stillness
+of death in this shark, and the mild deadliness of his habits, the
+French call him REQUIN.
+
+
+Bethink thee of the albatross, whence come those clouds of spiritual
+wonderment and pale dread, in which that white phantom sails in all
+imaginations? Not Coleridge first threw that spell; but God's great,
+unflattering laureate, Nature.*
+
+
+*I remember the first albatross I ever saw. It was during a
+prolonged gale, in waters hard upon the Antarctic seas. From my
+forenoon watch below, I ascended to the overclouded deck; and there,
+dashed upon the main hatches, I saw a regal, feathery thing of
+unspotted whiteness, and with a hooked, Roman bill sublime. At
+intervals, it arched forth its vast archangel wings, as if to embrace
+some holy ark. Wondrous flutterings and throbbings shook it. Though
+bodily unharmed, it uttered cries, as some king's ghost in
+supernatural distress. Through its inexpressible, strange eyes,
+methought I peeped to secrets which took hold of God. As Abraham
+before the angels, I bowed myself; the white thing was so white, its
+wings so wide, and in those for ever exiled waters, I had lost the
+miserable warping memories of traditions and of towns. Long I gazed
+at that prodigy of plumage. I cannot tell, can only hint, the things
+that darted through me then. But at last I awoke; and turning, asked
+a sailor what bird was this. A goney, he replied. Goney! never had
+heard that name before; is it conceivable that this glorious thing is
+utterly unknown to men ashore! never! But some time after, I learned
+that goney was some seaman's name for albatross. So that by no
+possibility could Coleridge's wild Rhyme have had aught to do with
+those mystical impressions which were mine, when I saw that bird upon
+our deck. For neither had I then read the Rhyme, nor knew the bird
+to be an albatross. Yet, in saying this, I do but indirectly burnish
+a little brighter the noble merit of the poem and the poet.
+
+I assert, then, that in the wondrous bodily whiteness of the bird
+chiefly lurks the secret of the spell; a truth the more evinced in
+this, that by a solecism of terms there are birds called grey
+albatrosses; and these I have frequently seen, but never with such
+emotions as when I beheld the Antarctic fowl.
+
+But how had the mystic thing been caught? Whisper it not, and I will
+tell; with a treacherous hook and line, as the fowl floated on the
+sea. At last the Captain made a postman of it; tying a lettered,
+leathern tally round its neck, with the ship's time and place; and
+then letting it escape. But I doubt not, that leathern tally, meant
+for man, was taken off in Heaven, when the white fowl flew to join
+the wing-folding, the invoking, and adoring cherubim!
+
+
+Most famous in our Western annals and Indian traditions is that of
+the White Steed of the Prairies; a magnificent milk-white charger,
+large-eyed, small-headed, bluff-chested, and with the dignity of a
+thousand monarchs in his lofty, overscorning carriage. He was the
+elected Xerxes of vast herds of wild horses, whose pastures in those
+days were only fenced by the Rocky Mountains and the Alleghanies. At
+their flaming head he westward trooped it like that chosen star which
+every evening leads on the hosts of light. The flashing cascade of
+his mane, the curving comet of his tail, invested him with housings
+more resplendent than gold and silver-beaters could have furnished
+him. A most imperial and archangelical apparition of that unfallen,
+western world, which to the eyes of the old trappers and hunters
+revived the glories of those primeval times when Adam walked majestic
+as a god, bluff-browed and fearless as this mighty steed. Whether
+marching amid his aides and marshals in the van of countless cohorts
+that endlessly streamed it over the plains, like an Ohio; or whether
+with his circumambient subjects browsing all around at the horizon,
+the White Steed gallopingly reviewed them with warm nostrils
+reddening through his cool milkiness; in whatever aspect he presented
+himself, always to the bravest Indians he was the object of trembling
+reverence and awe. Nor can it be questioned from what stands on
+legendary record of this noble horse, that it was his spiritual
+whiteness chiefly, which so clothed him with divineness; and that
+this divineness had that in it which, though commanding worship, at
+the same time enforced a certain nameless terror.
+
+But there are other instances where this whiteness loses all that
+accessory and strange glory which invests it in the White Steed and
+Albatross.
+
+What is it that in the Albino man so peculiarly repels and often
+shocks the eye, as that sometimes he is loathed by his own kith and
+kin! It is that whiteness which invests him, a thing expressed by
+the name he bears. The Albino is as well made as other men--has no
+substantive deformity--and yet this mere aspect of all-pervading
+whiteness makes him more strangely hideous than the ugliest abortion.
+Why should this be so?
+
+Nor, in quite other aspects, does Nature in her least palpable but
+not the less malicious agencies, fail to enlist among her forces this
+crowning attribute of the terrible. From its snowy aspect, the
+gauntleted ghost of the Southern Seas has been denominated the White
+Squall. Nor, in some historic instances, has the art of human malice
+omitted so potent an auxiliary. How wildly it heightens the effect
+of that passage in Froissart, when, masked in the snowy symbol of
+their faction, the desperate White Hoods of Ghent murder their
+bailiff in the market-place!
+
+Nor, in some things, does the common, hereditary experience of all
+mankind fail to bear witness to the supernaturalism of this hue. It
+cannot well be doubted, that the one visible quality in the aspect of
+the dead which most appals the gazer, is the marble pallor lingering
+there; as if indeed that pallor were as much like the badge of
+consternation in the other world, as of mortal trepidation here. And
+from that pallor of the dead, we borrow the expressive hue of the
+shroud in which we wrap them. Nor even in our superstitions do we
+fail to throw the same snowy mantle round our phantoms; all ghosts
+rising in a milk-white fog--Yea, while these terrors seize us, let us
+add, that even the king of terrors, when personified by the
+evangelist, rides on his pallid horse.
+
+Therefore, in his other moods, symbolize whatever grand or gracious
+thing he will by whiteness, no man can deny that in its profoundest
+idealized significance it calls up a peculiar apparition to the soul.
+
+But though without dissent this point be fixed, how is mortal man to
+account for it? To analyse it, would seem impossible. Can we,
+then, by the citation of some of those instances wherein this thing
+of whiteness--though for the time either wholly or in great part
+stripped of all direct associations calculated to impart to it aught
+fearful, but nevertheless, is found to exert over us the same
+sorcery, however modified;--can we thus hope to light upon some
+chance clue to conduct us to the hidden cause we seek?
+
+Let us try. But in a matter like this, subtlety appeals to subtlety,
+and without imagination no man can follow another into these halls.
+And though, doubtless, some at least of the imaginative impressions
+about to be presented may have been shared by most men, yet few
+perhaps were entirely conscious of them at the time, and therefore
+may not be able to recall them now.
+
+Why to the man of untutored ideality, who happens to be but loosely
+acquainted with the peculiar character of the day, does the bare
+mention of Whitsuntide marshal in the fancy such long, dreary,
+speechless processions of slow-pacing pilgrims, down-cast and hooded
+with new-fallen snow? Or, to the unread, unsophisticated Protestant
+of the Middle American States, why does the passing mention of a
+White Friar or a White Nun, evoke such an eyeless statue in the soul?
+
+Or what is there apart from the traditions of dungeoned warriors and
+kings (which will not wholly account for it) that makes the White
+Tower of London tell so much more strongly on the imagination of an
+untravelled American, than those other storied structures, its
+neighbors--the Byward Tower, or even the Bloody? And those sublimer
+towers, the White Mountains of New Hampshire, whence, in peculiar
+moods, comes that gigantic ghostliness over the soul at the bare
+mention of that name, while the thought of Virginia's Blue Ridge is
+full of a soft, dewy, distant dreaminess? Or why, irrespective of
+all latitudes and longitudes, does the name of the White Sea exert
+such a spectralness over the fancy, while that of the Yellow Sea
+lulls us with mortal thoughts of long lacquered mild afternoons on
+the waves, followed by the gaudiest and yet sleepiest of sunsets?
+Or, to choose a wholly unsubstantial instance, purely addressed to
+the fancy, why, in reading the old fairy tales of Central Europe,
+does "the tall pale man" of the Hartz forests, whose changeless
+pallor unrustlingly glides through the green of the groves--why is
+this phantom more terrible than all the whooping imps of the
+Blocksburg?
+
+Nor is it, altogether, the remembrance of her cathedral-toppling
+earthquakes; nor the stampedoes of her frantic seas; nor the
+tearlessness of arid skies that never rain; nor the sight of her
+wide field of leaning spires, wrenched cope-stones, and crosses all
+adroop (like canted yards of anchored fleets); and her suburban
+avenues of house-walls lying over upon each other, as a tossed pack
+of cards;--it is not these things alone which make tearless Lima, the
+strangest, saddest city thou can'st see. For Lima has taken the
+white veil; and there is a higher horror in this whiteness of her
+woe. Old as Pizarro, this whiteness keeps her ruins for ever new;
+admits not the cheerful greenness of complete decay; spreads over her
+broken ramparts the rigid pallor of an apoplexy that fixes its own
+distortions.
+
+I know that, to the common apprehension, this phenomenon of whiteness
+is not confessed to be the prime agent in exaggerating the terror of
+objects otherwise terrible; nor to the unimaginative mind is there
+aught of terror in those appearances whose awfulness to another mind
+almost solely consists in this one phenomenon, especially when
+exhibited under any form at all approaching to muteness or
+universality. What I mean by these two statements may perhaps be
+respectively elucidated by the following examples.
+
+First: The mariner, when drawing nigh the coasts of foreign lands, if
+by night he hear the roar of breakers, starts to vigilance, and feels
+just enough of trepidation to sharpen all his faculties; but under
+precisely similar circumstances, let him be called from his hammock
+to view his ship sailing through a midnight sea of milky
+whiteness--as if from encircling headlands shoals of combed white
+bears were swimming round him, then he feels a silent, superstitious
+dread; the shrouded phantom of the whitened waters is horrible to him
+as a real ghost; in vain the lead assures him he is still off
+soundings; heart and helm they both go down; he never rests till blue
+water is under him again. Yet where is the mariner who will tell
+thee, "Sir, it was not so much the fear of striking hidden rocks, as
+the fear of that hideous whiteness that so stirred me?"
+
+Second: To the native Indian of Peru, the continual sight of the
+snowhowdahed Andes conveys naught of dread, except, perhaps, in the
+mere fancying of the eternal frosted desolateness reigning at such
+vast altitudes, and the natural conceit of what a fearfulness it
+would be to lose oneself in such inhuman solitudes. Much the same is
+it with the backwoodsman of the West, who with comparative
+indifference views an unbounded prairie sheeted with driven snow, no
+shadow of tree or twig to break the fixed trance of whiteness. Not
+so the sailor, beholding the scenery of the Antarctic seas; where at
+times, by some infernal trick of legerdemain in the powers of frost
+and air, he, shivering and half shipwrecked, instead of rainbows
+speaking hope and solace to his misery, views what seems a boundless
+churchyard grinning upon him with its lean ice monuments and
+splintered crosses.
+
+But thou sayest, methinks that white-lead chapter about whiteness is
+but a white flag hung out from a craven soul; thou surrenderest to a
+hypo, Ishmael.
+
+Tell me, why this strong young colt, foaled in some peaceful valley
+of Vermont, far removed from all beasts of prey--why is it that upon
+the sunniest day, if you but shake a fresh buffalo robe behind him,
+so that he cannot even see it, but only smells its wild animal
+muskiness--why will he start, snort, and with bursting eyes paw the
+ground in phrensies of affright? There is no remembrance in him of
+any gorings of wild creatures in his green northern home, so that the
+strange muskiness he smells cannot recall to him anything associated
+with the experience of former perils; for what knows he, this New
+England colt, of the black bisons of distant Oregon?
+
+No; but here thou beholdest even in a dumb brute, the instinct of the
+knowledge of the demonism in the world. Though thousands of miles
+from Oregon, still when he smells that savage musk, the rending,
+goring bison herds are as present as to the deserted wild foal of the
+prairies, which this instant they may be trampling into dust.
+
+Thus, then, the muffled rollings of a milky sea; the bleak rustlings
+of the festooned frosts of mountains; the desolate shiftings of the
+windrowed snows of prairies; all these, to Ishmael, are as the
+shaking of that buffalo robe to the frightened colt!
+
+Though neither knows where lie the nameless things of which the
+mystic sign gives forth such hints; yet with me, as with the colt,
+somewhere those things must exist. Though in many of its aspects
+this visible world seems formed in love, the invisible spheres were
+formed in fright.
+
+But not yet have we solved the incantation of this whiteness, and
+learned why it appeals with such power to the soul; and more strange
+and far more portentous--why, as we have seen, it is at once the most
+meaning symbol of spiritual things, nay, the very veil of the
+Christian's Deity; and yet should be as it is, the intensifying agent
+in things the most appalling to mankind.
+
+Is it that by its indefiniteness it shadows forth the heartless voids
+and immensities of the universe, and thus stabs us from behind with
+the thought of annihilation, when beholding the white depths of the
+milky way? Or is it, that as in essence whiteness is not so much a
+colour as the visible absence of colour; and at the same time the
+concrete of all colours; is it for these reasons that there is such a
+dumb blankness, full of meaning, in a wide landscape of snows--a
+colourless, all-colour of atheism from which we shrink? And when we
+consider that other theory of the natural philosophers, that all
+other earthly hues--every stately or lovely emblazoning--the sweet
+tinges of sunset skies and woods; yea, and the gilded velvets of
+butterflies, and the butterfly cheeks of young girls; all these are
+but subtile deceits, not actually inherent in substances, but only
+laid on from without; so that all deified Nature absolutely paints
+like the harlot, whose allurements cover nothing but the
+charnel-house within; and when we proceed further, and consider that
+the mystical cosmetic which produces every one of her hues, the great
+principle of light, for ever remains white or colourless in itself,
+and if operating without medium upon matter, would touch all objects,
+even tulips and roses, with its own blank tinge--pondering all this,
+the palsied universe lies before us a leper; and like wilful
+travellers in Lapland, who refuse to wear coloured and colouring
+glasses upon their eyes, so the wretched infidel gazes himself blind
+at the monumental white shroud that wraps all the prospect around
+him. And of all these things the Albino whale was the symbol.
+Wonder ye then at the fiery hunt?
+
+
+
+CHAPTER 43
+
+Hark!
+
+
+"HIST! Did you hear that noise, Cabaco?
+
+It was the middle-watch; a fair moonlight; the seamen were standing
+in a cordon, extending from one of the fresh-water butts in the
+waist, to the scuttle-butt near the taffrail. In this manner, they
+passed the buckets to fill the scuttle-butt. Standing, for the most
+part, on the hallowed precincts of the quarter-deck, they were
+careful not to speak or rustle their feet. From hand to hand, the
+buckets went in the deepest silence, only broken by the occasional
+flap of a sail, and the steady hum of the unceasingly advancing keel.
+
+It was in the midst of this repose, that Archy, one of the cordon,
+whose post was near the after-hatches, whispered to his neighbor, a
+Cholo, the words above.
+
+"Hist! did you hear that noise, Cabaco?"
+
+"Take the bucket, will ye, Archy? what noise d'ye mean?"
+
+"There it is again--under the hatches--don't you hear it--a cough--it
+sounded like a cough."
+
+"Cough be damned! Pass along that return bucket."
+
+"There again--there it is!--it sounds like two or three sleepers
+turning over, now!"
+
+"Caramba! have done, shipmate, will ye? It's the three soaked
+biscuits ye eat for supper turning over inside of ye--nothing else.
+Look to the bucket!"
+
+"Say what ye will, shipmate; I've sharp ears."
+
+"Aye, you are the chap, ain't ye, that heard the hum of the old
+Quakeress's knitting-needles fifty miles at sea from Nantucket;
+you're the chap."
+
+"Grin away; we'll see what turns up. Hark ye, Cabaco, there is
+somebody down in the after-hold that has not yet been seen on deck;
+and I suspect our old Mogul knows something of it too. I heard Stubb
+tell Flask, one morning watch, that there was something of that sort
+in the wind."
+
+"Tish! the bucket!"
+
+
+
+CHAPTER 44
+
+The Chart.
+
+
+Had you followed Captain Ahab down into his cabin after the squall
+that took place on the night succeeding that wild ratification of his
+purpose with his crew, you would have seen him go to a locker in the
+transom, and bringing out a large wrinkled roll of yellowish sea
+charts, spread them before him on his screwed-down table. Then
+seating himself before it, you would have seen him intently study the
+various lines and shadings which there met his eye; and with slow but
+steady pencil trace additional courses over spaces that before were
+blank. At intervals, he would refer to piles of old log-books beside
+him, wherein were set down the seasons and places in which, on
+various former voyages of various ships, sperm whales had been
+captured or seen.
+
+While thus employed, the heavy pewter lamp suspended in chains over
+his head, continually rocked with the motion of the ship, and for
+ever threw shifting gleams and shadows of lines upon his wrinkled
+brow, till it almost seemed that while he himself was marking out
+lines and courses on the wrinkled charts, some invisible pencil was
+also tracing lines and courses upon the deeply marked chart of his
+forehead.
+
+But it was not this night in particular that, in the solitude of his
+cabin, Ahab thus pondered over his charts. Almost every night they
+were brought out; almost every night some pencil marks were effaced,
+and others were substituted. For with the charts of all four oceans
+before him, Ahab was threading a maze of currents and eddies, with a
+view to the more certain accomplishment of that monomaniac thought of
+his soul.
+
+Now, to any one not fully acquainted with the ways of the leviathans,
+it might seem an absurdly hopeless task thus to seek out one solitary
+creature in the unhooped oceans of this planet. But not so did it
+seem to Ahab, who knew the sets of all tides and currents; and
+thereby calculating the driftings of the sperm whale's food; and,
+also, calling to mind the regular, ascertained seasons for hunting
+him in particular latitudes; could arrive at reasonable surmises,
+almost approaching to certainties, concerning the timeliest day to be
+upon this or that ground in search of his prey.
+
+So assured, indeed, is the fact concerning the periodicalness of the
+sperm whale's resorting to given waters, that many hunters believe
+that, could he be closely observed and studied throughout the world;
+were the logs for one voyage of the entire whale fleet carefully
+collated, then the migrations of the sperm whale would be found to
+correspond in invariability to those of the herring-shoals or the
+flights of swallows. On this hint, attempts have been made to
+construct elaborate migratory charts of the sperm whale.*
+
+
+*Since the above was written, the statement is happily borne out by
+an official circular, issued by Lieutenant Maury, of the National
+Observatory, Washington, April 16th, 1851. By that circular, it
+appears that precisely such a chart is in course of completion; and
+portions of it are presented in the circular. "This chart divides
+the ocean into districts of five degrees of latitude by five degrees
+of longitude; perpendicularly through each of which districts are
+twelve columns for the twelve months; and horizontally through each
+of which districts are three lines; one to show the number of days
+that have been spent in each month in every district, and the two
+others to show the number of days in which whales, sperm or right,
+have been seen."
+
+
+Besides, when making a passage from one feeding-ground to another,
+the sperm whales, guided by some infallible instinct--say, rather,
+secret intelligence from the Deity--mostly swim in VEINS, as they are
+called; continuing their way along a given ocean-line with such
+undeviating exactitude, that no ship ever sailed her course, by any
+chart, with one tithe of such marvellous precision. Though, in these
+cases, the direction taken by any one whale be straight as a
+surveyor's parallel, and though the line of advance be strictly
+confined to its own unavoidable, straight wake, yet the arbitrary
+VEIN in which at these times he is said to swim, generally embraces
+some few miles in width (more or less, as the vein is presumed to
+expand or contract); but never exceeds the visual sweep from the
+whale-ship's mast-heads, when circumspectly gliding along this magic
+zone. The sum is, that at particular seasons within that breadth and
+along that path, migrating whales may with great confidence be looked
+for.
+
+And hence not only at substantiated times, upon well known separate
+feeding-grounds, could Ahab hope to encounter his prey; but in
+crossing the widest expanses of water between those grounds he could,
+by his art, so place and time himself on his way, as even then not to
+be wholly without prospect of a meeting.
+
+There was a circumstance which at first sight seemed to entangle his
+delirious but still methodical scheme. But not so in the reality,
+perhaps. Though the gregarious sperm whales have their regular
+seasons for particular grounds, yet in general you cannot conclude
+that the herds which haunted such and such a latitude or longitude
+this year, say, will turn out to be identically the same with those
+that were found there the preceding season; though there are peculiar
+and unquestionable instances where the contrary of this has proved
+true. In general, the same remark, only within a less wide limit,
+applies to the solitaries and hermits among the matured, aged sperm
+whales. So that though Moby Dick had in a former year been seen, for
+example, on what is called the Seychelle ground in the Indian ocean,
+or Volcano Bay on the Japanese Coast; yet it did not follow, that
+were the Pequod to visit either of those spots at any subsequent
+corresponding season, she would infallibly encounter him there. So,
+too, with some other feeding grounds, where he had at times revealed
+himself. But all these seemed only his casual stopping-places and
+ocean-inns, so to speak, not his places of prolonged abode. And
+where Ahab's chances of accomplishing his object have hitherto been
+spoken of, allusion has only been made to whatever way-side,
+antecedent, extra prospects were his, ere a particular set time or
+place were attained, when all possibilities would become
+probabilities, and, as Ahab fondly thought, every possibility the
+next thing to a certainty. That particular set time and place were
+conjoined in the one technical phrase--the Season-on-the-Line. For
+there and then, for several consecutive years, Moby Dick had been
+periodically descried, lingering in those waters for awhile, as the
+sun, in its annual round, loiters for a predicted interval in any one
+sign of the Zodiac. There it was, too, that most of the deadly
+encounters with the white whale had taken place; there the waves were
+storied with his deeds; there also was that tragic spot where the
+monomaniac old man had found the awful motive to his vengeance. But
+in the cautious comprehensiveness and unloitering vigilance with
+which Ahab threw his brooding soul into this unfaltering hunt, he
+would not permit himself to rest all his hopes upon the one crowning
+fact above mentioned, however flattering it might be to those hopes;
+nor in the sleeplessness of his vow could he so tranquillize his
+unquiet heart as to postpone all intervening quest.
+
+Now, the Pequod had sailed from Nantucket at the very beginning of
+the Season-on-the-Line. No possible endeavor then could enable her
+commander to make the great passage southwards, double Cape Horn, and
+then running down sixty degrees of latitude arrive in the equatorial
+Pacific in time to cruise there. Therefore, he must wait for the
+next ensuing season. Yet the premature hour of the Pequod's sailing
+had, perhaps, been correctly selected by Ahab, with a view to this
+very complexion of things. Because, an interval of three hundred and
+sixty-five days and nights was before him; an interval which, instead
+of impatiently enduring ashore, he would spend in a miscellaneous
+hunt; if by chance the White Whale, spending his vacation in seas far
+remote from his periodical feeding-grounds, should turn up his
+wrinkled brow off the Persian Gulf, or in the Bengal Bay, or China
+Seas, or in any other waters haunted by his race. So that Monsoons,
+Pampas, Nor'-Westers, Harmattans, Trades; any wind but the Levanter
+and Simoon, might blow Moby Dick into the devious zig-zag
+world-circle of the Pequod's circumnavigating wake.
+
+But granting all this; yet, regarded discreetly and coolly, seems it
+not but a mad idea, this; that in the broad boundless ocean, one
+solitary whale, even if encountered, should be thought capable of
+individual recognition from his hunter, even as a white-bearded Mufti
+in the thronged thoroughfares of Constantinople? Yes. For the
+peculiar snow-white brow of Moby Dick, and his snow-white hump, could
+not but be unmistakable. And have I not tallied the whale, Ahab
+would mutter to himself, as after poring over his charts till long
+after midnight he would throw himself back in reveries--tallied him,
+and shall he escape? His broad fins are bored, and scalloped out
+like a lost sheep's ear! And here, his mad mind would run on in a
+breathless race; till a weariness and faintness of pondering came
+over him; and in the open air of the deck he would seek to recover
+his strength. Ah, God! what trances of torments does that man endure
+who is consumed with one unachieved revengeful desire. He sleeps
+with clenched hands; and wakes with his own bloody nails in his
+palms.
+
+Often, when forced from his hammock by exhausting and intolerably
+vivid dreams of the night, which, resuming his own intense thoughts
+through the day, carried them on amid a clashing of phrensies, and
+whirled them round and round and round in his blazing brain, till
+the very throbbing of his life-spot became insufferable anguish; and
+when, as was sometimes the case, these spiritual throes in him heaved
+his being up from its base, and a chasm seemed opening in him, from
+which forked flames and lightnings shot up, and accursed fiends
+beckoned him to leap down among them; when this hell in himself
+yawned beneath him, a wild cry would be heard through the ship; and
+with glaring eyes Ahab would burst from his state room, as though
+escaping from a bed that was on fire. Yet these, perhaps, instead of
+being the unsuppressable symptoms of some latent weakness, or fright
+at his own resolve, were but the plainest tokens of its intensity.
+For, at such times, crazy Ahab, the scheming, unappeasedly steadfast
+hunter of the white whale; this Ahab that had gone to his hammock,
+was not the agent that so caused him to burst from it in horror
+again. The latter was the eternal, living principle or soul in him;
+and in sleep, being for the time dissociated from the characterizing
+mind, which at other times employed it for its outer vehicle or
+agent, it spontaneously sought escape from the scorching contiguity
+of the frantic thing, of which, for the time, it was no longer an
+integral. But as the mind does not exist unless leagued with the
+soul, therefore it must have been that, in Ahab's case, yielding up
+all his thoughts and fancies to his one supreme purpose; that
+purpose, by its own sheer inveteracy of will, forced itself against
+gods and devils into a kind of self-assumed, independent being of its
+own. Nay, could grimly live and burn, while the common vitality to
+which it was conjoined, fled horror-stricken from the unbidden and
+unfathered birth. Therefore, the tormented spirit that glared out of
+bodily eyes, when what seemed Ahab rushed from his room, was for the
+time but a vacated thing, a formless somnambulistic being, a ray of
+living light, to be sure, but without an object to colour, and
+therefore a blankness in itself. God help thee, old man, thy
+thoughts have created a creature in thee; and he whose intense
+thinking thus makes him a Prometheus; a vulture feeds upon that heart
+for ever; that vulture the very creature he creates.
+
+
+
+CHAPTER 45
+
+The Affidavit.
+
+
+So far as what there may be of a narrative in this book; and, indeed,
+as indirectly touching one or two very interesting and curious
+particulars in the habits of sperm whales, the foregoing chapter, in
+its earlier part, is as important a one as will be found in this
+volume; but the leading matter of it requires to be still further and
+more familiarly enlarged upon, in order to be adequately understood,
+and moreover to take away any incredulity which a profound ignorance
+of the entire subject may induce in some minds, as to the natural
+verity of the main points of this affair.
+
+I care not to perform this part of my task methodically; but shall be
+content to produce the desired impression by separate citations of
+items, practically or reliably known to me as a whaleman; and from
+these citations, I take it--the conclusion aimed at will naturally
+follow of itself.
+
+First: I have personally known three instances where a whale, after
+receiving a harpoon, has effected a complete escape; and, after an
+interval (in one instance of three years), has been again struck by
+the same hand, and slain; when the two irons, both marked by the same
+private cypher, have been taken from the body. In the instance where
+three years intervened between the flinging of the two harpoons; and
+I think it may have been something more than that; the man who darted
+them happening, in the interval, to go in a trading ship on a voyage
+to Africa, went ashore there, joined a discovery party, and
+penetrated far into the interior, where he travelled for a period of
+nearly two years, often endangered by serpents, savages, tigers,
+poisonous miasmas, with all the other common perils incident to
+wandering in the heart of unknown regions. Meanwhile, the whale he
+had struck must also have been on its travels; no doubt it had thrice
+circumnavigated the globe, brushing with its flanks all the coasts of
+Africa; but to no purpose. This man and this whale again came
+together, and the one vanquished the other. I say I, myself, have
+known three instances similar to this; that is in two of them I saw
+the whales struck; and, upon the second attack, saw the two irons
+with the respective marks cut in them, afterwards taken from the dead
+fish. In the three-year instance, it so fell out that I was in the
+boat both times, first and last, and the last time distinctly
+recognised a peculiar sort of huge mole under the whale's eye, which
+I had observed there three years previous. I say three years, but I
+am pretty sure it was more than that. Here are three instances,
+then, which I personally know the truth of; but I have heard of many
+other instances from persons whose veracity in the matter there is no
+good ground to impeach.
+
+Secondly: It is well known in the Sperm Whale Fishery, however
+ignorant the world ashore may be of it, that there have been several
+memorable historical instances where a particular whale in the ocean
+has been at distant times and places popularly cognisable. Why such
+a whale became thus marked was not altogether and originally owing to
+his bodily peculiarities as distinguished from other whales; for
+however peculiar in that respect any chance whale may be, they soon
+put an end to his peculiarities by killing him, and boiling him down
+into a peculiarly valuable oil. No: the reason was this: that from
+the fatal experiences of the fishery there hung a terrible prestige
+of perilousness about such a whale as there did about Rinaldo
+Rinaldini, insomuch that most fishermen were content to recognise him
+by merely touching their tarpaulins when he would be discovered
+lounging by them on the sea, without seeking to cultivate a more
+intimate acquaintance. Like some poor devils ashore that happen to
+know an irascible great man, they make distant unobtrusive
+salutations to him in the street, lest if they pursued the
+acquaintance further, they might receive a summary thump for their
+presumption.
+
+But not only did each of these famous whales enjoy great individual
+celebrity--Nay, you may call it an ocean-wide renown; not only was he
+famous in life and now is immortal in forecastle stories after death,
+but he was admitted into all the rights, privileges, and distinctions
+of a name; had as much a name indeed as Cambyses or Caesar. Was it
+not so, O Timor Tom! thou famed leviathan, scarred like an iceberg,
+who so long did'st lurk in the Oriental straits of that name, whose
+spout was oft seen from the palmy beach of Ombay? Was it not so, O
+New Zealand Jack! thou terror of all cruisers that crossed their
+wakes in the vicinity of the Tattoo Land? Was it not so, O Morquan!
+King of Japan, whose lofty jet they say at times assumed the
+semblance of a snow-white cross against the sky? Was it not so, O
+Don Miguel! thou Chilian whale, marked like an old tortoise with
+mystic hieroglyphics upon the back! In plain prose, here are four
+whales as well known to the students of Cetacean History as Marius or
+Sylla to the classic scholar.
+
+But this is not all. New Zealand Tom and Don Miguel, after at
+various times creating great havoc among the boats of different
+vessels, were finally gone in quest of, systematically hunted out,
+chased and killed by valiant whaling captains, who heaved up their
+anchors with that express object as much in view, as in setting out
+through the Narragansett Woods, Captain Butler of old had it in his
+mind to capture that notorious murderous savage Annawon, the headmost
+warrior of the Indian King Philip.
+
+I do not know where I can find a better place than just here, to make
+mention of one or two other things, which to me seem important, as in
+printed form establishing in all respects the reasonableness of the
+whole story of the White Whale, more especially the catastrophe. For
+this is one of those disheartening instances where truth requires
+full as much bolstering as error. So ignorant are most landsmen of
+some of the plainest and most palpable wonders of the world, that
+without some hints touching the plain facts, historical and
+otherwise, of the fishery, they might scout at Moby Dick as a
+monstrous fable, or still worse and more detestable, a hideous and
+intolerable allegory.
+
+First: Though most men have some vague flitting ideas of the general
+perils of the grand fishery, yet they have nothing like a fixed,
+vivid conception of those perils, and the frequency with which they
+recur. One reason perhaps is, that not one in fifty of the actual
+disasters and deaths by casualties in the fishery, ever finds a
+public record at home, however transient and immediately forgotten
+that record. Do you suppose that that poor fellow there, who this
+moment perhaps caught by the whale-line off the coast of New Guinea,
+is being carried down to the bottom of the sea by the sounding
+leviathan--do you suppose that that poor fellow's name will appear in
+the newspaper obituary you will read to-morrow at your breakfast?
+No: because the mails are very irregular between here and New Guinea.
+In fact, did you ever hear what might be called regular news direct
+or indirect from New Guinea? Yet I tell you that upon one particular
+voyage which I made to the Pacific, among many others we spoke thirty
+different ships, every one of which had had a death by a whale, some
+of them more than one, and three that had each lost a boat's crew.
+For God's sake, be economical with your lamps and candles! not a
+gallon you burn, but at least one drop of man's blood was spilled for
+it.
+
+Secondly: People ashore have indeed some indefinite idea that a whale
+is an enormous creature of enormous power; but I have ever found that
+when narrating to them some specific example of this two-fold
+enormousness, they have significantly complimented me upon my
+facetiousness; when, I declare upon my soul, I had no more idea of
+being facetious than Moses, when he wrote the history of the plagues
+of Egypt.
+
+But fortunately the special point I here seek can be established upon
+testimony entirely independent of my own. That point is this: The
+Sperm Whale is in some cases sufficiently powerful, knowing, and
+judiciously malicious, as with direct aforethought to stave in,
+utterly destroy, and sink a large ship; and what is more, the Sperm
+Whale HAS done it.
+
+First: In the year 1820 the ship Essex, Captain Pollard, of
+Nantucket, was cruising in the Pacific Ocean. One day she saw
+spouts, lowered her boats, and gave chase to a shoal of sperm whales.
+Ere long, several of the whales were wounded; when, suddenly, a very
+large whale escaping from the boats, issued from the shoal, and bore
+directly down upon the ship. Dashing his forehead against her hull,
+he so stove her in, that in less than "ten minutes" she settled down
+and fell over. Not a surviving plank of her has been seen since.
+After the severest exposure, part of the crew reached the land in
+their boats. Being returned home at last, Captain Pollard once more
+sailed for the Pacific in command of another ship, but the gods
+shipwrecked him again upon unknown rocks and breakers; for the second
+time his ship was utterly lost, and forthwith forswearing the sea, he
+has never tempted it since. At this day Captain Pollard is a
+resident of Nantucket. I have seen Owen Chace, who was chief mate of
+the Essex at the time of the tragedy; I have read his plain and
+faithful narrative; I have conversed with his son; and all this
+within a few miles of the scene of the catastrophe.*
+
+
+*The following are extracts from Chace's narrative: "Every fact
+seemed to warrant me in concluding that it was anything but chance
+which directed his operations; he made two several attacks upon the
+ship, at a short interval between them, both of which, according to
+their direction, were calculated to do us the most injury, by being
+made ahead, and thereby combining the speed of the two objects for
+the shock; to effect which, the exact manoeuvres which he made were
+necessary. His aspect was most horrible, and such as indicated
+resentment and fury. He came directly from the shoal which we had
+just before entered, and in which we had struck three of his
+companions, as if fired with revenge for their sufferings." Again:
+"At all events, the whole circumstances taken together, all happening
+before my own eyes, and producing, at the time, impressions in my
+mind of decided, calculating mischief, on the part of the whale (many
+of which impressions I cannot now recall), induce me to be satisfied
+that I am correct in my opinion."
+
+Here are his reflections some time after quitting the ship, during a
+black night an open boat, when almost despairing of reaching any
+hospitable shore. "The dark ocean and swelling waters were nothing;
+the fears of being swallowed up by some dreadful tempest, or dashed
+upon hidden rocks, with all the other ordinary subjects of fearful
+contemplation, seemed scarcely entitled to a moment's thought; the
+dismal looking wreck, and THE HORRID ASPECT AND REVENGE OF THE WHALE,
+wholly engrossed my reflections, until day again made its
+appearance."
+
+In another place--p. 45,--he speaks of "THE MYSTERIOUS AND MORTAL
+ATTACK OF THE ANIMAL."
+
+
+Secondly: The ship Union, also of Nantucket, was in the year 1807
+totally lost off the Azores by a similar onset, but the authentic
+particulars of this catastrophe I have never chanced to encounter,
+though from the whale hunters I have now and then heard casual
+allusions to it.
+
+Thirdly: Some eighteen or twenty years ago Commodore J---, then
+commanding an American sloop-of-war of the first class, happened to
+be dining with a party of whaling captains, on board a Nantucket ship
+in the harbor of Oahu, Sandwich Islands. Conversation turning upon
+whales, the Commodore was pleased to be sceptical touching the
+amazing strength ascribed to them by the professional gentlemen
+present. He peremptorily denied for example, that any whale could so
+smite his stout sloop-of-war as to cause her to leak so much as a
+thimbleful. Very good; but there is more coming. Some weeks after,
+the Commodore set sail in this impregnable craft for Valparaiso. But
+he was stopped on the way by a portly sperm whale, that begged a few
+moments' confidential business with him. That business consisted in
+fetching the Commodore's craft such a thwack, that with all his pumps
+going he made straight for the nearest port to heave down and repair.
+I am not superstitious, but I consider the Commodore's interview
+with that whale as providential. Was not Saul of Tarsus converted
+from unbelief by a similar fright? I tell you, the sperm whale will
+stand no nonsense.
+
+I will now refer you to Langsdorff's Voyages for a little
+circumstance in point, peculiarly interesting to the writer hereof.
+Langsdorff, you must know by the way, was attached to the Russian
+Admiral Krusenstern's famous Discovery Expedition in the beginning of
+the present century. Captain Langsdorff thus begins his seventeenth
+chapter:
+
+"By the thirteenth of May our ship was ready to sail, and the next
+day we were out in the open sea, on our way to Ochotsh. The weather
+was very clear and fine, but so intolerably cold that we were obliged
+to keep on our fur clothing. For some days we had very little wind;
+it was not till the nineteenth that a brisk gale from the northwest
+sprang up. An uncommon large whale, the body of which was larger
+than the ship itself, lay almost at the surface of the water, but was
+not perceived by any one on board till the moment when the ship,
+which was in full sail, was almost upon him, so that it was
+impossible to prevent its striking against him. We were thus placed
+in the most imminent danger, as this gigantic creature, setting up
+its back, raised the ship three feet at least out of the water. The
+masts reeled, and the sails fell altogether, while we who were below
+all sprang instantly upon the deck, concluding that we had struck
+upon some rock; instead of this we saw the monster sailing off with
+the utmost gravity and solemnity. Captain D'Wolf applied immediately
+to the pumps to examine whether or not the vessel had received any
+damage from the shock, but we found that very happily it had escaped
+entirely uninjured."
+
+Now, the Captain D'Wolf here alluded to as commanding the ship in
+question, is a New Englander, who, after a long life of unusual
+adventures as a sea-captain, this day resides in the village of
+Dorchester near Boston. I have the honour of being a nephew of his.
+I have particularly questioned him concerning this passage in
+Langsdorff. He substantiates every word. The ship, however, was by
+no means a large one: a Russian craft built on the Siberian coast,
+and purchased by my uncle after bartering away the vessel in which he
+sailed from home.
+
+In that up and down manly book of old-fashioned adventure, so full,
+too, of honest wonders--the voyage of Lionel Wafer, one of ancient
+Dampier's old chums--I found a little matter set down so like that
+just quoted from Langsdorff, that I cannot forbear inserting it here
+for a corroborative example, if such be needed.
+
+Lionel, it seems, was on his way to "John Ferdinando," as he calls
+the modern Juan Fernandes. "In our way thither," he says, "about
+four o'clock in the morning, when we were about one hundred and fifty
+leagues from the Main of America, our ship felt a terrible shock,
+which put our men in such consternation that they could hardly tell
+where they were or what to think; but every one began to prepare for
+death. And, indeed, the shock was so sudden and violent, that we
+took it for granted the ship had struck against a rock; but when the
+amazement was a little over, we cast the lead, and sounded, but found
+no ground. .... The suddenness of the shock made the guns leap in
+their carriages, and several of the men were shaken out of their
+hammocks. Captain Davis, who lay with his head on a gun, was thrown
+out of his cabin!" Lionel then goes on to impute the shock to an
+earthquake, and seems to substantiate the imputation by stating that
+a great earthquake, somewhere about that time, did actually do great
+mischief along the Spanish land. But I should not much wonder if, in
+the darkness of that early hour of the morning, the shock was after
+all caused by an unseen whale vertically bumping the hull from
+beneath.
+
+I might proceed with several more examples, one way or another known
+to me, of the great power and malice at times of the sperm whale. In
+more than one instance, he has been known, not only to chase the
+assailing boats back to their ships, but to pursue the ship itself,
+and long withstand all the lances hurled at him from its decks. The
+English ship Pusie Hall can tell a story on that head; and, as for
+his strength, let me say, that there have been examples where the
+lines attached to a running sperm whale have, in a calm, been
+transferred to the ship, and secured there; the whale towing her
+great hull through the water, as a horse walks off with a cart.
+Again, it is very often observed that, if the sperm whale, once
+struck, is allowed time to rally, he then acts, not so often with
+blind rage, as with wilful, deliberate designs of destruction to his
+pursuers; nor is it without conveying some eloquent indication of his
+character, that upon being attacked he will frequently open his
+mouth, and retain it in that dread expansion for several consecutive
+minutes. But I must be content with only one more and a concluding
+illustration; a remarkable and most significant one, by which you
+will not fail to see, that not only is the most marvellous event in
+this book corroborated by plain facts of the present day, but that
+these marvels (like all marvels) are mere repetitions of the ages; so
+that for the millionth time we say amen with Solomon--Verily there is
+nothing new under the sun.
+
+In the sixth Christian century lived Procopius, a Christian
+magistrate of Constantinople, in the days when Justinian was Emperor
+and Belisarius general. As many know, he wrote the history of his
+own times, a work every way of uncommon value. By the best
+authorities, he has always been considered a most trustworthy and
+unexaggerating historian, except in some one or two particulars, not
+at all affecting the matter presently to be mentioned.
+
+Now, in this history of his, Procopius mentions that, during the term
+of his prefecture at Constantinople, a great sea-monster was captured
+in the neighboring Propontis, or Sea of Marmora, after having
+destroyed vessels at intervals in those waters for a period of more
+than fifty years. A fact thus set down in substantial history cannot
+easily be gainsaid. Nor is there any reason it should be. Of what
+precise species this sea-monster was, is not mentioned. But as he
+destroyed ships, as well as for other reasons, he must have been a
+whale; and I am strongly inclined to think a sperm whale. And I will
+tell you why. For a long time I fancied that the sperm whale had
+been always unknown in the Mediterranean and the deep waters
+connecting with it. Even now I am certain that those seas are not,
+and perhaps never can be, in the present constitution of things, a
+place for his habitual gregarious resort. But further investigations
+have recently proved to me, that in modern times there have been
+isolated instances of the presence of the sperm whale in the
+Mediterranean. I am told, on good authority, that on the Barbary
+coast, a Commodore Davis of the British navy found the skeleton of a
+sperm whale. Now, as a vessel of war readily passes through the
+Dardanelles, hence a sperm whale could, by the same route, pass out
+of the Mediterranean into the Propontis.
+
+In the Propontis, as far as I can learn, none of that peculiar
+substance called BRIT is to be found, the aliment of the right whale.
+But I have every reason to believe that the food of the sperm
+whale--squid or cuttle-fish--lurks at the bottom of that sea, because
+large creatures, but by no means the largest of that sort, have been
+found at its surface. If, then, you properly put these statements
+together, and reason upon them a bit, you will clearly perceive that,
+according to all human reasoning, Procopius's sea-monster, that for
+half a century stove the ships of a Roman Emperor, must in all
+probability have been a sperm whale.
+
+
+
+CHAPTER 46
+
+Surmises.
+
+
+Though, consumed with the hot fire of his purpose, Ahab in all his
+thoughts and actions ever had in view the ultimate capture of Moby
+Dick; though he seemed ready to sacrifice all mortal interests to
+that one passion; nevertheless it may have been that he was by nature
+and long habituation far too wedded to a fiery whaleman's ways,
+altogether to abandon the collateral prosecution of the voyage. Or
+at least if this were otherwise, there were not wanting other motives
+much more influential with him. It would be refining too much,
+perhaps, even considering his monomania, to hint that his
+vindictiveness towards the White Whale might have possibly extended
+itself in some degree to all sperm whales, and that the more monsters
+he slew by so much the more he multiplied the chances that each
+subsequently encountered whale would prove to be the hated one he
+hunted. But if such an hypothesis be indeed exceptionable, there
+were still additional considerations which, though not so strictly
+according with the wildness of his ruling passion, yet were by no
+means incapable of swaying him.
+
+To accomplish his object Ahab must use tools; and of all tools used
+in the shadow of the moon, men are most apt to get out of order. He
+knew, for example, that however magnetic his ascendency in some
+respects was over Starbuck, yet that ascendency did not cover the
+complete spiritual man any more than mere corporeal superiority
+involves intellectual mastership; for to the purely spiritual, the
+intellectual but stand in a sort of corporeal relation. Starbuck's
+body and Starbuck's coerced will were Ahab's, so long as Ahab kept
+his magnet at Starbuck's brain; still he knew that for all this the
+chief mate, in his soul, abhorred his captain's quest, and could he,
+would joyfully disintegrate himself from it, or even frustrate it.
+It might be that a long interval would elapse ere the White Whale was
+seen. During that long interval Starbuck would ever be apt to fall
+into open relapses of rebellion against his captain's leadership,
+unless some ordinary, prudential, circumstantial influences were
+brought to bear upon him. Not only that, but the subtle insanity of
+Ahab respecting Moby Dick was noways more significantly manifested
+than in his superlative sense and shrewdness in foreseeing that, for
+the present, the hunt should in some way be stripped of that strange
+imaginative impiousness which naturally invested it; that the full
+terror of the voyage must be kept withdrawn into the obscure
+background (for few men's courage is proof against protracted
+meditation unrelieved by action); that when they stood their long
+night watches, his officers and men must have some nearer things to
+think of than Moby Dick. For however eagerly and impetuously the
+savage crew had hailed the announcement of his quest; yet all sailors
+of all sorts are more or less capricious and unreliable--they live in
+the varying outer weather, and they inhale its fickleness--and when
+retained for any object remote and blank in the pursuit, however
+promissory of life and passion in the end, it is above all things
+requisite that temporary interests and employments should intervene
+and hold them healthily suspended for the final dash.
+
+Nor was Ahab unmindful of another thing. In times of strong emotion
+mankind disdain all base considerations; but such times are
+evanescent. The permanent constitutional condition of the
+manufactured man, thought Ahab, is sordidness. Granting that the
+White Whale fully incites the hearts of this my savage crew, and
+playing round their savageness even breeds a certain generous
+knight-errantism in them, still, while for the love of it they give
+chase to Moby Dick, they must also have food for their more common,
+daily appetites. For even the high lifted and chivalric Crusaders of
+old times were not content to traverse two thousand miles of land to
+fight for their holy sepulchre, without committing burglaries,
+picking pockets, and gaining other pious perquisites by the way. Had
+they been strictly held to their one final and romantic object--that
+final and romantic object, too many would have turned from in
+disgust. I will not strip these men, thought Ahab, of all hopes of
+cash--aye, cash. They may scorn cash now; but let some months go by,
+and no perspective promise of it to them, and then this same
+quiescent cash all at once mutinying in them, this same cash would
+soon cashier Ahab.
+
+Nor was there wanting still another precautionary motive more related
+to Ahab personally. Having impulsively, it is probable, and perhaps
+somewhat prematurely revealed the prime but private purpose of the
+Pequod's voyage, Ahab was now entirely conscious that, in so doing,
+he had indirectly laid himself open to the unanswerable charge of
+usurpation; and with perfect impunity, both moral and legal, his crew
+if so disposed, and to that end competent, could refuse all further
+obedience to him, and even violently wrest from him the command.
+From even the barely hinted imputation of usurpation, and the
+possible consequences of such a suppressed impression gaining ground,
+Ahab must of course have been most anxious to protect himself. That
+protection could only consist in his own predominating brain and
+heart and hand, backed by a heedful, closely calculating attention to
+every minute atmospheric influence which it was possible for his crew
+to be subjected to.
+
+For all these reasons then, and others perhaps too analytic to be
+verbally developed here, Ahab plainly saw that he must still in a
+good degree continue true to the natural, nominal purpose of the
+Pequod's voyage; observe all customary usages; and not only that, but
+force himself to evince all his well known passionate interest in the
+general pursuit of his profession.
+
+Be all this as it may, his voice was now often heard hailing the
+three mast-heads and admonishing them to keep a bright look-out, and
+not omit reporting even a porpoise. This vigilance was not long
+without reward.
+
+
+
+CHAPTER 47
+
+The Mat-Maker.
+
+
+It was a cloudy, sultry afternoon; the seamen were lazily lounging
+about the decks, or vacantly gazing over into the lead-coloured
+waters. Queequeg and I were mildly employed weaving what is called a
+sword-mat, for an additional lashing to our boat. So still and
+subdued and yet somehow preluding was all the scene, and such an
+incantation of reverie lurked in the air, that each silent sailor
+seemed resolved into his own invisible self.
+
+I was the attendant or page of Queequeg, while busy at the mat. As I
+kept passing and repassing the filling or woof of marline between the
+long yarns of the warp, using my own hand for the shuttle, and as
+Queequeg, standing sideways, ever and anon slid his heavy oaken sword
+between the threads, and idly looking off upon the water, carelessly
+and unthinkingly drove home every yarn: I say so strange a
+dreaminess did there then reign all over the ship and all over the
+sea, only broken by the intermitting dull sound of the sword, that it
+seemed as if this were the Loom of Time, and I myself were a shuttle
+mechanically weaving and weaving away at the Fates. There lay the
+fixed threads of the warp subject to but one single, ever returning,
+unchanging vibration, and that vibration merely enough to admit of
+the crosswise interblending of other threads with its own. This warp
+seemed necessity; and here, thought I, with my own hand I ply my own
+shuttle and weave my own destiny into these unalterable threads.
+Meantime, Queequeg's impulsive, indifferent sword, sometimes hitting
+the woof slantingly, or crookedly, or strongly, or weakly, as the
+case might be; and by this difference in the concluding blow
+producing a corresponding contrast in the final aspect of the
+completed fabric; this savage's sword, thought I, which thus finally
+shapes and fashions both warp and woof; this easy, indifferent sword
+must be chance--aye, chance, free will, and necessity--nowise
+incompatible--all interweavingly working together. The straight warp
+of necessity, not to be swerved from its ultimate course--its every
+alternating vibration, indeed, only tending to that; free will still
+free to ply her shuttle between given threads; and chance, though
+restrained in its play within the right lines of necessity, and
+sideways in its motions directed by free will, though thus prescribed
+to by both, chance by turns rules either, and has the last featuring
+blow at events.
+
+
+Thus we were weaving and weaving away when I started at a sound so
+strange, long drawn, and musically wild and unearthly, that the ball
+of free will dropped from my hand, and I stood gazing up at the
+clouds whence that voice dropped like a wing. High aloft in the
+cross-trees was that mad Gay-Header, Tashtego. His body was reaching
+eagerly forward, his hand stretched out like a wand, and at brief
+sudden intervals he continued his cries. To be sure the same sound
+was that very moment perhaps being heard all over the seas, from
+hundreds of whalemen's look-outs perched as high in the air; but from
+few of those lungs could that accustomed old cry have derived such a
+marvellous cadence as from Tashtego the Indian's.
+
+As he stood hovering over you half suspended in air, so wildly and
+eagerly peering towards the horizon, you would have thought him some
+prophet or seer beholding the shadows of Fate, and by those wild
+cries announcing their coming.
+
+"There she blows! there! there! there! she blows! she blows!"
+
+"Where-away?"
+
+"On the lee-beam, about two miles off! a school of them!"
+
+Instantly all was commotion.
+
+The Sperm Whale blows as a clock ticks, with the same undeviating and
+reliable uniformity. And thereby whalemen distinguish this fish from
+other tribes of his genus.
+
+"There go flukes!" was now the cry from Tashtego; and the whales
+disappeared.
+
+"Quick, steward!" cried Ahab. "Time! time!"
+
+Dough-Boy hurried below, glanced at the watch, and reported the exact
+minute to Ahab.
+
+The ship was now kept away from the wind, and she went gently rolling
+before it. Tashtego reporting that the whales had gone down heading
+to leeward, we confidently looked to see them again directly in
+advance of our bows. For that singular craft at times evinced by the
+Sperm Whale when, sounding with his head in one direction, he
+nevertheless, while concealed beneath the surface, mills round, and
+swiftly swims off in the opposite quarter--this deceitfulness of his
+could not now be in action; for there was no reason to suppose that
+the fish seen by Tashtego had been in any way alarmed, or indeed knew
+at all of our vicinity. One of the men selected for
+shipkeepers--that is, those not appointed to the boats, by this time
+relieved the Indian at the main-mast head. The sailors at the fore
+and mizzen had come down; the line tubs were fixed in their places;
+the cranes were thrust out; the mainyard was backed, and the three
+boats swung over the sea like three samphire baskets over high
+cliffs. Outside of the bulwarks their eager crews with one hand
+clung to the rail, while one foot was expectantly poised on the
+gunwale. So look the long line of man-of-war's men about to throw
+themselves on board an enemy's ship.
+
+But at this critical instant a sudden exclamation was heard that took
+every eye from the whale. With a start all glared at dark Ahab, who
+was surrounded by five dusky phantoms that seemed fresh formed out of
+air.
+
+
+
+CHAPTER 48
+
+The First Lowering.
+
+
+The phantoms, for so they then seemed, were flitting on the other
+side of the deck, and, with a noiseless celerity, were casting loose
+the tackles and bands of the boat which swung there. This boat had
+always been deemed one of the spare boats, though technically called
+the captain's, on account of its hanging from the starboard quarter.
+The figure that now stood by its bows was tall and swart, with one
+white tooth evilly protruding from its steel-like lips. A rumpled
+Chinese jacket of black cotton funereally invested him, with wide
+black trowsers of the same dark stuff. But strangely crowning this
+ebonness was a glistening white plaited turban, the living hair
+braided and coiled round and round upon his head. Less swart in
+aspect, the companions of this figure were of that vivid,
+tiger-yellow complexion peculiar to some of the aboriginal natives of
+the Manillas;--a race notorious for a certain diabolism of subtilty,
+and by some honest white mariners supposed to be the paid spies and
+secret confidential agents on the water of the devil, their lord,
+whose counting-room they suppose to be elsewhere.
+
+While yet the wondering ship's company were gazing upon these
+strangers, Ahab cried out to the white-turbaned old man at their
+head, "All ready there, Fedallah?"
+
+"Ready," was the half-hissed reply.
+
+"Lower away then; d'ye hear?" shouting across the deck. "Lower away
+there, I say."
+
+Such was the thunder of his voice, that spite of their amazement the
+men sprang over the rail; the sheaves whirled round in the blocks;
+with a wallow, the three boats dropped into the sea; while, with a
+dexterous, off-handed daring, unknown in any other vocation, the
+sailors, goat-like, leaped down the rolling ship's side into the
+tossed boats below.
+
+Hardly had they pulled out from under the ship's lee, when a fourth
+keel, coming from the windward side, pulled round under the stern,
+and showed the five strangers rowing Ahab, who, standing erect in the
+stern, loudly hailed Starbuck, Stubb, and Flask, to spread themselves
+widely, so as to cover a large expanse of water. But with all their
+eyes again riveted upon the swart Fedallah and his crew, the inmates
+of the other boats obeyed not the command.
+
+"Captain Ahab?--" said Starbuck.
+
+"Spread yourselves," cried Ahab; "give way, all four boats. Thou,
+Flask, pull out more to leeward!"
+
+"Aye, aye, sir," cheerily cried little King-Post, sweeping round his
+great steering oar. "Lay back!" addressing his crew.
+"There!--there!--there again! There she blows right ahead,
+boys!--lay back!"
+
+"Never heed yonder yellow boys, Archy."
+
+"Oh, I don't mind'em, sir," said Archy; "I knew it all before now.
+Didn't I hear 'em in the hold? And didn't I tell Cabaco here of it?
+What say ye, Cabaco? They are stowaways, Mr. Flask."
+
+"Pull, pull, my fine hearts-alive; pull, my children; pull, my little
+ones," drawlingly and soothingly sighed Stubb to his crew, some of
+whom still showed signs of uneasiness. "Why don't you break your
+backbones, my boys? What is it you stare at? Those chaps in yonder
+boat? Tut! They are only five more hands come to help us--never
+mind from where--the more the merrier. Pull, then, do pull; never
+mind the brimstone--devils are good fellows enough. So, so; there
+you are now; that's the stroke for a thousand pounds; that's the
+stroke to sweep the stakes! Hurrah for the gold cup of sperm oil, my
+heroes! Three cheers, men--all hearts alive! Easy, easy; don't be
+in a hurry--don't be in a hurry. Why don't you snap your oars, you
+rascals? Bite something, you dogs! So, so, so, then:--softly,
+softly! That's it--that's it! long and strong. Give way there, give
+way! The devil fetch ye, ye ragamuffin rapscallions; ye are all
+asleep. Stop snoring, ye sleepers, and pull. Pull, will ye? pull,
+can't ye? pull, won't ye? Why in the name of gudgeons and
+ginger-cakes don't ye pull?--pull and break something! pull, and
+start your eyes out! Here!" whipping out the sharp knife from his
+girdle; "every mother's son of ye draw his knife, and pull with the
+blade between his teeth. That's it--that's it. Now ye do something;
+that looks like it, my steel-bits. Start her--start her, my
+silver-spoons! Start her, marling-spikes!"
+
+Stubb's exordium to his crew is given here at large, because he had
+rather a peculiar way of talking to them in general, and especially
+in inculcating the religion of rowing. But you must not suppose from
+this specimen of his sermonizings that he ever flew into downright
+passions with his congregation. Not at all; and therein consisted
+his chief peculiarity. He would say the most terrific things to his
+crew, in a tone so strangely compounded of fun and fury, and the fury
+seemed so calculated merely as a spice to the fun, that no oarsman
+could hear such queer invocations without pulling for dear life, and
+yet pulling for the mere joke of the thing. Besides he all the time
+looked so easy and indolent himself, so loungingly managed his
+steering-oar, and so broadly gaped--open-mouthed at times--that the
+mere sight of such a yawning commander, by sheer force of contrast,
+acted like a charm upon the crew. Then again, Stubb was one of those
+odd sort of humorists, whose jollity is sometimes so curiously
+ambiguous, as to put all inferiors on their guard in the matter of
+obeying them.
+
+In obedience to a sign from Ahab, Starbuck was now pulling obliquely
+across Stubb's bow; and when for a minute or so the two boats were
+pretty near to each other, Stubb hailed the mate.
+
+"Mr. Starbuck! larboard boat there, ahoy! a word with ye, sir, if ye
+please!"
+
+"Halloa!" returned Starbuck, turning round not a single inch as he
+spoke; still earnestly but whisperingly urging his crew; his face set
+like a flint from Stubb's.
+
+"What think ye of those yellow boys, sir!
+
+"Smuggled on board, somehow, before the ship sailed. (Strong, strong,
+boys!)" in a whisper to his crew, then speaking out loud again: "A
+sad business, Mr. Stubb! (seethe her, seethe her, my lads!) but never
+mind, Mr. Stubb, all for the best. Let all your crew pull strong,
+come what will. (Spring, my men, spring!) There's hogsheads of sperm
+ahead, Mr. Stubb, and that's what ye came for. (Pull, my boys!)
+Sperm, sperm's the play! This at least is duty; duty and profit hand
+in hand."
+
+"Aye, aye, I thought as much," soliloquized Stubb, when the boats
+diverged, "as soon as I clapt eye on 'em, I thought so. Aye, and
+that's what he went into the after hold for, so often, as Dough-Boy
+long suspected. They were hidden down there. The White Whale's at
+the bottom of it. Well, well, so be it! Can't be helped! All
+right! Give way, men! It ain't the White Whale to-day! Give way!"
+
+Now the advent of these outlandish strangers at such a critical
+instant as the lowering of the boats from the deck, this had not
+unreasonably awakened a sort of superstitious amazement in some of
+the ship's company; but Archy's fancied discovery having some time
+previous got abroad among them, though indeed not credited then, this
+had in some small measure prepared them for the event. It took off
+the extreme edge of their wonder; and so what with all this and
+Stubb's confident way of accounting for their appearance, they were
+for the time freed from superstitious surmisings; though the affair
+still left abundant room for all manner of wild conjectures as to
+dark Ahab's precise agency in the matter from the beginning. For me,
+I silently recalled the mysterious shadows I had seen creeping on
+board the Pequod during the dim Nantucket dawn, as well as the
+enigmatical hintings of the unaccountable Elijah.
+
+Meantime, Ahab, out of hearing of his officers, having sided the
+furthest to windward, was still ranging ahead of the other boats; a
+circumstance bespeaking how potent a crew was pulling him. Those
+tiger yellow creatures of his seemed all steel and whalebone; like
+five trip-hammers they rose and fell with regular strokes of
+strength, which periodically started the boat along the water like a
+horizontal burst boiler out of a Mississippi steamer. As for
+Fedallah, who was seen pulling the harpooneer oar, he had thrown
+aside his black jacket, and displayed his naked chest with the whole
+part of his body above the gunwale, clearly cut against the
+alternating depressions of the watery horizon; while at the other end
+of the boat Ahab, with one arm, like a fencer's, thrown half backward
+into the air, as if to counterbalance any tendency to trip; Ahab was
+seen steadily managing his steering oar as in a thousand boat
+lowerings ere the White Whale had torn him. All at once the
+outstretched arm gave a peculiar motion and then remained fixed,
+while the boat's five oars were seen simultaneously peaked. Boat and
+crew sat motionless on the sea. Instantly the three spread boats in
+the rear paused on their way. The whales had irregularly settled
+bodily down into the blue, thus giving no distantly discernible token
+of the movement, though from his closer vicinity Ahab had observed
+it.
+
+"Every man look out along his oars!" cried Starbuck. "Thou,
+Queequeg, stand up!"
+
+Nimbly springing up on the triangular raised box in the bow, the
+savage stood erect there, and with intensely eager eyes gazed off
+towards the spot where the chase had last been descried. Likewise
+upon the extreme stern of the boat where it was also triangularly
+platformed level with the gunwale, Starbuck himself was seen coolly
+and adroitly balancing himself to the jerking tossings of his chip of
+a craft, and silently eyeing the vast blue eye of the sea.
+
+Not very far distant Flask's boat was also lying breathlessly still;
+its commander recklessly standing upon the top of the loggerhead, a
+stout sort of post rooted in the keel, and rising some two feet above
+the level of the stern platform. It is used for catching turns with
+the whale line. Its top is not more spacious than the palm of a
+man's hand, and standing upon such a base as that, Flask seemed
+perched at the mast-head of some ship which had sunk to all but her
+trucks. But little King-Post was small and short, and at the same
+time little King-Post was full of a large and tall ambition, so that
+this loggerhead stand-point of his did by no means satisfy King-Post.
+
+"I can't see three seas off; tip us up an oar there, and let me on to
+that."
+
+Upon this, Daggoo, with either hand upon the gunwale to steady his
+way, swiftly slid aft, and then erecting himself volunteered his
+lofty shoulders for a pedestal.
+
+"Good a mast-head as any, sir. Will you mount?"
+
+"That I will, and thank ye very much, my fine fellow; only I wish you
+fifty feet taller."
+
+Whereupon planting his feet firmly against two opposite planks of the
+boat, the gigantic negro, stooping a little, presented his flat palm
+to Flask's foot, and then putting Flask's hand on his hearse-plumed
+head and bidding him spring as he himself should toss, with one
+dexterous fling landed the little man high and dry on his shoulders.
+And here was Flask now standing, Daggoo with one lifted arm
+furnishing him with a breastband to lean against and steady himself
+by.
+
+At any time it is a strange sight to the tyro to see with what
+wondrous habitude of unconscious skill the whaleman will maintain an
+erect posture in his boat, even when pitched about by the most
+riotously perverse and cross-running seas. Still more strange to see
+him giddily perched upon the loggerhead itself, under such
+circumstances. But the sight of little Flask mounted upon gigantic
+Daggoo was yet more curious; for sustaining himself with a cool,
+indifferent, easy, unthought of, barbaric majesty, the noble negro to
+every roll of the sea harmoniously rolled his fine form. On his
+broad back, flaxen-haired Flask seemed a snow-flake. The bearer
+looked nobler than the rider. Though truly vivacious, tumultuous,
+ostentatious little Flask would now and then stamp with impatience;
+but not one added heave did he thereby give to the negro's lordly
+chest. So have I seen Passion and Vanity stamping the living
+magnanimous earth, but the earth did not alter her tides and her
+seasons for that.
+
+Meanwhile Stubb, the third mate, betrayed no such far-gazing
+solicitudes. The whales might have made one of their regular
+soundings, not a temporary dive from mere fright; and if that were
+the case, Stubb, as his wont in such cases, it seems, was resolved to
+solace the languishing interval with his pipe. He withdrew it from
+his hatband, where he always wore it aslant like a feather. He
+loaded it, and rammed home the loading with his thumb-end; but hardly
+had he ignited his match across the rough sandpaper of his hand,
+when Tashtego, his harpooneer, whose eyes had been setting to
+windward like two fixed stars, suddenly dropped like light from his
+erect attitude to his seat, crying out in a quick phrensy of hurry,
+"Down, down all, and give way!--there they are!"
+
+To a landsman, no whale, nor any sign of a herring, would have been
+visible at that moment; nothing but a troubled bit of greenish white
+water, and thin scattered puffs of vapour hovering over it, and
+suffusingly blowing off to leeward, like the confused scud from white
+rolling billows. The air around suddenly vibrated and tingled, as it
+were, like the air over intensely heated plates of iron. Beneath
+this atmospheric waving and curling, and partially beneath a thin
+layer of water, also, the whales were swimming. Seen in advance of
+all the other indications, the puffs of vapour they spouted, seemed
+their forerunning couriers and detached flying outriders.
+
+All four boats were now in keen pursuit of that one spot of troubled
+water and air. But it bade fair to outstrip them; it flew on and on,
+as a mass of interblending bubbles borne down a rapid stream from the
+hills.
+
+"Pull, pull, my good boys," said Starbuck, in the lowest possible but
+intensest concentrated whisper to his men; while the sharp fixed
+glance from his eyes darted straight ahead of the bow, almost seemed
+as two visible needles in two unerring binnacle compasses. He did
+not say much to his crew, though, nor did his crew say anything to
+him. Only the silence of the boat was at intervals startlingly
+pierced by one of his peculiar whispers, now harsh with command, now
+soft with entreaty.
+
+How different the loud little King-Post. "Sing out and say
+something, my hearties. Roar and pull, my thunderbolts! Beach me,
+beach me on their black backs, boys; only do that for me, and I'll
+sign over to you my Martha's Vineyard plantation, boys; including
+wife and children, boys. Lay me on--lay me on! O Lord, Lord! but I
+shall go stark, staring mad! See! see that white water!" And so
+shouting, he pulled his hat from his head, and stamped up and down on
+it; then picking it up, flirted it far off upon the sea; and finally
+fell to rearing and plunging in the boat's stern like a crazed colt
+from the prairie.
+
+"Look at that chap now," philosophically drawled Stubb, who, with his
+unlighted short pipe, mechanically retained between his teeth, at a
+short distance, followed after--"He's got fits, that Flask has.
+Fits? yes, give him fits--that's the very word--pitch fits into 'em.
+Merrily, merrily, hearts-alive. Pudding for supper, you
+know;--merry's the word. Pull, babes--pull, sucklings--pull, all.
+But what the devil are you hurrying about? Softly, softly, and
+steadily, my men. Only pull, and keep pulling; nothing more. Crack
+all your backbones, and bite your knives in two--that's all. Take it
+easy--why don't ye take it easy, I say, and burst all your livers and
+lungs!"
+
+But what it was that inscrutable Ahab said to that tiger-yellow crew
+of his--these were words best omitted here; for you live under the
+blessed light of the evangelical land. Only the infidel sharks in
+the audacious seas may give ear to such words, when, with tornado
+brow, and eyes of red murder, and foam-glued lips, Ahab leaped after
+his prey.
+
+Meanwhile, all the boats tore on. The repeated specific allusions of
+Flask to "that whale," as he called the fictitious monster which he
+declared to be incessantly tantalizing his boat's bow with its
+tail--these allusions of his were at times so vivid and life-like,
+that they would cause some one or two of his men to snatch a fearful
+look over the shoulder. But this was against all rule; for the
+oarsmen must put out their eyes, and ram a skewer through their
+necks; usage pronouncing that they must have no organs but ears, and
+no limbs but arms, in these critical moments.
+
+It was a sight full of quick wonder and awe! The vast swells of the
+omnipotent sea; the surging, hollow roar they made, as they rolled
+along the eight gunwales, like gigantic bowls in a boundless
+bowling-green; the brief suspended agony of the boat, as it would tip
+for an instant on the knife-like edge of the sharper waves, that
+almost seemed threatening to cut it in two; the sudden profound dip
+into the watery glens and hollows; the keen spurrings and goadings to
+gain the top of the opposite hill; the headlong, sled-like slide down
+its other side;--all these, with the cries of the headsmen and
+harpooneers, and the shuddering gasps of the oarsmen, with the
+wondrous sight of the ivory Pequod bearing down upon her boats with
+outstretched sails, like a wild hen after her screaming brood;--all
+this was thrilling.
+
+Not the raw recruit, marching from the bosom of his wife into the
+fever heat of his first battle; not the dead man's ghost encountering
+the first unknown phantom in the other world;--neither of these can
+feel stranger and stronger emotions than that man does, who for the
+first time finds himself pulling into the charmed, churned circle of
+the hunted sperm whale.
+
+The dancing white water made by the chase was now becoming more and
+more visible, owing to the increasing darkness of the dun
+cloud-shadows flung upon the sea. The jets of vapour no longer
+blended, but tilted everywhere to right and left; the whales seemed
+separating their wakes. The boats were pulled more apart; Starbuck
+giving chase to three whales running dead to leeward. Our sail was
+now set, and, with the still rising wind, we rushed along; the boat
+going with such madness through the water, that the lee oars could
+scarcely be worked rapidly enough to escape being torn from the
+row-locks.
+
+Soon we were running through a suffusing wide veil of mist; neither
+ship nor boat to be seen.
+
+"Give way, men," whispered Starbuck, drawing still further aft the
+sheet of his sail; "there is time to kill a fish yet before the
+squall comes. There's white water again!--close to! Spring!"
+
+Soon after, two cries in quick succession on each side of us denoted
+that the other boats had got fast; but hardly were they overheard,
+when with a lightning-like hurtling whisper Starbuck said: "Stand
+up!" and Queequeg, harpoon in hand, sprang to his feet.
+
+Though not one of the oarsmen was then facing the life and death
+peril so close to them ahead, yet with their eyes on the intense
+countenance of the mate in the stern of the boat, they knew that the
+imminent instant had come; they heard, too, an enormous wallowing
+sound as of fifty elephants stirring in their litter. Meanwhile the
+boat was still booming through the mist, the waves curling and
+hissing around us like the erected crests of enraged serpents.
+
+"That's his hump. THERE, THERE, give it to him!" whispered Starbuck.
+
+A short rushing sound leaped out of the boat; it was the darted iron
+of Queequeg. Then all in one welded commotion came an invisible push
+from astern, while forward the boat seemed striking on a ledge; the
+sail collapsed and exploded; a gush of scalding vapour shot up near
+by; something rolled and tumbled like an earthquake beneath us. The
+whole crew were half suffocated as they were tossed helter-skelter
+into the white curdling cream of the squall. Squall, whale, and
+harpoon had all blended together; and the whale, merely grazed by the
+iron, escaped.
+
+Though completely swamped, the boat was nearly unharmed. Swimming
+round it we picked up the floating oars, and lashing them across the
+gunwale, tumbled back to our places. There we sat up to our knees in
+the sea, the water covering every rib and plank, so that to our
+downward gazing eyes the suspended craft seemed a coral boat grown up
+to us from the bottom of the ocean.
+
+The wind increased to a howl; the waves dashed their bucklers
+together; the whole squall roared, forked, and crackled around us
+like a white fire upon the prairie, in which, unconsumed, we were
+burning; immortal in these jaws of death! In vain we hailed the
+other boats; as well roar to the live coals down the chimney of a
+flaming furnace as hail those boats in that storm. Meanwhile the
+driving scud, rack, and mist, grew darker with the shadows of night;
+no sign of the ship could be seen. The rising sea forbade all
+attempts to bale out the boat. The oars were useless as propellers,
+performing now the office of life-preservers. So, cutting the
+lashing of the waterproof match keg, after many failures Starbuck
+contrived to ignite the lamp in the lantern; then stretching it on a
+waif pole, handed it to Queequeg as the standard-bearer of this
+forlorn hope. There, then, he sat, holding up that imbecile candle
+in the heart of that almighty forlornness. There, then, he sat, the
+sign and symbol of a man without faith, hopelessly holding up hope in
+the midst of despair.
+
+Wet, drenched through, and shivering cold, despairing of ship or
+boat, we lifted up our eyes as the dawn came on. The mist still
+spread over the sea, the empty lantern lay crushed in the bottom of
+the boat. Suddenly Queequeg started to his feet, hollowing his hand
+to his ear. We all heard a faint creaking, as of ropes and yards
+hitherto muffled by the storm. The sound came nearer and nearer; the
+thick mists were dimly parted by a huge, vague form. Affrighted, we
+all sprang into the sea as the ship at last loomed into view, bearing
+right down upon us within a distance of not much more than its
+length.
+
+Floating on the waves we saw the abandoned boat, as for one instant
+it tossed and gaped beneath the ship's bows like a chip at the base
+of a cataract; and then the vast hull rolled over it, and it was seen
+no more till it came up weltering astern. Again we swam for it, were
+dashed against it by the seas, and were at last taken up and safely
+landed on board. Ere the squall came close to, the other boats had
+cut loose from their fish and returned to the ship in good time. The
+ship had given us up, but was still cruising, if haply it might light
+upon some token of our perishing,--an oar or a lance pole.
+
+
+
+CHAPTER 49
+
+The Hyena.
+
+
+There are certain queer times and occasions in this strange mixed
+affair we call life when a man takes this whole universe for a vast
+practical joke, though the wit thereof he but dimly discerns, and
+more than suspects that the joke is at nobody's expense but his own.
+However, nothing dispirits, and nothing seems worth while disputing.
+He bolts down all events, all creeds, and beliefs, and persuasions,
+all hard things visible and invisible, never mind how knobby; as an
+ostrich of potent digestion gobbles down bullets and gun flints. And
+as for small difficulties and worryings, prospects of sudden
+disaster, peril of life and limb; all these, and death itself, seem
+to him only sly, good-natured hits, and jolly punches in the side
+bestowed by the unseen and unaccountable old joker. That odd sort of
+wayward mood I am speaking of, comes over a man only in some time of
+extreme tribulation; it comes in the very midst of his earnestness,
+so that what just before might have seemed to him a thing most
+momentous, now seems but a part of the general joke. There is
+nothing like the perils of whaling to breed this free and easy sort
+of genial, desperado philosophy; and with it I now regarded this
+whole voyage of the Pequod, and the great White Whale its object.
+
+"Queequeg," said I, when they had dragged me, the last man, to the
+deck, and I was still shaking myself in my jacket to fling off the
+water; "Queequeg, my fine friend, does this sort of thing often
+happen?" Without much emotion, though soaked through just like me,
+he gave me to understand that such things did often happen.
+
+"Mr. Stubb," said I, turning to that worthy, who, buttoned up in his
+oil-jacket, was now calmly smoking his pipe in the rain; "Mr. Stubb,
+I think I have heard you say that of all whalemen you ever met, our
+chief mate, Mr. Starbuck, is by far the most careful and prudent. I
+suppose then, that going plump on a flying whale with your sail set
+in a foggy squall is the height of a whaleman's discretion?"
+
+"Certain. I've lowered for whales from a leaking ship in a gale off
+Cape Horn."
+
+"Mr. Flask," said I, turning to little King-Post, who was standing
+close by; "you are experienced in these things, and I am not. Will
+you tell me whether it is an unalterable law in this fishery, Mr.
+Flask, for an oarsman to break his own back pulling himself
+back-foremost into death's jaws?"
+
+"Can't you twist that smaller?" said Flask. "Yes, that's the law. I
+should like to see a boat's crew backing water up to a whale face
+foremost. Ha, ha! the whale would give them squint for squint, mind
+that!"
+
+Here then, from three impartial witnesses, I had a deliberate
+statement of the entire case. Considering, therefore, that squalls
+and capsizings in the water and consequent bivouacks on the deep,
+were matters of common occurrence in this kind of life; considering
+that at the superlatively critical instant of going on to the whale I
+must resign my life into the hands of him who steered the
+boat--oftentimes a fellow who at that very moment is in his
+impetuousness upon the point of scuttling the craft with his own
+frantic stampings; considering that the particular disaster to our
+own particular boat was chiefly to be imputed to Starbuck's driving
+on to his whale almost in the teeth of a squall, and considering that
+Starbuck, notwithstanding, was famous for his great heedfulness in
+the fishery; considering that I belonged to this uncommonly prudent
+Starbuck's boat; and finally considering in what a devil's chase I
+was implicated, touching the White Whale: taking all things together,
+I say, I thought I might as well go below and make a rough draft of
+my will. "Queequeg," said I, "come along, you shall be my lawyer,
+executor, and legatee."
+
+It may seem strange that of all men sailors should be tinkering at
+their last wills and testaments, but there are no people in the world
+more fond of that diversion. This was the fourth time in my nautical
+life that I had done the same thing. After the ceremony was
+concluded upon the present occasion, I felt all the easier; a stone
+was rolled away from my heart. Besides, all the days I should now
+live would be as good as the days that Lazarus lived after his
+resurrection; a supplementary clean gain of so many months or weeks
+as the case might be. I survived myself; my death and burial were
+locked up in my chest. I looked round me tranquilly and contentedly,
+like a quiet ghost with a clean conscience sitting inside the bars of
+a snug family vault.
+
+Now then, thought I, unconsciously rolling up the sleeves of my
+frock, here goes for a cool, collected dive at death and destruction,
+and the devil fetch the hindmost.
+
+
+
+CHAPTER 50
+
+Ahab's Boat and Crew. Fedallah.
+
+
+"Who would have thought it, Flask!" cried Stubb; "if I had but one
+leg you would not catch me in a boat, unless maybe to stop the
+plug-hole with my timber toe. Oh! he's a wonderful old man!"
+
+"I don't think it so strange, after all, on that account," said
+Flask. "If his leg were off at the hip, now, it would be a different
+thing. That would disable him; but he has one knee, and good part of
+the other left, you know."
+
+"I don't know that, my little man; I never yet saw him kneel."
+
+
+Among whale-wise people it has often been argued whether, considering
+the paramount importance of his life to the success of the voyage, it
+is right for a whaling captain to jeopardize that life in the active
+perils of the chase. So Tamerlane's soldiers often argued with tears
+in their eyes, whether that invaluable life of his ought to be
+carried into the thickest of the fight.
+
+But with Ahab the question assumed a modified aspect. Considering
+that with two legs man is but a hobbling wight in all times of
+danger; considering that the pursuit of whales is always under great
+and extraordinary difficulties; that every individual moment, indeed,
+then comprises a peril; under these circumstances is it wise for any
+maimed man to enter a whale-boat in the hunt? As a general thing,
+the joint-owners of the Pequod must have plainly thought not.
+
+Ahab well knew that although his friends at home would think little
+of his entering a boat in certain comparatively harmless vicissitudes
+of the chase, for the sake of being near the scene of action and
+giving his orders in person, yet for Captain Ahab to have a boat
+actually apportioned to him as a regular headsman in the hunt--above
+all for Captain Ahab to be supplied with five extra men, as that same
+boat's crew, he well knew that such generous conceits never entered the
+heads of the owners of the Pequod. Therefore he had not solicited a
+boat's crew from them, nor had he in any way hinted his desires on
+that head. Nevertheless he had taken private measures of his own
+touching all that matter. Until Cabaco's published discovery, the
+sailors had little foreseen it, though to be sure when, after being a
+little while out of port, all hands had concluded the customary
+business of fitting the whaleboats for service; when some time after
+this Ahab was now and then found bestirring himself in the matter of
+making thole-pins with his own hands for what was thought to be one
+of the spare boats, and even solicitously cutting the small wooden
+skewers, which when the line is running out are pinned over the
+groove in the bow: when all this was observed in him, and
+particularly his solicitude in having an extra coat of sheathing in
+the bottom of the boat, as if to make it better withstand the pointed
+pressure of his ivory limb; and also the anxiety he evinced in
+exactly shaping the thigh board, or clumsy cleat, as it is sometimes
+called, the horizontal piece in the boat's bow for bracing the knee
+against in darting or stabbing at the whale; when it was observed how
+often he stood up in that boat with his solitary knee fixed in the
+semi-circular depression in the cleat, and with the carpenter's
+chisel gouged out a little here and straightened it a little there;
+all these things, I say, had awakened much interest and curiosity at
+the time. But almost everybody supposed that this particular
+preparative heedfulness in Ahab must only be with a view to the
+ultimate chase of Moby Dick; for he had already revealed his
+intention to hunt that mortal monster in person. But such a
+supposition did by no means involve the remotest suspicion as to any
+boat's crew being assigned to that boat.
+
+Now, with the subordinate phantoms, what wonder remained soon waned
+away; for in a whaler wonders soon wane. Besides, now and then such
+unaccountable odds and ends of strange nations come up from the
+unknown nooks and ash-holes of the earth to man these floating
+outlaws of whalers; and the ships themselves often pick up such queer
+castaway creatures found tossing about the open sea on planks, bits
+of wreck, oars, whaleboats, canoes, blown-off Japanese junks, and
+what not; that Beelzebub himself might climb up the side and step
+down into the cabin to chat with the captain, and it would not create
+any unsubduable excitement in the forecastle.
+
+But be all this as it may, certain it is that while the subordinate
+phantoms soon found their place among the crew, though still as it
+were somehow distinct from them, yet that hair-turbaned Fedallah
+remained a muffled mystery to the last. Whence he came in a mannerly
+world like this, by what sort of unaccountable tie he soon evinced
+himself to be linked with Ahab's peculiar fortunes; nay, so far as to
+have some sort of a half-hinted influence; Heaven knows, but it might
+have been even authority over him; all this none knew. But one
+cannot sustain an indifferent air concerning Fedallah. He was such a
+creature as civilized, domestic people in the temperate zone only see
+in their dreams, and that but dimly; but the like of whom now and
+then glide among the unchanging Asiatic communities, especially the
+Oriental isles to the east of the continent--those insulated,
+immemorial, unalterable countries, which even in these modern days
+still preserve much of the ghostly aboriginalness of earth's primal
+generations, when the memory of the first man was a distinct
+recollection, and all men his descendants, unknowing whence he came,
+eyed each other as real phantoms, and asked of the sun and the moon
+why they were created and to what end; when though, according to
+Genesis, the angels indeed consorted with the daughters of men, the
+devils also, add the uncanonical Rabbins, indulged in mundane amours.
+
+
+
+CHAPTER 51
+
+The Spirit-Spout.
+
+
+Days, weeks passed, and under easy sail, the ivory Pequod had slowly
+swept across four several cruising-grounds; that off the Azores; off
+the Cape de Verdes; on the Plate (so called), being off the mouth of
+the Rio de la Plata; and the Carrol Ground, an unstaked, watery
+locality, southerly from St. Helena.
+
+It was while gliding through these latter waters that one serene and
+moonlight night, when all the waves rolled by like scrolls of silver;
+and, by their soft, suffusing seethings, made what seemed a silvery
+silence, not a solitude; on such a silent night a silvery jet was
+seen far in advance of the white bubbles at the bow. Lit up by the
+moon, it looked celestial; seemed some plumed and glittering god
+uprising from the sea. Fedallah first descried this jet. For of
+these moonlight nights, it was his wont to mount to the main-mast
+head, and stand a look-out there, with the same precision as if it
+had been day. And yet, though herds of whales were seen by night,
+not one whaleman in a hundred would venture a lowering for them. You
+may think with what emotions, then, the seamen beheld this old
+Oriental perched aloft at such unusual hours; his turban and the
+moon, companions in one sky. But when, after spending his uniform
+interval there for several successive nights without uttering a
+single sound; when, after all this silence, his unearthly voice was
+heard announcing that silvery, moon-lit jet, every reclining mariner
+started to his feet as if some winged spirit had lighted in the
+rigging, and hailed the mortal crew. "There she blows!" Had the
+trump of judgment blown, they could not have quivered more; yet still
+they felt no terror; rather pleasure. For though it was a most
+unwonted hour, yet so impressive was the cry, and so deliriously
+exciting, that almost every soul on board instinctively desired a
+lowering.
+
+Walking the deck with quick, side-lunging strides, Ahab commanded the
+t'gallant sails and royals to be set, and every stunsail spread. The
+best man in the ship must take the helm. Then, with every mast-head
+manned, the piled-up craft rolled down before the wind. The strange,
+upheaving, lifting tendency of the taffrail breeze filling the
+hollows of so many sails, made the buoyant, hovering deck to feel
+like air beneath the feet; while still she rushed along, as if two
+antagonistic influences were struggling in her--one to mount direct
+to heaven, the other to drive yawingly to some horizontal goal. And
+had you watched Ahab's face that night, you would have thought that
+in him also two different things were warring. While his one live
+leg made lively echoes along the deck, every stroke of his dead limb
+sounded like a coffin-tap. On life and death this old man walked.
+But though the ship so swiftly sped, and though from every eye, like
+arrows, the eager glances shot, yet the silvery jet was no more seen
+that night. Every sailor swore he saw it once, but not a second
+time.
+
+This midnight-spout had almost grown a forgotten thing, when, some
+days after, lo! at the same silent hour, it was again announced:
+again it was descried by all; but upon making sail to overtake it,
+once more it disappeared as if it had never been. And so it served
+us night after night, till no one heeded it but to wonder at it.
+Mysteriously jetted into the clear moonlight, or starlight, as the
+case might be; disappearing again for one whole day, or two days, or
+three; and somehow seeming at every distinct repetition to be
+advancing still further and further in our van, this solitary jet
+seemed for ever alluring us on.
+
+Nor with the immemorial superstition of their race, and in accordance
+with the preternaturalness, as it seemed, which in many things
+invested the Pequod, were there wanting some of the seamen who swore
+that whenever and wherever descried; at however remote times, or in
+however far apart latitudes and longitudes, that unnearable spout was
+cast by one self-same whale; and that whale, Moby Dick. For a time,
+there reigned, too, a sense of peculiar dread at this flitting
+apparition, as if it were treacherously beckoning us on and on, in
+order that the monster might turn round upon us, and rend us at last
+in the remotest and most savage seas.
+
+These temporary apprehensions, so vague but so awful, derived a
+wondrous potency from the contrasting serenity of the weather, in
+which, beneath all its blue blandness, some thought there lurked a
+devilish charm, as for days and days we voyaged along, through seas
+so wearily, lonesomely mild, that all space, in repugnance to our
+vengeful errand, seemed vacating itself of life before our urn-like
+prow.
+
+But, at last, when turning to the eastward, the Cape winds began
+howling around us, and we rose and fell upon the long, troubled seas
+that are there; when the ivory-tusked Pequod sharply bowed to the
+blast, and gored the dark waves in her madness, till, like showers of
+silver chips, the foam-flakes flew over her bulwarks; then all this
+desolate vacuity of life went away, but gave place to sights more
+dismal than before.
+
+Close to our bows, strange forms in the water darted hither and
+thither before us; while thick in our rear flew the inscrutable
+sea-ravens. And every morning, perched on our stays, rows of these
+birds were seen; and spite of our hootings, for a long time
+obstinately clung to the hemp, as though they deemed our ship some
+drifting, uninhabited craft; a thing appointed to desolation, and
+therefore fit roosting-place for their homeless selves. And heaved
+and heaved, still unrestingly heaved the black sea, as if its vast
+tides were a conscience; and the great mundane soul were in anguish
+and remorse for the long sin and suffering it had bred.
+
+Cape of Good Hope, do they call ye? Rather Cape Tormentoto, as
+called of yore; for long allured by the perfidious silences that
+before had attended us, we found ourselves launched into this
+tormented sea, where guilty beings transformed into those fowls and
+these fish, seemed condemned to swim on everlastingly without any
+haven in store, or beat that black air without any horizon. But
+calm, snow-white, and unvarying; still directing its fountain of
+feathers to the sky; still beckoning us on from before, the solitary
+jet would at times be descried.
+
+During all this blackness of the elements, Ahab, though assuming for
+the time the almost continual command of the drenched and dangerous
+deck, manifested the gloomiest reserve; and more seldom than ever
+addressed his mates. In tempestuous times like these, after
+everything above and aloft has been secured, nothing more can be done
+but passively to await the issue of the gale. Then Captain and crew
+become practical fatalists. So, with his ivory leg inserted into its
+accustomed hole, and with one hand firmly grasping a shroud, Ahab for
+hours and hours would stand gazing dead to windward, while an
+occasional squall of sleet or snow would all but congeal his very
+eyelashes together. Meantime, the crew driven from the forward part
+of the ship by the perilous seas that burstingly broke over its bows,
+stood in a line along the bulwarks in the waist; and the better to
+guard against the leaping waves, each man had slipped himself into a
+sort of bowline secured to the rail, in which he swung as in a
+loosened belt. Few or no words were spoken; and the silent ship, as
+if manned by painted sailors in wax, day after day tore on through
+all the swift madness and gladness of the demoniac waves. By night
+the same muteness of humanity before the shrieks of the ocean
+prevailed; still in silence the men swung in the bowlines; still
+wordless Ahab stood up to the blast. Even when wearied nature seemed
+demanding repose he would not seek that repose in his hammock.
+Never could Starbuck forget the old man's aspect, when one night
+going down into the cabin to mark how the barometer stood, he saw him
+with closed eyes sitting straight in his floor-screwed chair; the
+rain and half-melted sleet of the storm from which he had some time
+before emerged, still slowly dripping from the unremoved hat and
+coat. On the table beside him lay unrolled one of those charts of
+tides and currents which have previously been spoken of. His lantern
+swung from his tightly clenched hand. Though the body was erect, the
+head was thrown back so that the closed eyes were pointed towards the
+needle of the tell-tale that swung from a beam in the ceiling.*
+
+
+*The cabin-compass is called the tell-tale, because without going to
+the compass at the helm, the Captain, while below, can inform himself
+of the course of the ship.
+
+
+Terrible old man! thought Starbuck with a shudder, sleeping in this
+gale, still thou steadfastly eyest thy purpose.
+
+
+
+CHAPTER 52
+
+The Albatross.
+
+
+South-eastward from the Cape, off the distant Crozetts, a good
+cruising ground for Right Whalemen, a sail loomed ahead, the Goney
+(Albatross) by name. As she slowly drew nigh, from my lofty perch at
+the fore-mast-head, I had a good view of that sight so remarkable to
+a tyro in the far ocean fisheries--a whaler at sea, and long absent
+from home.
+
+As if the waves had been fullers, this craft was bleached like the
+skeleton of a stranded walrus. All down her sides, this spectral
+appearance was traced with long channels of reddened rust, while all
+her spars and her rigging were like the thick branches of trees
+furred over with hoar-frost. Only her lower sails were set. A wild
+sight it was to see her long-bearded look-outs at those three
+mast-heads. They seemed clad in the skins of beasts, so torn and
+bepatched the raiment that had survived nearly four years of
+cruising. Standing in iron hoops nailed to the mast, they swayed and
+swung over a fathomless sea; and though, when the ship slowly glided
+close under our stern, we six men in the air came so nigh to each
+other that we might almost have leaped from the mast-heads of one
+ship to those of the other; yet, those forlorn-looking fishermen,
+mildly eyeing us as they passed, said not one word to our own
+look-outs, while the quarter-deck hail was being heard from below.
+
+"Ship ahoy! Have ye seen the White Whale?"
+
+But as the strange captain, leaning over the pallid bulwarks, was in
+the act of putting his trumpet to his mouth, it somehow fell from his
+hand into the sea; and the wind now rising amain, he in vain strove
+to make himself heard without it. Meantime his ship was still
+increasing the distance between. While in various silent ways
+the seamen of the Pequod were evincing their observance of this
+ominous incident at the first mere mention of the White Whale's name
+to another ship, Ahab for a moment paused; it almost seemed as though
+he would have lowered a boat to board the stranger, had not the
+threatening wind forbade. But taking advantage of his windward
+position, he again seized his trumpet, and knowing by her aspect that
+the stranger vessel was a Nantucketer and shortly bound home, he
+loudly hailed--"Ahoy there! This is the Pequod, bound round the
+world! Tell them to address all future letters to the Pacific ocean!
+and this time three years, if I am not at home, tell them to address
+them to--"
+
+At that moment the two wakes were fairly crossed, and instantly,
+then, in accordance with their singular ways, shoals of small
+harmless fish, that for some days before had been placidly swimming
+by our side, darted away with what seemed shuddering fins, and ranged
+themselves fore and aft with the stranger's flanks. Though in the
+course of his continual voyagings Ahab must often before have noticed
+a similar sight, yet, to any monomaniac man, the veriest trifles
+capriciously carry meanings.
+
+"Swim away from me, do ye?" murmured Ahab, gazing over into the
+water. There seemed but little in the words, but the tone conveyed
+more of deep helpless sadness than the insane old man had ever before
+evinced. But turning to the steersman, who thus far had been holding
+the ship in the wind to diminish her headway, he cried out in his old
+lion voice,--"Up helm! Keep her off round the world!"
+
+Round the world! There is much in that sound to inspire proud
+feelings; but whereto does all that circumnavigation conduct? Only
+through numberless perils to the very point whence we started, where
+those that we left behind secure, were all the time before us.
+
+Were this world an endless plain, and by sailing eastward we could
+for ever reach new distances, and discover sights more sweet and
+strange than any Cyclades or Islands of King Solomon, then there were
+promise in the voyage. But in pursuit of those far mysteries we
+dream of, or in tormented chase of that demon phantom that, some time
+or other, swims before all human hearts; while chasing such over this
+round globe, they either lead us on in barren mazes or midway leave
+us whelmed.
+
+
+
+CHAPTER 53
+
+The Gam.
+
+
+The ostensible reason why Ahab did not go on board of the whaler we
+had spoken was this: the wind and sea betokened storms. But even had
+this not been the case, he would not after all, perhaps, have boarded
+her--judging by his subsequent conduct on similar occasions--if so it
+had been that, by the process of hailing, he had obtained a negative
+answer to the question he put. For, as it eventually turned out, he
+cared not to consort, even for five minutes, with any stranger
+captain, except he could contribute some of that information he so
+absorbingly sought. But all this might remain inadequately
+estimated, were not something said here of the peculiar usages of
+whaling-vessels when meeting each other in foreign seas, and
+especially on a common cruising-ground.
+
+If two strangers crossing the Pine Barrens in New York State, or the
+equally desolate Salisbury Plain in England; if casually encountering
+each other in such inhospitable wilds, these twain, for the life of
+them, cannot well avoid a mutual salutation; and stopping for a
+moment to interchange the news; and, perhaps, sitting down for a
+while and resting in concert: then, how much more natural that upon
+the illimitable Pine Barrens and Salisbury Plains of the sea, two
+whaling vessels descrying each other at the ends of the earth--off
+lone Fanning's Island, or the far away King's Mills; how much more
+natural, I say, that under such circumstances these ships should not
+only interchange hails, but come into still closer, more friendly and
+sociable contact. And especially would this seem to be a matter of
+course, in the case of vessels owned in one seaport, and whose
+captains, officers, and not a few of the men are personally known to
+each other; and consequently, have all sorts of dear domestic things
+to talk about.
+
+For the long absent ship, the outward-bounder, perhaps, has letters
+on board; at any rate, she will be sure to let her have some papers
+of a date a year or two later than the last one on her blurred and
+thumb-worn files. And in return for that courtesy, the outward-bound
+ship would receive the latest whaling intelligence from the
+cruising-ground to which she may be destined, a thing of the utmost
+importance to her. And in degree, all this will hold true concerning
+whaling vessels crossing each other's track on the cruising-ground
+itself, even though they are equally long absent from home. For one
+of them may have received a transfer of letters from some third, and
+now far remote vessel; and some of those letters may be for the
+people of the ship she now meets. Besides, they would exchange the
+whaling news, and have an agreeable chat. For not only would they
+meet with all the sympathies of sailors, but likewise with all the
+peculiar congenialities arising from a common pursuit and mutually
+shared privations and perils.
+
+Nor would difference of country make any very essential difference;
+that is, so long as both parties speak one language, as is the case
+with Americans and English. Though, to be sure, from the small
+number of English whalers, such meetings do not very often occur, and
+when they do occur there is too apt to be a sort of shyness between
+them; for your Englishman is rather reserved, and your Yankee, he
+does not fancy that sort of thing in anybody but himself. Besides,
+the English whalers sometimes affect a kind of metropolitan
+superiority over the American whalers; regarding the long, lean
+Nantucketer, with his nondescript provincialisms, as a sort of
+sea-peasant. But where this superiority in the English whalemen
+does really consist, it would be hard to say, seeing that the Yankees
+in one day, collectively, kill more whales than all the English,
+collectively, in ten years. But this is a harmless little foible in
+the English whale-hunters, which the Nantucketer does not take much
+to heart; probably, because he knows that he has a few foibles
+himself.
+
+So, then, we see that of all ships separately sailing the sea, the
+whalers have most reason to be sociable--and they are so. Whereas,
+some merchant ships crossing each other's wake in the mid-Atlantic,
+will oftentimes pass on without so much as a single word of
+recognition, mutually cutting each other on the high seas, like a
+brace of dandies in Broadway; and all the time indulging, perhaps, in
+finical criticism upon each other's rig. As for Men-of-War, when
+they chance to meet at sea, they first go through such a string of
+silly bowings and scrapings, such a ducking of ensigns, that there
+does not seem to be much right-down hearty good-will and brotherly
+love about it at all. As touching Slave-ships meeting, why, they are
+in such a prodigious hurry, they run away from each other as soon as
+possible. And as for Pirates, when they chance to cross each other's
+cross-bones, the first hail is--"How many skulls?"--the same way that
+whalers hail--"How many barrels?" And that question once answered,
+pirates straightway steer apart, for they are infernal villains on
+both sides, and don't like to see overmuch of each other's villanous
+likenesses.
+
+But look at the godly, honest, unostentatious, hospitable, sociable,
+free-and-easy whaler! What does the whaler do when she meets another
+whaler in any sort of decent weather? She has a "GAM," a thing so
+utterly unknown to all other ships that they never heard of the name
+even; and if by chance they should hear of it, they only grin at it,
+and repeat gamesome stuff about "spouters" and "blubber-boilers," and
+such like pretty exclamations. Why it is that all Merchant-seamen,
+and also all Pirates and Man-of-War's men, and Slave-ship sailors,
+cherish such a scornful feeling towards Whale-ships; this is a
+question it would be hard to answer. Because, in the case of
+pirates, say, I should like to know whether that profession of theirs
+has any peculiar glory about it. It sometimes ends in uncommon
+elevation, indeed; but only at the gallows. And besides, when a man
+is elevated in that odd fashion, he has no proper foundation for his
+superior altitude. Hence, I conclude, that in boasting himself to be
+high lifted above a whaleman, in that assertion the pirate has no
+solid basis to stand on.
+
+But what is a GAM? You might wear out your index-finger running up
+and down the columns of dictionaries, and never find the word. Dr.
+Johnson never attained to that erudition; Noah Webster's ark does not
+hold it. Nevertheless, this same expressive word has now for many
+years been in constant use among some fifteen thousand true born
+Yankees. Certainly, it needs a definition, and should be
+incorporated into the Lexicon. With that view, let me learnedly
+define it.
+
+GAM. NOUN--A SOCIAL MEETING OF TWO (OR MORE) WHALESHIPS, GENERALLY
+ON A CRUISING-GROUND; WHEN, AFTER EXCHANGING HAILS, THEY EXCHANGE
+VISITS BY BOATS' CREWS; THE TWO CAPTAINS REMAINING, FOR THE TIME, ON
+BOARD OF ONE SHIP, AND THE TWO CHIEF MATES ON THE OTHER.
+
+There is another little item about Gamming which must not be
+forgotten here. All professions have their own little peculiarities
+of detail; so has the whale fishery. In a pirate, man-of-war, or
+slave ship, when the captain is rowed anywhere in his boat, he always
+sits in the stern sheets on a comfortable, sometimes cushioned seat
+there, and often steers himself with a pretty little milliner's
+tiller decorated with gay cords and ribbons. But the whale-boat has
+no seat astern, no sofa of that sort whatever, and no tiller at all.
+High times indeed, if whaling captains were wheeled about the water
+on castors like gouty old aldermen in patent chairs. And as for a
+tiller, the whale-boat never admits of any such effeminacy; and
+therefore as in gamming a complete boat's crew must leave the ship,
+and hence as the boat steerer or harpooneer is of the number, that
+subordinate is the steersman upon the occasion, and the captain,
+having no place to sit in, is pulled off to his visit all standing
+like a pine tree. And often you will notice that being conscious of
+the eyes of the whole visible world resting on him from the sides of
+the two ships, this standing captain is all alive to the importance
+of sustaining his dignity by maintaining his legs. Nor is this any
+very easy matter; for in his rear is the immense projecting steering
+oar hitting him now and then in the small of his back, the after-oar
+reciprocating by rapping his knees in front. He is thus completely
+wedged before and behind, and can only expand himself sideways by
+settling down on his stretched legs; but a sudden, violent pitch of
+the boat will often go far to topple him, because length of
+foundation is nothing without corresponding breadth. Merely make a
+spread angle of two poles, and you cannot stand them up. Then,
+again, it would never do in plain sight of the world's riveted eyes,
+it would never do, I say, for this straddling captain to be seen
+steadying himself the slightest particle by catching hold of anything
+with his hands; indeed, as token of his entire, buoyant self-command,
+he generally carries his hands in his trowsers' pockets; but perhaps
+being generally very large, heavy hands, he carries them there for
+ballast. Nevertheless there have occurred instances, well
+authenticated ones too, where the captain has been known for an
+uncommonly critical moment or two, in a sudden squall say--to seize
+hold of the nearest oarsman's hair, and hold on there like grim
+death.
+
+
+
+CHAPTER 54
+
+The Town-Ho's Story.
+
+
+(AS TOLD AT THE GOLDEN INN)
+
+
+The Cape of Good Hope, and all the watery region round about there,
+is much like some noted four corners of a great highway, where you
+meet more travellers than in any other part.
+
+It was not very long after speaking the Goney that another
+homeward-bound whaleman, the Town-Ho,* was encountered. She was
+manned almost wholly by Polynesians. In the short gam that ensued
+she gave us strong news of Moby Dick. To some the general interest
+in the White Whale was now wildly heightened by a circumstance of the
+Town-Ho's story, which seemed obscurely to involve with the whale a
+certain wondrous, inverted visitation of one of those so called
+judgments of God which at times are said to overtake some men. This
+latter circumstance, with its own particular accompaniments, forming
+what may be called the secret part of the tragedy about to be
+narrated, never reached the ears of Captain Ahab or his mates. For
+that secret part of the story was unknown to the captain of the
+Town-Ho himself. It was the private property of three confederate
+white seamen of that ship, one of whom, it seems, communicated it to
+Tashtego with Romish injunctions of secrecy, but the following night
+Tashtego rambled in his sleep, and revealed so much of it in that
+way, that when he was wakened he could not well withhold the rest.
+Nevertheless, so potent an influence did this thing have on those
+seamen in the Pequod who came to the full knowledge of it, and by
+such a strange delicacy, to call it so, were they governed in this
+matter, that they kept the secret among themselves so that it never
+transpired abaft the Pequod's main-mast. Interweaving in its proper
+place this darker thread with the story as publicly narrated on the
+ship, the whole of this strange affair I now proceed to put on
+lasting record.
+
+
+*The ancient whale-cry upon first sighting a whale from the
+mast-head, still used by whalemen in hunting the famous Gallipagos
+terrapin.
+
+
+For my humor's sake, I shall preserve the style in which I once
+narrated it at Lima, to a lounging circle of my Spanish friends, one
+saint's eve, smoking upon the thick-gilt tiled piazza of the Golden
+Inn. Of those fine cavaliers, the young Dons, Pedro and Sebastian,
+were on the closer terms with me; and hence the interluding questions
+they occasionally put, and which are duly answered at the time.
+
+"Some two years prior to my first learning the events which I am
+about rehearsing to you, gentlemen, the Town-Ho, Sperm Whaler of
+Nantucket, was cruising in your Pacific here, not very many days'
+sail eastward from the eaves of this good Golden Inn. She was
+somewhere to the northward of the Line. One morning upon handling
+the pumps, according to daily usage, it was observed that she made
+more water in her hold than common. They supposed a sword-fish had
+stabbed her, gentlemen. But the captain, having some unusual reason
+for believing that rare good luck awaited him in those latitudes; and
+therefore being very averse to quit them, and the leak not being then
+considered at all dangerous, though, indeed, they could not find it
+after searching the hold as low down as was possible in rather heavy
+weather, the ship still continued her cruisings, the mariners working
+at the pumps at wide and easy intervals; but no good luck came; more
+days went by, and not only was the leak yet undiscovered, but it
+sensibly increased. So much so, that now taking some alarm, the
+captain, making all sail, stood away for the nearest harbor among the
+islands, there to have his hull hove out and repaired.
+
+"Though no small passage was before her, yet, if the commonest chance
+favoured, he did not at all fear that his ship would founder by the
+way, because his pumps were of the best, and being periodically
+relieved at them, those six-and-thirty men of his could easily keep
+the ship free; never mind if the leak should double on her. In
+truth, well nigh the whole of this passage being attended by very
+prosperous breezes, the Town-Ho had all but certainly arrived in
+perfect safety at her port without the occurrence of the least
+fatality, had it not been for the brutal overbearing of Radney, the
+mate, a Vineyarder, and the bitterly provoked vengeance of Steelkilt,
+a Lakeman and desperado from Buffalo.
+
+"'Lakeman!--Buffalo! Pray, what is a Lakeman, and where is Buffalo?'
+said Don Sebastian, rising in his swinging mat of grass.
+
+"On the eastern shore of our Lake Erie, Don; but--I crave your
+courtesy--may be, you shall soon hear further of all that. Now,
+gentlemen, in square-sail brigs and three-masted ships, well-nigh as
+large and stout as any that ever sailed out of your old Callao to far
+Manilla; this Lakeman, in the land-locked heart of our America, had
+yet been nurtured by all those agrarian freebooting impressions
+popularly connected with the open ocean. For in their interflowing
+aggregate, those grand fresh-water seas of ours,--Erie, and Ontario,
+and Huron, and Superior, and Michigan,--possess an ocean-like
+expansiveness, with many of the ocean's noblest traits; with many of
+its rimmed varieties of races and of climes. They contain round
+archipelagoes of romantic isles, even as the Polynesian waters do; in
+large part, are shored by two great contrasting nations, as the
+Atlantic is; they furnish long maritime approaches to our numerous
+territorial colonies from the East, dotted all round their banks;
+here and there are frowned upon by batteries, and by the goat-like
+craggy guns of lofty Mackinaw; they have heard the fleet thunderings
+of naval victories; at intervals, they yield their beaches to wild
+barbarians, whose red painted faces flash from out their peltry
+wigwams; for leagues and leagues are flanked by ancient and unentered
+forests, where the gaunt pines stand like serried lines of kings in
+Gothic genealogies; those same woods harboring wild Afric beasts of
+prey, and silken creatures whose exported furs give robes to Tartar
+Emperors; they mirror the paved capitals of Buffalo and Cleveland, as
+well as Winnebago villages; they float alike the full-rigged merchant
+ship, the armed cruiser of the State, the steamer, and the beech
+canoe; they are swept by Borean and dismasting blasts as direful as
+any that lash the salted wave; they know what shipwrecks are, for out
+of sight of land, however inland, they have drowned full many a
+midnight ship with all its shrieking crew. Thus, gentlemen, though
+an inlander, Steelkilt was wild-ocean born, and wild-ocean nurtured;
+as much of an audacious mariner as any. And for Radney, though in
+his infancy he may have laid him down on the lone Nantucket beach, to
+nurse at his maternal sea; though in after life he had long followed
+our austere Atlantic and your contemplative Pacific; yet was he quite
+as vengeful and full of social quarrel as the backwoods seaman, fresh
+from the latitudes of buck-horn handled bowie-knives. Yet was this
+Nantucketer a man with some good-hearted traits; and this Lakeman, a
+mariner, who though a sort of devil indeed, might yet by inflexible
+firmness, only tempered by that common decency of human recognition
+which is the meanest slave's right; thus treated, this Steelkilt had
+long been retained harmless and docile. At all events, he had proved
+so thus far; but Radney was doomed and made mad, and Steelkilt--but,
+gentlemen, you shall hear.
+
+"It was not more than a day or two at the furthest after pointing her
+prow for her island haven, that the Town-Ho's leak seemed again
+increasing, but only so as to require an hour or more at the pumps
+every day. You must know that in a settled and civilized ocean like
+our Atlantic, for example, some skippers think little of pumping
+their whole way across it; though of a still, sleepy night, should
+the officer of the deck happen to forget his duty in that respect,
+the probability would be that he and his shipmates would never again
+remember it, on account of all hands gently subsiding to the bottom.
+Nor in the solitary and savage seas far from you to the westward,
+gentlemen, is it altogether unusual for ships to keep clanging at
+their pump-handles in full chorus even for a voyage of considerable
+length; that is, if it lie along a tolerably accessible coast, or if
+any other reasonable retreat is afforded them. It is only when a
+leaky vessel is in some very out of the way part of those waters,
+some really landless latitude, that her captain begins to feel a
+little anxious.
+
+"Much this way had it been with the Town-Ho; so when her leak was
+found gaining once more, there was in truth some small concern
+manifested by several of her company; especially by Radney the mate.
+He commanded the upper sails to be well hoisted, sheeted home anew,
+and every way expanded to the breeze. Now this Radney, I suppose,
+was as little of a coward, and as little inclined to any sort of
+nervous apprehensiveness touching his own person as any fearless,
+unthinking creature on land or on sea that you can conveniently
+imagine, gentlemen. Therefore when he betrayed this solicitude about
+the safety of the ship, some of the seamen declared that it was only
+on account of his being a part owner in her. So when they were
+working that evening at the pumps, there was on this head no small
+gamesomeness slily going on among them, as they stood with their feet
+continually overflowed by the rippling clear water; clear as any
+mountain spring, gentlemen--that bubbling from the pumps ran across
+the deck, and poured itself out in steady spouts at the lee
+scupper-holes.
+
+"Now, as you well know, it is not seldom the case in this
+conventional world of ours--watery or otherwise; that when a person
+placed in command over his fellow-men finds one of them to be very
+significantly his superior in general pride of manhood, straightway
+against that man he conceives an unconquerable dislike and
+bitterness; and if he have a chance he will pull down and pulverize
+that subaltern's tower, and make a little heap of dust of it. Be
+this conceit of mine as it may, gentlemen, at all events Steelkilt
+was a tall and noble animal with a head like a Roman, and a flowing
+golden beard like the tasseled housings of your last viceroy's
+snorting charger; and a brain, and a heart, and a soul in him,
+gentlemen, which had made Steelkilt Charlemagne, had he been born son
+to Charlemagne's father. But Radney, the mate, was ugly as a mule;
+yet as hardy, as stubborn, as malicious. He did not love Steelkilt,
+and Steelkilt knew it.
+
+"Espying the mate drawing near as he was toiling at the pump with the
+rest, the Lakeman affected not to notice him, but unawed, went on
+with his gay banterings.
+
+"'Aye, aye, my merry lads, it's a lively leak this; hold a cannikin,
+one of ye, and let's have a taste. By the Lord, it's worth bottling!
+I tell ye what, men, old Rad's investment must go for it! he had
+best cut away his part of the hull and tow it home. The fact is,
+boys, that sword-fish only began the job; he's come back again with a
+gang of ship-carpenters, saw-fish, and file-fish, and what not; and
+the whole posse of 'em are now hard at work cutting and slashing at
+the bottom; making improvements, I suppose. If old Rad were here
+now, I'd tell him to jump overboard and scatter 'em. They're playing
+the devil with his estate, I can tell him. But he's a simple old
+soul,--Rad, and a beauty too. Boys, they say the rest of his
+property is invested in looking-glasses. I wonder if he'd give a
+poor devil like me the model of his nose.'
+
+"'Damn your eyes! what's that pump stopping for?' roared Radney,
+pretending not to have heard the sailors' talk. 'Thunder away at
+it!'
+
+'Aye, aye, sir,' said Steelkilt, merry as a cricket. 'Lively, boys,
+lively, now!' And with that the pump clanged like fifty
+fire-engines; the men tossed their hats off to it, and ere long that
+peculiar gasping of the lungs was heard which denotes the fullest
+tension of life's utmost energies.
+
+"Quitting the pump at last, with the rest of his band, the Lakeman
+went forward all panting, and sat himself down on the windlass; his
+face fiery red, his eyes bloodshot, and wiping the profuse sweat from
+his brow. Now what cozening fiend it was, gentlemen, that possessed
+Radney to meddle with such a man in that corporeally exasperated
+state, I know not; but so it happened. Intolerably striding along
+the deck, the mate commanded him to get a broom and sweep down the
+planks, and also a shovel, and remove some offensive matters
+consequent upon allowing a pig to run at large.
+
+"Now, gentlemen, sweeping a ship's deck at sea is a piece of
+household work which in all times but raging gales is regularly
+attended to every evening; it has been known to be done in the case
+of ships actually foundering at the time. Such, gentlemen, is the
+inflexibility of sea-usages and the instinctive love of neatness in
+seamen; some of whom would not willingly drown without first washing
+their faces. But in all vessels this broom business is the
+prescriptive province of the boys, if boys there be aboard. Besides,
+it was the stronger men in the Town-Ho that had been divided into
+gangs, taking turns at the pumps; and being the most athletic seaman
+of them all, Steelkilt had been regularly assigned captain of one of
+the gangs; consequently he should have been freed from any trivial
+business not connected with truly nautical duties, such being the
+case with his comrades. I mention all these particulars so that you
+may understand exactly how this affair stood between the two men.
+
+"But there was more than this: the order about the shovel was almost
+as plainly meant to sting and insult Steelkilt, as though Radney had
+spat in his face. Any man who has gone sailor in a whale-ship will
+understand this; and all this and doubtless much more, the Lakeman
+fully comprehended when the mate uttered his command. But as he sat
+still for a moment, and as he steadfastly looked into the mate's
+malignant eye and perceived the stacks of powder-casks heaped up in
+him and the slow-match silently burning along towards them; as he
+instinctively saw all this, that strange forbearance and
+unwillingness to stir up the deeper passionateness in any already
+ireful being--a repugnance most felt, when felt at all, by really
+valiant men even when aggrieved--this nameless phantom feeling,
+gentlemen, stole over Steelkilt.
+
+"Therefore, in his ordinary tone, only a little broken by the bodily
+exhaustion he was temporarily in, he answered him saying that
+sweeping the deck was not his business, and he would not do it. And
+then, without at all alluding to the shovel, he pointed to three
+lads as the customary sweepers; who, not being billeted at the
+pumps, had done little or nothing all day. To this, Radney replied
+with an oath, in a most domineering and outrageous manner
+unconditionally reiterating his command; meanwhile advancing upon the
+still seated Lakeman, with an uplifted cooper's club hammer which he
+had snatched from a cask near by.
+
+"Heated and irritated as he was by his spasmodic toil at the pumps,
+for all his first nameless feeling of forbearance the sweating
+Steelkilt could but ill brook this bearing in the mate; but somehow
+still smothering the conflagration within him, without speaking he
+remained doggedly rooted to his seat, till at last the incensed
+Radney shook the hammer within a few inches of his face, furiously
+commanding him to do his bidding.
+
+"Steelkilt rose, and slowly retreating round the windlass, steadily
+followed by the mate with his menacing hammer, deliberately repeated
+his intention not to obey. Seeing, however, that his forbearance had
+not the slightest effect, by an awful and unspeakable intimation with
+his twisted hand he warned off the foolish and infatuated man; but it
+was to no purpose. And in this way the two went once slowly round
+the windlass; when, resolved at last no longer to retreat, bethinking
+him that he had now forborne as much as comported with his humor, the
+Lakeman paused on the hatches and thus spoke to the officer:
+
+"'Mr. Radney, I will not obey you. Take that hammer away, or look to
+yourself.' But the predestinated mate coming still closer to him,
+where the Lakeman stood fixed, now shook the heavy hammer within an
+inch of his teeth; meanwhile repeating a string of insufferable
+maledictions. Retreating not the thousandth part of an inch;
+stabbing him in the eye with the unflinching poniard of his glance,
+Steelkilt, clenching his right hand behind him and creepingly drawing
+it back, told his persecutor that if the hammer but grazed his cheek
+he (Steelkilt) would murder him. But, gentlemen, the fool had been
+branded for the slaughter by the gods. Immediately the hammer
+touched the cheek; the next instant the lower jaw of the mate was
+stove in his head; he fell on the hatch spouting blood like a whale.
+
+"Ere the cry could go aft Steelkilt was shaking one of the backstays
+leading far aloft to where two of his comrades were standing their
+mastheads. They were both Canallers.
+
+"'Canallers!' cried Don Pedro. 'We have seen many whale-ships in our
+harbours, but never heard of your Canallers. Pardon: who and what are
+they?'
+
+"'Canallers, Don, are the boatmen belonging to our grand Erie Canal.
+You must have heard of it.'
+
+"'Nay, Senor; hereabouts in this dull, warm, most lazy, and
+hereditary land, we know but little of your vigorous North.'
+
+"'Aye? Well then, Don, refill my cup. Your chicha's very fine; and
+ere proceeding further I will tell ye what our Canallers are; for
+such information may throw side-light upon my story.'
+
+"For three hundred and sixty miles, gentlemen, through the entire
+breadth of the state of New York; through numerous populous cities
+and most thriving villages; through long, dismal, uninhabited swamps,
+and affluent, cultivated fields, unrivalled for fertility; by
+billiard-room and bar-room; through the holy-of-holies of great
+forests; on Roman arches over Indian rivers; through sun and shade;
+by happy hearts or broken; through all the wide contrasting scenery
+of those noble Mohawk counties; and especially, by rows of snow-white
+chapels, whose spires stand almost like milestones, flows one
+continual stream of Venetianly corrupt and often lawless life.
+There's your true Ashantee, gentlemen; there howl your pagans; where
+you ever find them, next door to you; under the long-flung shadow,
+and the snug patronising lee of churches. For by some curious
+fatality, as it is often noted of your metropolitan freebooters that
+they ever encamp around the halls of justice, so sinners, gentlemen,
+most abound in holiest vicinities.
+
+"'Is that a friar passing?' said Don Pedro, looking downwards into
+the crowded plazza, with humorous concern.
+
+"'Well for our northern friend, Dame Isabella's Inquisition wanes in
+Lima,' laughed Don Sebastian. 'Proceed, Senor.'
+
+"'A moment! Pardon!' cried another of the company. 'In the name of
+all us Limeese, I but desire to express to you, sir sailor, that we
+have by no means overlooked your delicacy in not substituting present
+Lima for distant Venice in your corrupt comparison. Oh! do not bow
+and look surprised; you know the proverb all along this
+coast--"Corrupt as Lima." It but bears out your saying, too;
+churches more plentiful than billiard-tables, and for ever open--and
+"Corrupt as Lima." So, too, Venice; I have been there; the holy city
+of the blessed evangelist, St. Mark!--St. Dominic, purge it! Your
+cup! Thanks: here I refill; now, you pour out again.'
+
+"Freely depicted in his own vocation, gentlemen, the Canaller would
+make a fine dramatic hero, so abundantly and picturesquely wicked is
+he. Like Mark Antony, for days and days along his green-turfed,
+flowery Nile, he indolently floats, openly toying with his
+red-cheeked Cleopatra, ripening his apricot thigh upon the sunny
+deck. But ashore, all this effeminacy is dashed. The brigandish
+guise which the Canaller so proudly sports; his slouched and
+gaily-ribboned hat betoken his grand features. A terror to the
+smiling innocence of the villages through which he floats; his swart
+visage and bold swagger are not unshunned in cities. Once a vagabond
+on his own canal, I have received good turns from one of these
+Canallers; I thank him heartily; would fain be not ungrateful; but it
+is often one of the prime redeeming qualities of your man of
+violence, that at times he has as stiff an arm to back a poor
+stranger in a strait, as to plunder a wealthy one. In sum,
+gentlemen, what the wildness of this canal life is, is emphatically
+evinced by this; that our wild whale-fishery contains so many of its
+most finished graduates, and that scarce any race of mankind, except
+Sydney men, are so much distrusted by our whaling captains. Nor does
+it at all diminish the curiousness of this matter, that to many
+thousands of our rural boys and young men born along its line, the
+probationary life of the Grand Canal furnishes the sole transition
+between quietly reaping in a Christian corn-field, and recklessly
+ploughing the waters of the most barbaric seas.
+
+"'I see! I see!' impetuously exclaimed Don Pedro, spilling his
+chicha upon his silvery ruffles. 'No need to travel! The world's
+one Lima. I had thought, now, that at your temperate North the
+generations were cold and holy as the hills.--But the story.'
+
+"I left off, gentlemen, where the Lakeman shook the backstay.
+Hardly had he done so, when he was surrounded by the three junior
+mates and the four harpooneers, who all crowded him to the deck. But
+sliding down the ropes like baleful comets, the two Canallers rushed
+into the uproar, and sought to drag their man out of it towards the
+forecastle. Others of the sailors joined with them in this attempt,
+and a twisted turmoil ensued; while standing out of harm's way, the
+valiant captain danced up and down with a whale-pike, calling upon
+his officers to manhandle that atrocious scoundrel, and smoke him
+along to the quarter-deck. At intervals, he ran close up to the
+revolving border of the confusion, and prying into the heart of it
+with his pike, sought to prick out the object of his resentment. But
+Steelkilt and his desperadoes were too much for them all; they
+succeeded in gaining the forecastle deck, where, hastily slewing
+about three or four large casks in a line with the windlass, these
+sea-Parisians entrenched themselves behind the barricade.
+
+"'Come out of that, ye pirates!' roared the captain, now menacing
+them with a pistol in each hand, just brought to him by the steward.
+'Come out of that, ye cut-throats!'
+
+"Steelkilt leaped on the barricade, and striding up and down there,
+defied the worst the pistols could do; but gave the captain to
+understand distinctly, that his (Steelkilt's) death would be the
+signal for a murderous mutiny on the part of all hands. Fearing in
+his heart lest this might prove but too true, the captain a little
+desisted, but still commanded the insurgents instantly to return to
+their duty.
+
+"'Will you promise not to touch us, if we do?' demanded their
+ringleader.
+
+"'Turn to! turn to!--I make no promise;--to your duty! Do you want
+to sink the ship, by knocking off at a time like this? Turn to!' and
+he once more raised a pistol.
+
+"'Sink the ship?' cried Steelkilt. 'Aye, let her sink. Not a man of
+us turns to, unless you swear not to raise a rope-yarn against us.
+What say ye, men?' turning to his comrades. A fierce cheer was their
+response.
+
+"The Lakeman now patrolled the barricade, all the while keeping his
+eye on the Captain, and jerking out such sentences as these:--'It's
+not our fault; we didn't want it; I told him to take his hammer away;
+it was boy's business; he might have known me before this; I told him
+not to prick the buffalo; I believe I have broken a finger here
+against his cursed jaw; ain't those mincing knives down in the
+forecastle there, men? look to those handspikes, my hearties.
+Captain, by God, look to yourself; say the word; don't be a fool;
+forget it all; we are ready to turn to; treat us decently, and we're
+your men; but we won't be flogged.'
+
+"'Turn to! I make no promises, turn to, I say!'
+
+"'Look ye, now,' cried the Lakeman, flinging out his arm towards him,
+'there are a few of us here (and I am one of them) who have shipped
+for the cruise, d'ye see; now as you well know, sir, we can claim our
+discharge as soon as the anchor is down; so we don't want a row; it's
+not our interest; we want to be peaceable; we are ready to work, but
+we won't be flogged.'
+
+"'Turn to!' roared the Captain.
+
+"Steelkilt glanced round him a moment, and then said:--'I tell you
+what it is now, Captain, rather than kill ye, and be hung for such a
+shabby rascal, we won't lift a hand against ye unless ye attack us;
+but till you say the word about not flogging us, we don't do a hand's
+turn.'
+
+"'Down into the forecastle then, down with ye, I'll keep ye there
+till ye're sick of it. Down ye go.'
+
+"'Shall we?' cried the ringleader to his men. Most of them were
+against it; but at length, in obedience to Steelkilt, they preceded
+him down into their dark den, growlingly disappearing, like bears
+into a cave.
+
+"As the Lakeman's bare head was just level with the planks, the
+Captain and his posse leaped the barricade, and rapidly drawing over
+the slide of the scuttle, planted their group of hands upon it, and
+loudly called for the steward to bring the heavy brass padlock
+belonging to the companionway.
+
+Then opening the slide a little, the Captain whispered something down
+the crack, closed it, and turned the key upon them--ten in
+number--leaving on deck some twenty or more, who thus far had
+remained neutral.
+
+"All night a wide-awake watch was kept by all the officers, forward
+and aft, especially about the forecastle scuttle and fore hatchway;
+at which last place it was feared the insurgents might emerge, after
+breaking through the bulkhead below. But the hours of darkness
+passed in peace; the men who still remained at their duty toiling
+hard at the pumps, whose clinking and clanking at intervals through
+the dreary night dismally resounded through the ship.
+
+"At sunrise the Captain went forward, and knocking on the deck,
+summoned the prisoners to work; but with a yell they refused. Water
+was then lowered down to them, and a couple of handfuls of biscuit
+were tossed after it; when again turning the key upon them and
+pocketing it, the Captain returned to the quarter-deck. Twice every
+day for three days this was repeated; but on the fourth morning a
+confused wrangling, and then a scuffling was heard, as the customary
+summons was delivered; and suddenly four men burst up from the
+forecastle, saying they were ready to turn to. The fetid closeness
+of the air, and a famishing diet, united perhaps to some fears of
+ultimate retribution, had constrained them to surrender at
+discretion. Emboldened by this, the Captain reiterated his demand to
+the rest, but Steelkilt shouted up to him a terrific hint to stop his
+babbling and betake himself where he belonged. On the fifth morning
+three others of the mutineers bolted up into the air from the
+desperate arms below that sought to restrain them. Only three were
+left.
+
+"'Better turn to, now?' said the Captain with a heartless jeer.
+
+"'Shut us up again, will ye!' cried Steelkilt.
+
+"'Oh certainly,' the Captain, and the key clicked.
+
+"It was at this point, gentlemen, that enraged by the defection of
+seven of his former associates, and stung by the mocking voice that
+had last hailed him, and maddened by his long entombment in a place
+as black as the bowels of despair; it was then that Steelkilt
+proposed to the two Canallers, thus far apparently of one mind with
+him, to burst out of their hole at the next summoning of the
+garrison; and armed with their keen mincing knives (long, crescentic,
+heavy implements with a handle at each end) run amuck from the
+bowsprit to the taffrail; and if by any devilishness of desperation
+possible, seize the ship. For himself, he would do this, he said,
+whether they joined him or not. That was the last night he should
+spend in that den. But the scheme met with no opposition on the part
+of the other two; they swore they were ready for that, or for any
+other mad thing, for anything in short but a surrender. And what was
+more, they each insisted upon being the first man on deck, when the
+time to make the rush should come. But to this their leader as
+fiercely objected, reserving that priority for himself; particularly
+as his two comrades would not yield, the one to the other, in the
+matter; and both of them could not be first, for the ladder would but
+admit one man at a time. And here, gentlemen, the foul play of these
+miscreants must come out.
+
+"Upon hearing the frantic project of their leader, each in his own
+separate soul had suddenly lighted, it would seem, upon the same
+piece of treachery, namely: to be foremost in breaking out, in
+order to be the first of the three, though the last of the ten, to
+surrender; and thereby secure whatever small chance of pardon such
+conduct might merit. But when Steelkilt made known his determination
+still to lead them to the last, they in some way, by some subtle
+chemistry of villany, mixed their before secret treacheries together;
+and when their leader fell into a doze, verbally opened their souls
+to each other in three sentences; and bound the sleeper with cords,
+and gagged him with cords; and shrieked out for the Captain at
+midnight.
+
+"Thinking murder at hand, and smelling in the dark for the blood, he
+and all his armed mates and harpooneers rushed for the forecastle.
+In a few minutes the scuttle was opened, and, bound hand and foot,
+the still struggling ringleader was shoved up into the air by his
+perfidious allies, who at once claimed the honour of securing a man
+who had been fully ripe for murder. But all these were collared, and
+dragged along the deck like dead cattle; and, side by side, were
+seized up into the mizzen rigging, like three quarters of meat, and
+there they hung till morning. 'Damn ye,' cried the Captain, pacing
+to and fro before them, 'the vultures would not touch ye, ye
+villains!'
+
+"At sunrise he summoned all hands; and separating those who had
+rebelled from those who had taken no part in the mutiny, he told the
+former that he had a good mind to flog them all round--thought, upon
+the whole, he would do so--he ought to--justice demanded it; but for
+the present, considering their timely surrender, he would let them go
+with a reprimand, which he accordingly administered in the vernacular.
+
+"'But as for you, ye carrion rogues,' turning to the three men in the
+rigging--'for you, I mean to mince ye up for the try-pots;' and,
+seizing a rope, he applied it with all his might to the backs of the
+two traitors, till they yelled no more, but lifelessly hung their
+heads sideways, as the two crucified thieves are drawn.
+
+"'My wrist is sprained with ye!' he cried, at last; 'but there is
+still rope enough left for you, my fine bantam, that wouldn't give
+up. Take that gag from his mouth, and let us hear what he can say
+for himself.'
+
+"For a moment the exhausted mutineer made a tremulous motion of his
+cramped jaws, and then painfully twisting round his head, said in a
+sort of hiss, 'What I say is this--and mind it well--if you flog me,
+I murder you!'
+
+"'Say ye so? then see how ye frighten me'--and the Captain drew off
+with the rope to strike.
+
+"'Best not,' hissed the Lakeman.
+
+"'But I must,'--and the rope was once more drawn back for the stroke.
+
+"Steelkilt here hissed out something, inaudible to all but the
+Captain; who, to the amazement of all hands, started back, paced the
+deck rapidly two or three times, and then suddenly throwing down his
+rope, said, 'I won't do it--let him go--cut him down: d'ye hear?'
+
+But as the junior mates were hurrying to execute the order, a pale
+man, with a bandaged head, arrested them--Radney the chief mate.
+Ever since the blow, he had lain in his berth; but that morning,
+hearing the tumult on the deck, he had crept out, and thus far had
+watched the whole scene. Such was the state of his mouth, that he
+could hardly speak; but mumbling something about his being willing
+and able to do what the captain dared not attempt, he snatched the
+rope and advanced to his pinioned foe.
+
+"'You are a coward!' hissed the Lakeman.
+
+"'So I am, but take that.' The mate was in the very act of striking,
+when another hiss stayed his uplifted arm. He paused: and then
+pausing no more, made good his word, spite of Steelkilt's threat,
+whatever that might have been. The three men were then cut down, all
+hands were turned to, and, sullenly worked by the moody seamen, the
+iron pumps clanged as before.
+
+"Just after dark that day, when one watch had retired below, a clamor
+was heard in the forecastle; and the two trembling traitors running
+up, besieged the cabin door, saying they durst not consort with the
+crew. Entreaties, cuffs, and kicks could not drive them back, so at
+their own instance they were put down in the ship's run for
+salvation. Still, no sign of mutiny reappeared among the rest. On
+the contrary, it seemed, that mainly at Steelkilt's instigation, they
+had resolved to maintain the strictest peacefulness, obey all orders
+to the last, and, when the ship reached port, desert her in a body.
+But in order to insure the speediest end to the voyage, they all
+agreed to another thing--namely, not to sing out for whales, in case
+any should be discovered. For, spite of her leak, and spite of all her
+other perils, the Town-Ho still maintained her mast-heads, and her
+captain was just as willing to lower for a fish that moment, as on
+the day his craft first struck the cruising ground; and Radney the mate
+was quite as ready to change his berth for a boat, and with his
+bandaged mouth seek to gag in death the vital jaw of the whale.
+
+"But though the Lakeman had induced the seamen to adopt this sort of
+passiveness in their conduct, he kept his own counsel (at least till
+all was over) concerning his own proper and private revenge upon the
+man who had stung him in the ventricles of his heart. He was in
+Radney the chief mate's watch; and as if the infatuated man sought to
+run more than half way to meet his doom, after the scene at the
+rigging, he insisted, against the express counsel of the captain,
+upon resuming the head of his watch at night. Upon this, and one or
+two other circumstances, Steelkilt systematically built the plan of
+his revenge.
+
+"During the night, Radney had an unseamanlike way of sitting on the
+bulwarks of the quarter-deck, and leaning his arm upon the gunwale of
+the boat which was hoisted up there, a little above the ship's side.
+In this attitude, it was well known, he sometimes dozed. There was a
+considerable vacancy between the boat and the ship, and down between
+this was the sea. Steelkilt calculated his time, and found that his
+next trick at the helm would come round at two o'clock, in the
+morning of the third day from that in which he had been betrayed. At
+his leisure, he employed the interval in braiding something very
+carefully in his watches below.
+
+"'What are you making there?' said a shipmate.
+
+"'What do you think? what does it look like?'
+
+"'Like a lanyard for your bag; but it's an odd one, seems to me.'
+
+'Yes, rather oddish,' said the Lakeman, holding it at arm's length
+before him; 'but I think it will answer. Shipmate, I haven't enough
+twine,--have you any?'
+
+"But there was none in the forecastle.
+
+"'Then I must get some from old Rad;' and he rose to go aft.
+
+"'You don't mean to go a begging to HIM!' said a sailor.
+
+"'Why not? Do you think he won't do me a turn, when it's to help
+himself in the end, shipmate?' and going to the mate, he looked at
+him quietly, and asked him for some twine to mend his hammock. It
+was given him--neither twine nor lanyard were seen again; but the
+next night an iron ball, closely netted, partly rolled from the
+pocket of the Lakeman's monkey jacket, as he was tucking the coat
+into his hammock for a pillow. Twenty-four hours after, his trick at
+the silent helm--nigh to the man who was apt to doze over the grave
+always ready dug to the seaman's hand--that fatal hour was then to
+come; and in the fore-ordaining soul of Steelkilt, the mate was
+already stark and stretched as a corpse, with his forehead crushed
+in.
+
+"But, gentlemen, a fool saved the would-be murderer from the bloody
+deed he had planned. Yet complete revenge he had, and without being
+the avenger. For by a mysterious fatality, Heaven itself seemed to
+step in to take out of his hands into its own the damning thing he
+would have done.
+
+"It was just between daybreak and sunrise of the morning of the
+second day, when they were washing down the decks, that a stupid
+Teneriffe man, drawing water in the main-chains, all at once shouted
+out, 'There she rolls! there she rolls!' Jesu, what a whale! It was
+Moby Dick.
+
+"'Moby Dick!' cried Don Sebastian; 'St. Dominic! Sir sailor, but do
+whales have christenings? Whom call you Moby Dick?'
+
+"'A very white, and famous, and most deadly immortal monster,
+Don;--but that would be too long a story.'
+
+"'How? how?' cried all the young Spaniards, crowding.
+
+"'Nay, Dons, Dons--nay, nay! I cannot rehearse that now. Let me get
+more into the air, Sirs.'
+
+"'The chicha! the chicha!' cried Don Pedro; 'our vigorous friend looks
+faint;--fill up his empty glass!'
+
+"No need, gentlemen; one moment, and I proceed.--Now, gentlemen, so
+suddenly perceiving the snowy whale within fifty yards of the
+ship--forgetful of the compact among the crew--in the excitement of
+the moment, the Teneriffe man had instinctively and involuntarily
+lifted his voice for the monster, though for some little time past it
+had been plainly beheld from the three sullen mast-heads. All was
+now a phrensy. 'The White Whale--the White Whale!' was the cry from
+captain, mates, and harpooneers, who, undeterred by fearful rumours,
+were all anxious to capture so famous and precious a fish; while the
+dogged crew eyed askance, and with curses, the appalling beauty of
+the vast milky mass, that lit up by a horizontal spangling sun,
+shifted and glistened like a living opal in the blue morning sea.
+Gentlemen, a strange fatality pervades the whole career of these
+events, as if verily mapped out before the world itself was charted.
+The mutineer was the bowsman of the mate, and when fast to a fish, it
+was his duty to sit next him, while Radney stood up with his lance in
+the prow, and haul in or slacken the line, at the word of command.
+Moreover, when the four boats were lowered, the mate's got the start;
+and none howled more fiercely with delight than did Steelkilt, as he
+strained at his oar. After a stiff pull, their harpooneer got fast,
+and, spear in hand, Radney sprang to the bow. He was always a
+furious man, it seems, in a boat. And now his bandaged cry was, to
+beach him on the whale's topmost back. Nothing loath, his bowsman
+hauled him up and up, through a blinding foam that blent two
+whitenesses together; till of a sudden the boat struck as against a
+sunken ledge, and keeling over, spilled out the standing mate. That
+instant, as he fell on the whale's slippery back, the boat righted,
+and was dashed aside by the swell, while Radney was tossed over into
+the sea, on the other flank of the whale. He struck out through the
+spray, and, for an instant, was dimly seen through that veil, wildly
+seeking to remove himself from the eye of Moby Dick. But the whale
+rushed round in a sudden maelstrom; seized the swimmer between his
+jaws; and rearing high up with him, plunged headlong again, and went
+down.
+
+"Meantime, at the first tap of the boat's bottom, the Lakeman had
+slackened the line, so as to drop astern from the whirlpool; calmly
+looking on, he thought his own thoughts. But a sudden, terrific,
+downward jerking of the boat, quickly brought his knife to the line.
+He cut it; and the whale was free. But, at some distance, Moby Dick
+rose again, with some tatters of Radney's red woollen shirt, caught
+in the teeth that had destroyed him. All four boats gave chase
+again; but the whale eluded them, and finally wholly disappeared.
+
+"In good time, the Town-Ho reached her port--a savage, solitary
+place--where no civilized creature resided. There, headed by the
+Lakeman, all but five or six of the foremastmen deliberately
+deserted among the palms; eventually, as it turned out, seizing a
+large double war-canoe of the savages, and setting sail for some
+other harbor.
+
+"The ship's company being reduced to but a handful, the captain
+called upon the Islanders to assist him in the laborious business of
+heaving down the ship to stop the leak. But to such unresting
+vigilance over their dangerous allies was this small band of whites
+necessitated, both by night and by day, and so extreme was the hard
+work they underwent, that upon the vessel being ready again for sea,
+they were in such a weakened condition that the captain durst not put
+off with them in so heavy a vessel. After taking counsel with his
+officers, he anchored the ship as far off shore as possible; loaded
+and ran out his two cannon from the bows; stacked his muskets on the
+poop; and warning the Islanders not to approach the ship at their
+peril, took one man with him, and setting the sail of his best
+whale-boat, steered straight before the wind for Tahiti, five hundred
+miles distant, to procure a reinforcement to his crew.
+
+"On the fourth day of the sail, a large canoe was descried, which
+seemed to have touched at a low isle of corals. He steered away from
+it; but the savage craft bore down on him; and soon the voice of
+Steelkilt hailed him to heave to, or he would run him under water.
+The captain presented a pistol. With one foot on each prow of the
+yoked war-canoes, the Lakeman laughed him to scorn; assuring him that
+if the pistol so much as clicked in the lock, he would bury him in
+bubbles and foam.
+
+"'What do you want of me?' cried the captain.
+
+"'Where are you bound? and for what are you bound?' demanded
+Steelkilt; 'no lies.'
+
+"'I am bound to Tahiti for more men.'
+
+"'Very good. Let me board you a moment--I come in peace.' With that
+he leaped from the canoe, swam to the boat; and climbing the gunwale,
+stood face to face with the captain.
+
+"'Cross your arms, sir; throw back your head. Now, repeat after me.
+As soon as Steelkilt leaves me, I swear to beach this boat on yonder
+island, and remain there six days. If I do not, may lightning strike
+me!'
+
+"'A pretty scholar,' laughed the Lakeman. 'Adios, Senor!' and
+leaping into the sea, he swam back to his comrades.
+
+"Watching the boat till it was fairly beached, and drawn up to the
+roots of the cocoa-nut trees, Steelkilt made sail again, and in due
+time arrived at Tahiti, his own place of destination. There, luck
+befriended him; two ships were about to sail for France, and were
+providentially in want of precisely that number of men which the
+sailor headed. They embarked; and so for ever got the start of
+their former captain, had he been at all minded to work them legal
+retribution.
+
+"Some ten days after the French ships sailed, the whale-boat arrived,
+and the captain was forced to enlist some of the more civilized
+Tahitians, who had been somewhat used to the sea. Chartering a small
+native schooner, he returned with them to his vessel; and finding all
+right there, again resumed his cruisings.
+
+"Where Steelkilt now is, gentlemen, none know; but upon the island of
+Nantucket, the widow of Radney still turns to the sea which refuses
+to give up its dead; still in dreams sees the awful white whale that
+destroyed him.
+
+"'Are you through?' said Don Sebastian, quietly.
+
+"'I am, Don.'
+
+"'Then I entreat you, tell me if to the best of your own convictions,
+this your story is in substance really true? It is so passing
+wonderful! Did you get it from an unquestionable source? Bear with
+me if I seem to press.'
+
+"'Also bear with all of us, sir sailor; for we all join in Don
+Sebastian's suit,' cried the company, with exceeding interest.
+
+"'Is there a copy of the Holy Evangelists in the Golden Inn,
+gentlemen?'
+
+"'Nay,' said Don Sebastian; 'but I know a worthy priest near by, who
+will quickly procure one for me. I go for it; but are you well
+advised? this may grow too serious.'
+
+"'Will you be so good as to bring the priest also, Don?'
+
+"'Though there are no Auto-da-Fe's in Lima now,' said one of the
+company to another; 'I fear our sailor friend runs risk of the
+archiepiscopacy. Let us withdraw more out of the moonlight. I see
+no need of this.'
+
+"'Excuse me for running after you, Don Sebastian; but may I also beg
+that you will be particular in procuring the largest sized
+Evangelists you can.'
+
+
+'This is the priest, he brings you the Evangelists,' said Don
+Sebastian, gravely, returning with a tall and solemn figure.
+
+"'Let me remove my hat. Now, venerable priest, further into the
+light, and hold the Holy Book before me that I may touch it.
+
+"'So help me Heaven, and on my honour the story I have told ye,
+gentlemen, is in substance and its great items, true. I know it to
+be true; it happened on this ball; I trod the ship; I knew the crew;
+I have seen and talked with Steelkilt since the death of Radney.'"
+
+
+
+CHAPTER 55
+
+Of the Monstrous Pictures of Whales.
+
+
+I shall ere long paint to you as well as one can without canvas,
+something like the true form of the whale as he actually appears to
+the eye of the whaleman when in his own absolute body the whale is
+moored alongside the whale-ship so that he can be fairly stepped upon
+there. It may be worth while, therefore, previously to advert to
+those curious imaginary portraits of him which even down to the
+present day confidently challenge the faith of the landsman. It is
+time to set the world right in this matter, by proving such pictures
+of the whale all wrong.
+
+It may be that the primal source of all those pictorial delusions
+will be found among the oldest Hindoo, Egyptian, and Grecian
+sculptures. For ever since those inventive but unscrupulous times
+when on the marble panellings of temples, the pedestals of statues,
+and on shields, medallions, cups, and coins, the dolphin was drawn in
+scales of chain-armor like Saladin's, and a helmeted head like St.
+George's; ever since then has something of the same sort of license
+prevailed, not only in most popular pictures of the whale, but in
+many scientific presentations of him.
+
+Now, by all odds, the most ancient extant portrait anyways purporting
+to be the whale's, is to be found in the famous cavern-pagoda of
+Elephanta, in India. The Brahmins maintain that in the almost
+endless sculptures of that immemorial pagoda, all the trades and
+pursuits, every conceivable avocation of man, were prefigured ages
+before any of them actually came into being. No wonder then, that in
+some sort our noble profession of whaling should have been there
+shadowed forth. The Hindoo whale referred to, occurs in a separate
+department of the wall, depicting the incarnation of Vishnu in the
+form of leviathan, learnedly known as the Matse Avatar. But though
+this sculpture is half man and half whale, so as only to give the
+tail of the latter, yet that small section of him is all wrong. It
+looks more like the tapering tail of an anaconda, than the broad palms
+of the true whale's majestic flukes.
+
+But go to the old Galleries, and look now at a great Christian
+painter's portrait of this fish; for he succeeds no better than the
+antediluvian Hindoo. It is Guido's picture of Perseus rescuing
+Andromeda from the sea-monster or whale. Where did Guido get the
+model of such a strange creature as that? Nor does Hogarth, in
+painting the same scene in his own "Perseus Descending," make out one
+whit better. The huge corpulence of that Hogarthian monster
+undulates on the surface, scarcely drawing one inch of water. It has
+a sort of howdah on its back, and its distended tusked mouth into
+which the billows are rolling, might be taken for the Traitors' Gate
+leading from the Thames by water into the Tower. Then, there are the
+Prodromus whales of old Scotch Sibbald, and Jonah's whale, as
+depicted in the prints of old Bibles and the cuts of old primers.
+What shall be said of these? As for the book-binder's whale winding
+like a vine-stalk round the stock of a descending anchor--as stamped
+and gilded on the backs and title-pages of many books both old and
+new--that is a very picturesque but purely fabulous creature,
+imitated, I take it, from the like figures on antique vases. Though
+universally denominated a dolphin, I nevertheless call this
+book-binder's fish an attempt at a whale; because it was so intended
+when the device was first introduced. It was introduced by an old
+Italian publisher somewhere about the 15th century, during the
+Revival of Learning; and in those days, and even down to a
+comparatively late period, dolphins were popularly supposed to be a
+species of the Leviathan.
+
+In the vignettes and other embellishments of some ancient books you
+will at times meet with very curious touches at the whale, where all
+manner of spouts, jets d'eau, hot springs and cold, Saratoga and
+Baden-Baden, come bubbling up from his unexhausted brain. In the
+title-page of the original edition of the "Advancement of Learning"
+you will find some curious whales.
+
+But quitting all these unprofessional attempts, let us glance at
+those pictures of leviathan purporting to be sober, scientific
+delineations, by those who know. In old Harris's collection of
+voyages there are some plates of whales extracted from a Dutch book
+of voyages, A.D. 1671, entitled "A Whaling Voyage to Spitzbergen in
+the ship Jonas in the Whale, Peter Peterson of Friesland, master."
+In one of those plates the whales, like great rafts of logs, are
+represented lying among ice-isles, with white bears running over
+their living backs. In another plate, the prodigious blunder is made
+of representing the whale with perpendicular flukes.
+
+Then again, there is an imposing quarto, written by one Captain
+Colnett, a Post Captain in the English navy, entitled "A Voyage round
+Cape Horn into the South Seas, for the purpose of extending the
+Spermaceti Whale Fisheries." In this book is an outline purporting
+to be a "Picture of a Physeter or Spermaceti whale, drawn by scale
+from one killed on the coast of Mexico, August, 1793, and hoisted on
+deck." I doubt not the captain had this veracious picture taken for
+the benefit of his marines. To mention but one thing about it, let
+me say that it has an eye which applied, according to the
+accompanying scale, to a full grown sperm whale, would make the eye
+of that whale a bow-window some five feet long. Ah, my gallant
+captain, why did ye not give us Jonah looking out of that eye!
+
+Nor are the most conscientious compilations of Natural History for
+the benefit of the young and tender, free from the same heinousness
+of mistake. Look at that popular work "Goldsmith's Animated Nature."
+In the abridged London edition of 1807, there are plates of an
+alleged "whale" and a "narwhale." I do not wish to seem inelegant,
+but this unsightly whale looks much like an amputated sow; and, as
+for the narwhale, one glimpse at it is enough to amaze one, that in
+this nineteenth century such a hippogriff could be palmed for genuine
+upon any intelligent public of schoolboys.
+
+Then, again, in 1825, Bernard Germain, Count de Lacepede, a great
+naturalist, published a scientific systemized whale book, wherein are
+several pictures of the different species of the Leviathan. All
+these are not only incorrect, but the picture of the Mysticetus or
+Greenland whale (that is to say, the Right whale), even Scoresby, a
+long experienced man as touching that species, declares not to have
+its counterpart in nature.
+
+But the placing of the cap-sheaf to all this blundering business was
+reserved for the scientific Frederick Cuvier, brother to the famous
+Baron. In 1836, he published a Natural History of Whales, in which
+he gives what he calls a picture of the Sperm Whale. Before showing
+that picture to any Nantucketer, you had best provide for your
+summary retreat from Nantucket. In a word, Frederick Cuvier's Sperm
+Whale is not a Sperm Whale, but a squash. Of course, he never had
+the benefit of a whaling voyage (such men seldom have), but whence he
+derived that picture, who can tell? Perhaps he got it as his
+scientific predecessor in the same field, Desmarest, got one of his
+authentic abortions; that is, from a Chinese drawing. And what sort
+of lively lads with the pencil those Chinese are, many queer cups and
+saucers inform us.
+
+As for the sign-painters' whales seen in the streets hanging over the
+shops of oil-dealers, what shall be said of them? They are generally
+Richard III. whales, with dromedary humps, and very savage;
+breakfasting on three or four sailor tarts, that is whaleboats full
+of mariners: their deformities floundering in seas of blood and blue
+paint.
+
+But these manifold mistakes in depicting the whale are not so very
+surprising after all. Consider! Most of the scientific drawings
+have been taken from the stranded fish; and these are about as
+correct as a drawing of a wrecked ship, with broken back, would
+correctly represent the noble animal itself in all its undashed pride
+of hull and spars. Though elephants have stood for their
+full-lengths, the living Leviathan has never yet fairly floated
+himself for his portrait. The living whale, in his full majesty and
+significance, is only to be seen at sea in unfathomable waters; and
+afloat the vast bulk of him is out of sight, like a launched
+line-of-battle ship; and out of that element it is a thing eternally
+impossible for mortal man to hoist him bodily into the air, so as to
+preserve all his mighty swells and undulations. And, not to speak of
+the highly presumable difference of contour between a young sucking
+whale and a full-grown Platonian Leviathan; yet, even in the case of
+one of those young sucking whales hoisted to a ship's deck, such is
+then the outlandish, eel-like, limbered, varying shape of him, that
+his precise expression the devil himself could not catch.
+
+But it may be fancied, that from the naked skeleton of the stranded
+whale, accurate hints may be derived touching his true form. Not at
+all. For it is one of the more curious things about this Leviathan,
+that his skeleton gives very little idea of his general shape.
+Though Jeremy Bentham's skeleton, which hangs for candelabra in the
+library of one of his executors, correctly conveys the idea of a
+burly-browed utilitarian old gentleman, with all Jeremy's other
+leading personal characteristics; yet nothing of this kind could be
+inferred from any leviathan's articulated bones. In fact, as the
+great Hunter says, the mere skeleton of the whale bears the same
+relation to the fully invested and padded animal as the insect does
+to the chrysalis that so roundingly envelopes it. This peculiarity
+is strikingly evinced in the head, as in some part of this book will
+be incidentally shown. It is also very curiously displayed in the
+side fin, the bones of which almost exactly answer to the bones of the
+human hand, minus only the thumb. This fin has four regular
+bone-fingers, the index, middle, ring, and little finger. But all
+these are permanently lodged in their fleshy covering, as the human
+fingers in an artificial covering. "However recklessly the whale may
+sometimes serve us," said humorous Stubb one day, "he can never be
+truly said to handle us without mittens."
+
+For all these reasons, then, any way you may look at it, you must
+needs conclude that the great Leviathan is that one creature in the
+world which must remain unpainted to the last. True, one portrait
+may hit the mark much nearer than another, but none can hit it with
+any very considerable degree of exactness. So there is no earthly
+way of finding out precisely what the whale really looks like. And
+the only mode in which you can derive even a tolerable idea of his
+living contour, is by going a whaling yourself; but by so doing, you
+run no small risk of being eternally stove and sunk by him.
+Wherefore, it seems to me you had best not be too fastidious in your
+curiosity touching this Leviathan.
+
+
+
+CHAPTER 56
+
+Of the Less Erroneous Pictures of Whales, and the True Pictures of
+Whaling Scenes.
+
+
+In connexion with the monstrous pictures of whales, I am strongly
+tempted here to enter upon those still more monstrous stories of them
+which are to be found in certain books, both ancient and modern,
+especially in Pliny, Purchas, Hackluyt, Harris, Cuvier, etc. But I
+pass that matter by.
+
+I know of only four published outlines of the great Sperm Whale;
+Colnett's, Huggins's, Frederick Cuvier's, and Beale's. In the
+previous chapter Colnett and Cuvier have been referred to. Huggins's
+is far better than theirs; but, by great odds, Beale's is the best.
+All Beale's drawings of this whale are good, excepting the middle
+figure in the picture of three whales in various attitudes, capping
+his second chapter. His frontispiece, boats attacking Sperm Whales,
+though no doubt calculated to excite the civil scepticism of some
+parlor men, is admirably correct and life-like in its general effect.
+Some of the Sperm Whale drawings in J. Ross Browne are pretty
+correct in contour; but they are wretchedly engraved. That is not
+his fault though.
+
+Of the Right Whale, the best outline pictures are in Scoresby; but
+they are drawn on too small a scale to convey a desirable impression.
+He has but one picture of whaling scenes, and this is a sad
+deficiency, because it is by such pictures only, when at all well
+done, that you can derive anything like a truthful idea of the living
+whale as seen by his living hunters.
+
+But, taken for all in all, by far the finest, though in some details
+not the most correct, presentations of whales and whaling scenes to
+be anywhere found, are two large French engravings, well executed,
+and taken from paintings by one Garnery. Respectively, they
+represent attacks on the Sperm and Right Whale. In the first
+engraving a noble Sperm Whale is depicted in full majesty of might,
+just risen beneath the boat from the profundities of the ocean, and
+bearing high in the air upon his back the terrific wreck of the
+stoven planks. The prow of the boat is partially unbroken, and is
+drawn just balancing upon the monster's spine; and standing in that
+prow, for that one single incomputable flash of time, you behold an
+oarsman, half shrouded by the incensed boiling spout of the whale,
+and in the act of leaping, as if from a precipice. The action of the
+whole thing is wonderfully good and true. The half-emptied line-tub
+floats on the whitened sea; the wooden poles of the spilled harpoons
+obliquely bob in it; the heads of the swimming crew are scattered
+about the whale in contrasting expressions of affright; while in the
+black stormy distance the ship is bearing down upon the scene.
+Serious fault might be found with the anatomical details of this
+whale, but let that pass; since, for the life of me, I could not draw
+so good a one.
+
+In the second engraving, the boat is in the act of drawing alongside
+the barnacled flank of a large running Right Whale, that rolls his
+black weedy bulk in the sea like some mossy rock-slide from the
+Patagonian cliffs. His jets are erect, full, and black like soot; so
+that from so abounding a smoke in the chimney, you would think there
+must be a brave supper cooking in the great bowels below. Sea fowls
+are pecking at the small crabs, shell-fish, and other sea candies and
+maccaroni, which the Right Whale sometimes carries on his pestilent
+back. And all the while the thick-lipped leviathan is rushing
+through the deep, leaving tons of tumultuous white curds in his wake,
+and causing the slight boat to rock in the swells like a skiff caught
+nigh the paddle-wheels of an ocean steamer. Thus, the foreground is
+all raging commotion; but behind, in admirable artistic contrast, is
+the glassy level of a sea becalmed, the drooping unstarched sails of
+the powerless ship, and the inert mass of a dead whale, a conquered
+fortress, with the flag of capture lazily hanging from the whale-pole
+inserted into his spout-hole.
+
+Who Garnery the painter is, or was, I know not. But my life for it
+he was either practically conversant with his subject, or else
+marvellously tutored by some experienced whaleman. The French are
+the lads for painting action. Go and gaze upon all the paintings of
+Europe, and where will you find such a gallery of living and
+breathing commotion on canvas, as in that triumphal hall at
+Versailles; where the beholder fights his way, pell-mell, through the
+consecutive great battles of France; where every sword seems a flash
+of the Northern Lights, and the successive armed kings and Emperors
+dash by, like a charge of crowned centaurs? Not wholly unworthy of a
+place in that gallery, are these sea battle-pieces of Garnery.
+
+The natural aptitude of the French for seizing the picturesqueness of
+things seems to be peculiarly evinced in what paintings and
+engravings they have of their whaling scenes. With not one tenth of
+England's experience in the fishery, and not the thousandth part of
+that of the Americans, they have nevertheless furnished both nations
+with the only finished sketches at all capable of conveying the real
+spirit of the whale hunt. For the most part, the English and
+American whale draughtsmen seem entirely content with presenting the
+mechanical outline of things, such as the vacant profile of the
+whale; which, so far as picturesqueness of effect is concerned, is
+about tantamount to sketching the profile of a pyramid. Even
+Scoresby, the justly renowned Right whaleman, after giving us a stiff
+full length of the Greenland whale, and three or four delicate
+miniatures of narwhales and porpoises, treats us to a series of
+classical engravings of boat hooks, chopping knives, and grapnels;
+and with the microscopic diligence of a Leuwenhoeck submits to the
+inspection of a shivering world ninety-six fac-similes of magnified
+Arctic snow crystals. I mean no disparagement to the excellent
+voyager (I honour him for a veteran), but in so important a matter it
+was certainly an oversight not to have procured for every crystal a
+sworn affidavit taken before a Greenland Justice of the Peace.
+
+In addition to those fine engravings from Garnery, there are two
+other French engravings worthy of note, by some one who subscribes
+himself "H. Durand." One of them, though not precisely adapted to
+our present purpose, nevertheless deserves mention on other accounts.
+It is a quiet noon-scene among the isles of the Pacific; a French
+whaler anchored, inshore, in a calm, and lazily taking water on
+board; the loosened sails of the ship, and the long leaves of the
+palms in the background, both drooping together in the breezeless
+air. The effect is very fine, when considered with reference to its
+presenting the hardy fishermen under one of their few aspects of
+oriental repose. The other engraving is quite a different affair:
+the ship hove-to upon the open sea, and in the very heart of the
+Leviathanic life, with a Right Whale alongside; the vessel (in the
+act of cutting-in) hove over to the monster as if to a quay; and a
+boat, hurriedly pushing off from this scene of activity, is about
+giving chase to whales in the distance. The harpoons and lances lie
+levelled for use; three oarsmen are just setting the mast in its
+hole; while from a sudden roll of the sea, the little craft stands
+half-erect out of the water, like a rearing horse. From the ship,
+the smoke of the torments of the boiling whale is going up like the
+smoke over a village of smithies; and to windward, a black cloud,
+rising up with earnest of squalls and rains, seems to quicken the
+activity of the excited seamen.
+
+
+
+CHAPTER 57
+
+Of Whales in Paint; in Teeth; in Wood; in Sheet-Iron; in Stone; in
+Mountains; in Stars.
+
+
+On Tower-hill, as you go down to the London docks, you may have seen
+a crippled beggar (or KEDGER, as the sailors say) holding a painted
+board before him, representing the tragic scene in which he lost his
+leg. There are three whales and three boats; and one of the boats
+(presumed to contain the missing leg in all its original integrity)
+is being crunched by the jaws of the foremost whale. Any time these
+ten years, they tell me, has that man held up that picture, and
+exhibited that stump to an incredulous world. But the time of his
+justification has now come. His three whales are as good whales as
+were ever published in Wapping, at any rate; and his stump as
+unquestionable a stump as any you will find in the western clearings.
+But, though for ever mounted on that stump, never a stump-speech
+does the poor whaleman make; but, with downcast eyes, stands ruefully
+contemplating his own amputation.
+
+Throughout the Pacific, and also in Nantucket, and New Bedford, and
+Sag Harbor, you will come across lively sketches of whales and
+whaling-scenes, graven by the fishermen themselves on Sperm
+Whale-teeth, or ladies' busks wrought out of the Right Whale-bone,
+and other like skrimshander articles, as the whalemen call the
+numerous little ingenious contrivances they elaborately carve out of
+the rough material, in their hours of ocean leisure. Some of them
+have little boxes of dentistical-looking implements, specially
+intended for the skrimshandering business. But, in general, they
+toil with their jack-knives alone; and, with that almost omnipotent
+tool of the sailor, they will turn you out anything you please, in
+the way of a mariner's fancy.
+
+Long exile from Christendom and civilization inevitably restores a
+man to that condition in which God placed him, i.e. what is called
+savagery. Your true whale-hunter is as much a savage as an Iroquois.
+I myself am a savage, owning no allegiance but to the King of the
+Cannibals; and ready at any moment to rebel against him.
+
+Now, one of the peculiar characteristics of the savage in his
+domestic hours, is his wonderful patience of industry. An ancient
+Hawaiian war-club or spear-paddle, in its full multiplicity and
+elaboration of carving, is as great a trophy of human perseverance as
+a Latin lexicon. For, with but a bit of broken sea-shell or a
+shark's tooth, that miraculous intricacy of wooden net-work has been
+achieved; and it has cost steady years of steady application.
+
+As with the Hawaiian savage, so with the white sailor-savage. With
+the same marvellous patience, and with the same single shark's tooth,
+of his one poor jack-knife, he will carve you a bit of bone
+sculpture, not quite as workmanlike, but as close packed in its
+maziness of design, as the Greek savage, Achilles's shield; and full
+of barbaric spirit and suggestiveness, as the prints of that fine old
+Dutch savage, Albert Durer.
+
+Wooden whales, or whales cut in profile out of the small dark slabs
+of the noble South Sea war-wood, are frequently met with in the
+forecastles of American whalers. Some of them are done with much
+accuracy.
+
+At some old gable-roofed country houses you will see brass whales
+hung by the tail for knockers to the road-side door. When the porter
+is sleepy, the anvil-headed whale would be best. But these knocking
+whales are seldom remarkable as faithful essays. On the spires of
+some old-fashioned churches you will see sheet-iron whales placed
+there for weather-cocks; but they are so elevated, and besides that
+are to all intents and purposes so labelled with "HANDS OFF!" you
+cannot examine them closely enough to decide upon their merit.
+
+In bony, ribby regions of the earth, where at the base of high broken
+cliffs masses of rock lie strewn in fantastic groupings upon the
+plain, you will often discover images as of the petrified forms of
+the Leviathan partly merged in grass, which of a windy day breaks
+against them in a surf of green surges.
+
+Then, again, in mountainous countries where the traveller is
+continually girdled by amphitheatrical heights; here and there from
+some lucky point of view you will catch passing glimpses of the
+profiles of whales defined along the undulating ridges. But you must
+be a thorough whaleman, to see these sights; and not only that, but
+if you wish to return to such a sight again, you must be sure and
+take the exact intersecting latitude and longitude of your first
+stand-point, else so chance-like are such observations of the hills,
+that your precise, previous stand-point would require a laborious
+re-discovery; like the Soloma Islands, which still remain incognita,
+though once high-ruffed Mendanna trod them and old Figuera
+chronicled them.
+
+Nor when expandingly lifted by your subject, can you fail to trace
+out great whales in the starry heavens, and boats in pursuit of them;
+as when long filled with thoughts of war the Eastern nations saw
+armies locked in battle among the clouds. Thus at the North have I
+chased Leviathan round and round the Pole with the revolutions of the
+bright points that first defined him to me. And beneath the
+effulgent Antarctic skies I have boarded the Argo-Navis, and joined
+the chase against the starry Cetus far beyond the utmost stretch of
+Hydrus and the Flying Fish.
+
+With a frigate's anchors for my bridle-bitts and fasces of harpoons
+for spurs, would I could mount that whale and leap the topmost skies,
+to see whether the fabled heavens with all their countless tents
+really lie encamped beyond my mortal sight!
+
+
+
+CHAPTER 58
+
+Brit.
+
+
+Steering north-eastward from the Crozetts, we fell in with vast
+meadows of brit, the minute, yellow substance, upon which the Right
+Whale largely feeds. For leagues and leagues it undulated round us,
+so that we seemed to be sailing through boundless fields of ripe and
+golden wheat.
+
+On the second day, numbers of Right Whales were seen, who, secure
+from the attack of a Sperm Whaler like the Pequod, with open jaws
+sluggishly swam through the brit, which, adhering to the fringing
+fibres of that wondrous Venetian blind in their mouths, was in that
+manner separated from the water that escaped at the lip.
+
+As morning mowers, who side by side slowly and seethingly advance
+their scythes through the long wet grass of marshy meads; even so
+these monsters swam, making a strange, grassy, cutting sound; and
+leaving behind them endless swaths of blue upon the yellow sea.*
+
+
+*That part of the sea known among whalemen as the "Brazil Banks" does
+not bear that name as the Banks of Newfoundland do, because of there
+being shallows and soundings there, but because of this remarkable
+meadow-like appearance, caused by the vast drifts of brit continually
+floating in those latitudes, where the Right Whale is often chased.
+
+
+But it was only the sound they made as they parted the brit which at
+all reminded one of mowers. Seen from the mast-heads, especially
+when they paused and were stationary for a while, their vast black
+forms looked more like lifeless masses of rock than anything else.
+And as in the great hunting countries of India, the stranger at a
+distance will sometimes pass on the plains recumbent elephants
+without knowing them to be such, taking them for bare, blackened
+elevations of the soil; even so, often, with him, who for the first
+time beholds this species of the leviathans of the sea. And even
+when recognised at last, their immense magnitude renders it very
+hard really to believe that such bulky masses of overgrowth can
+possibly be instinct, in all parts, with the same sort of life that
+lives in a dog or a horse.
+
+Indeed, in other respects, you can hardly regard any creatures of the
+deep with the same feelings that you do those of the shore. For
+though some old naturalists have maintained that all creatures of the
+land are of their kind in the sea; and though taking a broad general
+view of the thing, this may very well be; yet coming to specialties,
+where, for example, does the ocean furnish any fish that in
+disposition answers to the sagacious kindness of the dog? The
+accursed shark alone can in any generic respect be said to bear
+comparative analogy to him.
+
+But though, to landsmen in general, the native inhabitants of the
+seas have ever been regarded with emotions unspeakably unsocial and
+repelling; though we know the sea to be an everlasting terra
+incognita, so that Columbus sailed over numberless unknown worlds to
+discover his one superficial western one; though, by vast odds, the
+most terrific of all mortal disasters have immemorially and
+indiscriminately befallen tens and hundreds of thousands of those who
+have gone upon the waters; though but a moment's consideration will
+teach, that however baby man may brag of his science and skill, and
+however much, in a flattering future, that science and skill may
+augment; yet for ever and for ever, to the crack of doom, the sea
+will insult and murder him, and pulverize the stateliest, stiffest
+frigate he can make; nevertheless, by the continual repetition of
+these very impressions, man has lost that sense of the full awfulness
+of the sea which aboriginally belongs to it.
+
+The first boat we read of, floated on an ocean, that with Portuguese
+vengeance had whelmed a whole world without leaving so much as a
+widow. That same ocean rolls now; that same ocean destroyed the
+wrecked ships of last year. Yea, foolish mortals, Noah's flood is
+not yet subsided; two thirds of the fair world it yet covers.
+
+Wherein differ the sea and the land, that a miracle upon one is not a
+miracle upon the other? Preternatural terrors rested upon the
+Hebrews, when under the feet of Korah and his company the live ground
+opened and swallowed them up for ever; yet not a modern sun ever
+sets, but in precisely the same manner the live sea swallows up ships
+and crews.
+
+But not only is the sea such a foe to man who is an alien to it, but
+it is also a fiend to its own off-spring; worse than the Persian host
+who murdered his own guests; sparing not the creatures which itself
+hath spawned. Like a savage tigress that tossing in the jungle
+overlays her own cubs, so the sea dashes even the mightiest whales
+against the rocks, and leaves them there side by side with the split
+wrecks of ships. No mercy, no power but its own controls it.
+Panting and snorting like a mad battle steed that has lost its rider,
+the masterless ocean overruns the globe.
+
+Consider the subtleness of the sea; how its most dreaded creatures
+glide under water, unapparent for the most part, and treacherously
+hidden beneath the loveliest tints of azure. Consider also the
+devilish brilliance and beauty of many of its most remorseless
+tribes, as the dainty embellished shape of many species of sharks.
+Consider, once more, the universal cannibalism of the sea; all whose
+creatures prey upon each other, carrying on eternal war since the
+world began.
+
+Consider all this; and then turn to this green, gentle, and most
+docile earth; consider them both, the sea and the land; and do you
+not find a strange analogy to something in yourself? For as this
+appalling ocean surrounds the verdant land, so in the soul of man
+there lies one insular Tahiti, full of peace and joy, but encompassed
+by all the horrors of the half known life. God keep thee! Push not
+off from that isle, thou canst never return!
+
+
+CHAPTER 59
+
+Squid.
+
+
+Slowly wading through the meadows of brit, the Pequod still held on
+her way north-eastward towards the island of Java; a gentle air
+impelling her keel, so that in the surrounding serenity her three
+tall tapering masts mildly waved to that languid breeze, as three
+mild palms on a plain. And still, at wide intervals in the silvery
+night, the lonely, alluring jet would be seen.
+
+But one transparent blue morning, when a stillness almost
+preternatural spread over the sea, however unattended with any
+stagnant calm; when the long burnished sun-glade on the waters seemed
+a golden finger laid across them, enjoining some secrecy; when the
+slippered waves whispered together as they softly ran on; in this
+profound hush of the visible sphere a strange spectre was seen by
+Daggoo from the main-mast-head.
+
+In the distance, a great white mass lazily rose, and rising higher
+and higher, and disentangling itself from the azure, at last gleamed
+before our prow like a snow-slide, new slid from the hills. Thus
+glistening for a moment, as slowly it subsided, and sank. Then once
+more arose, and silently gleamed. It seemed not a whale; and yet is
+this Moby Dick? thought Daggoo. Again the phantom went down, but on
+re-appearing once more, with a stiletto-like cry that startled every
+man from his nod, the negro yelled out--"There! there again! there
+she breaches! right ahead! The White Whale, the White Whale!"
+
+Upon this, the seamen rushed to the yard-arms, as in swarming-time
+the bees rush to the boughs. Bare-headed in the sultry sun, Ahab
+stood on the bowsprit, and with one hand pushed far behind in
+readiness to wave his orders to the helmsman, cast his eager glance
+in the direction indicated aloft by the outstretched motionless arm
+of Daggoo.
+
+Whether the flitting attendance of the one still and solitary jet had
+gradually worked upon Ahab, so that he was now prepared to connect
+the ideas of mildness and repose with the first sight of the
+particular whale he pursued; however this was, or whether his
+eagerness betrayed him; whichever way it might have been, no sooner
+did he distinctly perceive the white mass, than with a quick
+intensity he instantly gave orders for lowering.
+
+The four boats were soon on the water; Ahab's in advance, and all
+swiftly pulling towards their prey. Soon it went down, and while,
+with oars suspended, we were awaiting its reappearance, lo! in the
+same spot where it sank, once more it slowly rose. Almost forgetting
+for the moment all thoughts of Moby Dick, we now gazed at the most
+wondrous phenomenon which the secret seas have hitherto revealed to
+mankind. A vast pulpy mass, furlongs in length and breadth, of a
+glancing cream-colour, lay floating on the water, innumerable long
+arms radiating from its centre, and curling and twisting like a nest
+of anacondas, as if blindly to clutch at any hapless object within
+reach. No perceptible face or front did it have; no conceivable
+token of either sensation or instinct; but undulated there on the
+billows, an unearthly, formless, chance-like apparition of life.
+
+As with a low sucking sound it slowly disappeared again, Starbuck
+still gazing at the agitated waters where it had sunk, with a wild
+voice exclaimed--"Almost rather had I seen Moby Dick and fought him,
+than to have seen thee, thou white ghost!"
+
+"What was it, Sir?" said Flask.
+
+"The great live squid, which, they say, few whale-ships ever beheld,
+and returned to their ports to tell of it."
+
+But Ahab said nothing; turning his boat, he sailed back to the
+vessel; the rest as silently following.
+
+Whatever superstitions the sperm whalemen in general have connected
+with the sight of this object, certain it is, that a glimpse of it
+being so very unusual, that circumstance has gone far to invest it
+with portentousness. So rarely is it beheld, that though one and all
+of them declare it to be the largest animated thing in the ocean, yet
+very few of them have any but the most vague ideas concerning its
+true nature and form; notwithstanding, they believe it to furnish to
+the sperm whale his only food. For though other species of whales
+find their food above water, and may be seen by man in the act of
+feeding, the spermaceti whale obtains his whole food in unknown zones
+below the surface; and only by inference is it that any one can tell
+of what, precisely, that food consists. At times, when closely
+pursued, he will disgorge what are supposed to be the detached arms
+of the squid; some of them thus exhibited exceeding twenty and thirty
+feet in length. They fancy that the monster to which these arms
+belonged ordinarily clings by them to the bed of the ocean; and that
+the sperm whale, unlike other species, is supplied with teeth in
+order to attack and tear it.
+
+There seems some ground to imagine that the great Kraken of Bishop
+Pontoppodan may ultimately resolve itself into Squid. The manner in
+which the Bishop describes it, as alternately rising and sinking,
+with some other particulars he narrates, in all this the two
+correspond. But much abatement is necessary with respect to the
+incredible bulk he assigns it.
+
+By some naturalists who have vaguely heard rumors of the mysterious
+creature, here spoken of, it is included among the class of
+cuttle-fish, to which, indeed, in certain external respects it would
+seem to belong, but only as the Anak of the tribe.
+
+
+
+CHAPTER 60
+
+The Line.
+
+
+With reference to the whaling scene shortly to be described, as well
+as for the better understanding of all similar scenes elsewhere
+presented, I have here to speak of the magical, sometimes horrible
+whale-line.
+
+The line originally used in the fishery was of the best hemp,
+slightly vapoured with tar, not impregnated with it, as in the case of
+ordinary ropes; for while tar, as ordinarily used, makes the hemp
+more pliable to the rope-maker, and also renders the rope itself more
+convenient to the sailor for common ship use; yet, not only would the
+ordinary quantity too much stiffen the whale-line for the close
+coiling to which it must be subjected; but as most seamen are
+beginning to learn, tar in general by no means adds to the rope's
+durability or strength, however much it may give it compactness and
+gloss.
+
+Of late years the Manilla rope has in the American fishery almost
+entirely superseded hemp as a material for whale-lines; for, though
+not so durable as hemp, it is stronger, and far more soft and
+elastic; and I will add (since there is an aesthetics in all things),
+is much more handsome and becoming to the boat, than hemp. Hemp is a
+dusky, dark fellow, a sort of Indian; but Manilla is as a
+golden-haired Circassian to behold.
+
+The whale-line is only two-thirds of an inch in thickness. At first
+sight, you would not think it so strong as it really is. By
+experiment its one and fifty yarns will each suspend a weight of one
+hundred and twenty pounds; so that the whole rope will bear a strain
+nearly equal to three tons. In length, the common sperm whale-line
+measures something over two hundred fathoms. Towards the stern of
+the boat it is spirally coiled away in the tub, not like the
+worm-pipe of a still though, but so as to form one round,
+cheese-shaped mass of densely bedded "sheaves," or layers of
+concentric spiralizations, without any hollow but the "heart," or
+minute vertical tube formed at the axis of the cheese. As the least
+tangle or kink in the coiling would, in running out, infallibly take
+somebody's arm, leg, or entire body off, the utmost precaution is
+used in stowing the line in its tub. Some harpooneers will consume
+almost an entire morning in this business, carrying the line high
+aloft and then reeving it downwards through a block towards the tub,
+so as in the act of coiling to free it from all possible wrinkles and
+twists.
+
+In the English boats two tubs are used instead of one; the same line
+being continuously coiled in both tubs. There is some advantage in
+this; because these twin-tubs being so small they fit more readily
+into the boat, and do not strain it so much; whereas, the American
+tub, nearly three feet in diameter and of proportionate depth, makes
+a rather bulky freight for a craft whose planks are but one half-inch
+in thickness; for the bottom of the whale-boat is like critical ice,
+which will bear up a considerable distributed weight, but not very
+much of a concentrated one. When the painted canvas cover is clapped
+on the American line-tub, the boat looks as if it were pulling off
+with a prodigious great wedding-cake to present to the whales.
+
+Both ends of the line are exposed; the lower end terminating in an
+eye-splice or loop coming up from the bottom against the side of the
+tub, and hanging over its edge completely disengaged from everything.
+This arrangement of the lower end is necessary on two accounts.
+First: In order to facilitate the fastening to it of an additional
+line from a neighboring boat, in case the stricken whale should sound
+so deep as to threaten to carry off the entire line originally
+attached to the harpoon. In these instances, the whale of course is
+shifted like a mug of ale, as it were, from the one boat to the
+other; though the first boat always hovers at hand to assist its
+consort. Second: This arrangement is indispensable for common
+safety's sake; for were the lower end of the line in any way attached
+to the boat, and were the whale then to run the line out to the end
+almost in a single, smoking minute as he sometimes does, he would not
+stop there, for the doomed boat would infallibly be dragged down
+after him into the profundity of the sea; and in that case no
+town-crier would ever find her again.
+
+Before lowering the boat for the chase, the upper end of the line is
+taken aft from the tub, and passing round the loggerhead there, is
+again carried forward the entire length of the boat, resting
+crosswise upon the loom or handle of every man's oar, so that it jogs
+against his wrist in rowing; and also passing between the men, as
+they alternately sit at the opposite gunwales, to the leaded chocks
+or grooves in the extreme pointed prow of the boat, where a wooden
+pin or skewer the size of a common quill, prevents it from slipping
+out. From the chocks it hangs in a slight festoon over the bows, and
+is then passed inside the boat again; and some ten or twenty fathoms
+(called box-line) being coiled upon the box in the bows, it continues
+its way to the gunwale still a little further aft, and is then
+attached to the short-warp--the rope which is immediately connected
+with the harpoon; but previous to that connexion, the short-warp goes
+through sundry mystifications too tedious to detail.
+
+Thus the whale-line folds the whole boat in its complicated coils,
+twisting and writhing around it in almost every direction. All the
+oarsmen are involved in its perilous contortions; so that to the
+timid eye of the landsman, they seem as Indian jugglers, with the
+deadliest snakes sportively festooning their limbs. Nor can any son
+of mortal woman, for the first time, seat himself amid those hempen
+intricacies, and while straining his utmost at the oar, bethink him
+that at any unknown instant the harpoon may be darted, and all these
+horrible contortions be put in play like ringed lightnings; he cannot
+be thus circumstanced without a shudder that makes the very marrow in
+his bones to quiver in him like a shaken jelly. Yet habit--strange
+thing! what cannot habit accomplish?--Gayer sallies, more merry
+mirth, better jokes, and brighter repartees, you never heard over
+your mahogany, than you will hear over the half-inch white cedar of
+the whale-boat, when thus hung in hangman's nooses; and, like the six
+burghers of Calais before King Edward, the six men composing the crew
+pull into the jaws of death, with a halter around every neck, as you
+may say.
+
+Perhaps a very little thought will now enable you to account for
+those repeated whaling disasters--some few of which are casually
+chronicled--of this man or that man being taken out of the boat by
+the line, and lost. For, when the line is darting out, to be seated
+then in the boat, is like being seated in the midst of the manifold
+whizzings of a steam-engine in full play, when every flying beam, and
+shaft, and wheel, is grazing you. It is worse; for you cannot sit
+motionless in the heart of these perils, because the boat is rocking
+like a cradle, and you are pitched one way and the other, without the
+slightest warning; and only by a certain self-adjusting buoyancy and
+simultaneousness of volition and action, can you escape being made a
+Mazeppa of, and run away with where the all-seeing sun himself could
+never pierce you out.
+
+Again: as the profound calm which only apparently precedes and
+prophesies of the storm, is perhaps more awful than the storm itself;
+for, indeed, the calm is but the wrapper and envelope of the storm;
+and contains it in itself, as the seemingly harmless rifle holds the
+fatal powder, and the ball, and the explosion; so the graceful repose
+of the line, as it silently serpentines about the oarsmen before
+being brought into actual play--this is a thing which carries more of
+true terror than any other aspect of this dangerous affair. But why
+say more? All men live enveloped in whale-lines. All are born with
+halters round their necks; but it is only when caught in the swift,
+sudden turn of death, that mortals realize the silent, subtle,
+ever-present perils of life. And if you be a philosopher, though
+seated in the whale-boat, you would not at heart feel one whit more
+of terror, than though seated before your evening fire with a poker,
+and not a harpoon, by your side.
+
+
+
+CHAPTER 61
+
+Stubb Kills a Whale.
+
+
+If to Starbuck the apparition of the Squid was a thing of portents,
+to Queequeg it was quite a different object.
+
+"When you see him 'quid," said the savage, honing his harpoon in the
+bow of his hoisted boat, "then you quick see him 'parm whale."
+
+The next day was exceedingly still and sultry, and with nothing
+special to engage them, the Pequod's crew could hardly resist the
+spell of sleep induced by such a vacant sea. For this part of the
+Indian Ocean through which we then were voyaging is not what whalemen
+call a lively ground; that is, it affords fewer glimpses of
+porpoises, dolphins, flying-fish, and other vivacious denizens of
+more stirring waters, than those off the Rio de la Plata, or the
+in-shore ground off Peru.
+
+It was my turn to stand at the foremast-head; and with my shoulders
+leaning against the slackened royal shrouds, to and fro I idly swayed
+in what seemed an enchanted air. No resolution could withstand it;
+in that dreamy mood losing all consciousness, at last my soul went
+out of my body; though my body still continued to sway as a pendulum
+will, long after the power which first moved it is withdrawn.
+
+Ere forgetfulness altogether came over me, I had noticed that the
+seamen at the main and mizzen-mast-heads were already drowsy. So
+that at last all three of us lifelessly swung from the spars, and for
+every swing that we made there was a nod from below from the
+slumbering helmsman. The waves, too, nodded their indolent crests;
+and across the wide trance of the sea, east nodded to west, and the
+sun over all.
+
+Suddenly bubbles seemed bursting beneath my closed eyes; like vices
+my hands grasped the shrouds; some invisible, gracious agency
+preserved me; with a shock I came back to life. And lo! close under
+our lee, not forty fathoms off, a gigantic Sperm Whale lay rolling in
+the water like the capsized hull of a frigate, his broad, glossy
+back, of an Ethiopian hue, glistening in the sun's rays like a
+mirror. But lazily undulating in the trough of the sea, and ever and
+anon tranquilly spouting his vapoury jet, the whale looked like a
+portly burgher smoking his pipe of a warm afternoon. But that pipe,
+poor whale, was thy last. As if struck by some enchanter's wand, the
+sleepy ship and every sleeper in it all at once started into
+wakefulness; and more than a score of voices from all parts of the
+vessel, simultaneously with the three notes from aloft, shouted forth
+the accustomed cry, as the great fish slowly and regularly spouted
+the sparkling brine into the air.
+
+"Clear away the boats! Luff!" cried Ahab. And obeying his own
+order, he dashed the helm down before the helmsman could handle the
+spokes.
+
+The sudden exclamations of the crew must have alarmed the whale; and
+ere the boats were down, majestically turning, he swam away to the
+leeward, but with such a steady tranquillity, and making so few
+ripples as he swam, that thinking after all he might not as yet be
+alarmed, Ahab gave orders that not an oar should be used, and no man
+must speak but in whispers. So seated like Ontario Indians on the
+gunwales of the boats, we swiftly but silently paddled along; the
+calm not admitting of the noiseless sails being set. Presently, as
+we thus glided in chase, the monster perpendicularly flitted his tail
+forty feet into the air, and then sank out of sight like a tower
+swallowed up.
+
+"There go flukes!" was the cry, an announcement immediately followed
+by Stubb's producing his match and igniting his pipe, for now a
+respite was granted. After the full interval of his sounding had
+elapsed, the whale rose again, and being now in advance of the
+smoker's boat, and much nearer to it than to any of the others, Stubb
+counted upon the honour of the capture. It was obvious, now, that the
+whale had at length become aware of his pursuers. All silence of
+cautiousness was therefore no longer of use. Paddles were dropped,
+and oars came loudly into play. And still puffing at his pipe, Stubb
+cheered on his crew to the assault.
+
+Yes, a mighty change had come over the fish. All alive to his
+jeopardy, he was going "head out"; that part obliquely projecting
+from the mad yeast which he brewed.*
+
+
+*It will be seen in some other place of what a very light substance
+the entire interior of the sperm whale's enormous head consists.
+Though apparently the most massive, it is by far the most buoyant
+part about him. So that with ease he elevates it in the air, and
+invariably does so when going at his utmost speed. Besides, such is
+the breadth of the upper part of the front of his head, and such the
+tapering cut-water formation of the lower part, that by obliquely
+elevating his head, he thereby may be said to transform himself from
+a bluff-bowed sluggish galliot into a sharppointed New York
+pilot-boat.
+
+
+"Start her, start her, my men! Don't hurry yourselves; take plenty
+of time--but start her; start her like thunder-claps, that's all,"
+cried Stubb, spluttering out the smoke as he spoke. "Start her, now;
+give 'em the long and strong stroke, Tashtego. Start her, Tash, my
+boy--start her, all; but keep cool, keep cool--cucumbers is the
+word--easy, easy--only start her like grim death and grinning devils,
+and raise the buried dead perpendicular out of their graves,
+boys--that's all. Start her!"
+
+"Woo-hoo! Wa-hee!" screamed the Gay-Header in reply, raising some
+old war-whoop to the skies; as every oarsman in the strained boat
+involuntarily bounced forward with the one tremendous leading stroke
+which the eager Indian gave.
+
+But his wild screams were answered by others quite as wild.
+"Kee-hee! Kee-hee!" yelled Daggoo, straining forwards and backwards
+on his seat, like a pacing tiger in his cage.
+
+"Ka-la! Koo-loo!" howled Queequeg, as if smacking his lips over a
+mouthful of Grenadier's steak. And thus with oars and yells the
+keels cut the sea. Meanwhile, Stubb retaining his place in the
+van, still encouraged his men to the onset, all the while puffing the
+smoke from his mouth. Like desperadoes they tugged and they
+strained, till the welcome cry was heard--"Stand up, Tashtego!--give
+it to him!" The harpoon was hurled. "Stern all!" The oarsmen
+backed water; the same moment something went hot and hissing along
+every one of their wrists. It was the magical line. An instant
+before, Stubb had swiftly caught two additional turns with it round
+the loggerhead, whence, by reason of its increased rapid circlings, a
+hempen blue smoke now jetted up and mingled with the steady fumes
+from his pipe. As the line passed round and round the loggerhead; so
+also, just before reaching that point, it blisteringly passed through
+and through both of Stubb's hands, from which the hand-cloths, or
+squares of quilted canvas sometimes worn at these times, had
+accidentally dropped. It was like holding an enemy's sharp two-edged
+sword by the blade, and that enemy all the time striving to wrest it
+out of your clutch.
+
+"Wet the line! wet the line!" cried Stubb to the tub oarsman (him
+seated by the tub) who, snatching off his hat, dashed sea-water into
+it.* More turns were taken, so that the line began holding its place.
+The boat now flew through the boiling water like a shark all fins.
+Stubb and Tashtego here changed places--stem for stern--a staggering
+business truly in that rocking commotion.
+
+
+*Partly to show the indispensableness of this act, it may here be
+stated, that, in the old Dutch fishery, a mop was used to dash the
+running line with water; in many other ships, a wooden piggin, or
+bailer, is set apart for that purpose. Your hat, however, is the
+most convenient.
+
+
+From the vibrating line extending the entire length of the upper part
+of the boat, and from its now being more tight than a harpstring, you
+would have thought the craft had two keels--one cleaving the water,
+the other the air--as the boat churned on through both opposing
+elements at once. A continual cascade played at the bows; a
+ceaseless whirling eddy in her wake; and, at the slightest motion
+from within, even but of a little finger, the vibrating, cracking
+craft canted over her spasmodic gunwale into the sea. Thus they
+rushed; each man with might and main clinging to his seat, to prevent
+being tossed to the foam; and the tall form of Tashtego at the
+steering oar crouching almost double, in order to bring down his
+centre of gravity. Whole Atlantics and Pacifics seemed passed as
+they shot on their way, till at length the whale somewhat slackened
+his flight.
+
+"Haul in--haul in!" cried Stubb to the bowsman! and, facing round
+towards the whale, all hands began pulling the boat up to him, while
+yet the boat was being towed on. Soon ranging up by his flank,
+Stubb, firmly planting his knee in the clumsy cleat, darted dart
+after dart into the flying fish; at the word of command, the boat
+alternately sterning out of the way of the whale's horrible wallow,
+and then ranging up for another fling.
+
+The red tide now poured from all sides of the monster like brooks
+down a hill. His tormented body rolled not in brine but in blood,
+which bubbled and seethed for furlongs behind in their wake. The
+slanting sun playing upon this crimson pond in the sea, sent back
+its reflection into every face, so that they all glowed to each other
+like red men. And all the while, jet after jet of white smoke was
+agonizingly shot from the spiracle of the whale, and vehement puff
+after puff from the mouth of the excited headsman; as at every dart,
+hauling in upon his crooked lance (by the line attached to it), Stubb
+straightened it again and again, by a few rapid blows against the
+gunwale, then again and again sent it into the whale.
+
+"Pull up--pull up!" he now cried to the bowsman, as the waning whale
+relaxed in his wrath. "Pull up!--close to!" and the boat ranged
+along the fish's flank. When reaching far over the bow, Stubb slowly
+churned his long sharp lance into the fish, and kept it there,
+carefully churning and churning, as if cautiously seeking to feel
+after some gold watch that the whale might have swallowed, and which
+he was fearful of breaking ere he could hook it out. But that gold
+watch he sought was the innermost life of the fish. And now it is
+struck; for, starting from his trance into that unspeakable thing
+called his "flurry," the monster horribly wallowed in his blood,
+overwrapped himself in impenetrable, mad, boiling spray, so that the
+imperilled craft, instantly dropping astern, had much ado blindly to
+struggle out from that phrensied twilight into the clear air of the
+day.
+
+And now abating in his flurry, the whale once more rolled out into
+view; surging from side to side; spasmodically dilating and
+contracting his spout-hole, with sharp, cracking, agonized
+respirations. At last, gush after gush of clotted red gore, as if it
+had been the purple lees of red wine, shot into the frighted air; and
+falling back again, ran dripping down his motionless flanks into
+the sea. His heart had burst!
+
+"He's dead, Mr. Stubb," said Daggoo.
+
+"Yes; both pipes smoked out!" and withdrawing his own from his mouth,
+Stubb scattered the dead ashes over the water; and, for a moment,
+stood thoughtfully eyeing the vast corpse he had made.
+
+
+
+CHAPTER 62
+
+The Dart.
+
+
+A word concerning an incident in the last chapter.
+
+According to the invariable usage of the fishery, the whale-boat
+pushes off from the ship, with the headsman or whale-killer as
+temporary steersman, and the harpooneer or whale-fastener pulling the
+foremost oar, the one known as the harpooneer-oar. Now it needs a
+strong, nervous arm to strike the first iron into the fish; for
+often, in what is called a long dart, the heavy implement has to be
+flung to the distance of twenty or thirty feet. But however
+prolonged and exhausting the chase, the harpooneer is expected to
+pull his oar meanwhile to the uttermost; indeed, he is expected to
+set an example of superhuman activity to the rest, not only by
+incredible rowing, but by repeated loud and intrepid exclamations;
+and what it is to keep shouting at the top of one's compass, while
+all the other muscles are strained and half started--what that is
+none know but those who have tried it. For one, I cannot bawl very
+heartily and work very recklessly at one and the same time. In this
+straining, bawling state, then, with his back to the fish, all at
+once the exhausted harpooneer hears the exciting cry--"Stand up, and
+give it to him!" He now has to drop and secure his oar, turn round
+on his centre half way, seize his harpoon from the crotch, and with
+what little strength may remain, he essays to pitch it somehow into
+the whale. No wonder, taking the whole fleet of whalemen in a body,
+that out of fifty fair chances for a dart, not five are successful;
+no wonder that so many hapless harpooneers are madly cursed and
+disrated; no wonder that some of them actually burst their
+blood-vessels in the boat; no wonder that some sperm whalemen are
+absent four years with four barrels; no wonder that to many ship
+owners, whaling is but a losing concern; for it is the harpooneer
+that makes the voyage, and if you take the breath out of his body how
+can you expect to find it there when most wanted!
+
+Again, if the dart be successful, then at the second critical
+instant, that is, when the whale starts to run, the boatheader and
+harpooneer likewise start to running fore and aft, to the imminent
+jeopardy of themselves and every one else. It is then they change
+places; and the headsman, the chief officer of the little craft,
+takes his proper station in the bows of the boat.
+
+Now, I care not who maintains the contrary, but all this is both
+foolish and unnecessary. The headsman should stay in the bows from
+first to last; he should both dart the harpoon and the lance, and no
+rowing whatever should be expected of him, except under circumstances
+obvious to any fisherman. I know that this would sometimes involve a
+slight loss of speed in the chase; but long experience in various
+whalemen of more than one nation has convinced me that in the vast
+majority of failures in the fishery, it has not by any means been so
+much the speed of the whale as the before described exhaustion of the
+harpooneer that has caused them.
+
+To insure the greatest efficiency in the dart, the harpooneers of
+this world must start to their feet from out of idleness, and not
+from out of toil.
+
+
+
+CHAPTER 63
+
+The Crotch.
+
+
+Out of the trunk, the branches grow; out of them, the twigs. So, in
+productive subjects, grow the chapters.
+
+The crotch alluded to on a previous page deserves independent
+mention. It is a notched stick of a peculiar form, some two feet in
+length, which is perpendicularly inserted into the starboard gunwale
+near the bow, for the purpose of furnishing a rest for the wooden
+extremity of the harpoon, whose other naked, barbed end slopingly
+projects from the prow. Thereby the weapon is instantly at hand to
+its hurler, who snatches it up as readily from its rest as a
+backwoodsman swings his rifle from the wall. It is customary to have
+two harpoons reposing in the crotch, respectively called the first
+and second irons.
+
+But these two harpoons, each by its own cord, are both connected with
+the line; the object being this: to dart them both, if possible, one
+instantly after the other into the same whale; so that if, in the
+coming drag, one should draw out, the other may still retain a hold.
+It is a doubling of the chances. But it very often happens that
+owing to the instantaneous, violent, convulsive running of the whale
+upon receiving the first iron, it becomes impossible for the
+harpooneer, however lightning-like in his movements, to pitch the
+second iron into him. Nevertheless, as the second iron is already
+connected with the line, and the line is running, hence that weapon
+must, at all events, be anticipatingly tossed out of the boat,
+somehow and somewhere; else the most terrible jeopardy would involve
+all hands. Tumbled into the water, it accordingly is in such cases;
+the spare coils of box line (mentioned in a preceding chapter) making
+this feat, in most instances, prudently practicable. But this
+critical act is not always unattended with the saddest and most fatal
+casualties.
+
+Furthermore: you must know that when the second iron is thrown
+overboard, it thenceforth becomes a dangling, sharp-edged terror,
+skittishly curvetting about both boat and whale, entangling the
+lines, or cutting them, and making a prodigious sensation in all
+directions. Nor, in general, is it possible to secure it again until
+the whale is fairly captured and a corpse.
+
+Consider, now, how it must be in the case of four boats all engaging
+one unusually strong, active, and knowing whale; when owing to these
+qualities in him, as well as to the thousand concurring accidents of
+such an audacious enterprise, eight or ten loose second irons may be
+simultaneously dangling about him. For, of course, each boat is
+supplied with several harpoons to bend on to the line should the
+first one be ineffectually darted without recovery. All these
+particulars are faithfully narrated here, as they will not fail to
+elucidate several most important, however intricate passages, in
+scenes hereafter to be painted.
+
+
+
+CHAPTER 64
+
+Stubb's Supper.
+
+
+Stubb's whale had been killed some distance from the ship. It was a
+calm; so, forming a tandem of three boats, we commenced the slow
+business of towing the trophy to the Pequod. And now, as we eighteen
+men with our thirty-six arms, and one hundred and eighty thumbs and
+fingers, slowly toiled hour after hour upon that inert, sluggish
+corpse in the sea; and it seemed hardly to budge at all, except at
+long intervals; good evidence was hereby furnished of the
+enormousness of the mass we moved. For, upon the great canal of
+Hang-Ho, or whatever they call it, in China, four or five laborers on
+the foot-path will draw a bulky freighted junk at the rate of a mile
+an hour; but this grand argosy we towed heavily forged along, as if
+laden with pig-lead in bulk.
+
+Darkness came on; but three lights up and down in the Pequod's
+main-rigging dimly guided our way; till drawing nearer we saw Ahab
+dropping one of several more lanterns over the bulwarks. Vacantly
+eyeing the heaving whale for a moment, he issued the usual orders for
+securing it for the night, and then handing his lantern to a seaman,
+went his way into the cabin, and did not come forward again until
+morning.
+
+Though, in overseeing the pursuit of this whale, Captain Ahab had
+evinced his customary activity, to call it so; yet now that the
+creature was dead, some vague dissatisfaction, or impatience, or
+despair, seemed working in him; as if the sight of that dead body
+reminded him that Moby Dick was yet to be slain; and though a
+thousand other whales were brought to his ship, all that would not
+one jot advance his grand, monomaniac object. Very soon you would
+have thought from the sound on the Pequod's decks, that all hands
+were preparing to cast anchor in the deep; for heavy chains are being
+dragged along the deck, and thrust rattling out of the port-holes.
+But by those clanking links, the vast corpse itself, not the ship, is
+to be moored. Tied by the head to the stern, and by the tail to the
+bows, the whale now lies with its black hull close to the vessel's
+and seen through the darkness of the night, which obscured the spars
+and rigging aloft, the two--ship and whale, seemed yoked together
+like colossal bullocks, whereof one reclines while the other remains
+standing.*
+
+
+*A little item may as well be related here. The strongest and most
+reliable hold which the ship has upon the whale when moored
+alongside, is by the flukes or tail; and as from its greater density
+that part is relatively heavier than any other (excepting the
+side-fins), its flexibility even in death, causes it to sink low
+beneath the surface; so that with the hand you cannot get at it from
+the boat, in order to put the chain round it. But this difficulty is
+ingeniously overcome: a small, strong line is prepared with a wooden
+float at its outer end, and a weight in its middle, while the other
+end is secured to the ship. By adroit management the wooden float is
+made to rise on the other side of the mass, so that now having
+girdled the whale, the chain is readily made to follow suit; and
+being slipped along the body, is at last locked fast round the
+smallest part of the tail, at the point of junction with its broad
+flukes or lobes.
+
+
+If moody Ahab was now all quiescence, at least so far as could be
+known on deck, Stubb, his second mate, flushed with conquest,
+betrayed an unusual but still good-natured excitement. Such an
+unwonted bustle was he in that the staid Starbuck, his official
+superior, quietly resigned to him for the time the sole management of
+affairs. One small, helping cause of all this liveliness in Stubb,
+was soon made strangely manifest. Stubb was a high liver; he was
+somewhat intemperately fond of the whale as a flavorish thing to his
+palate.
+
+"A steak, a steak, ere I sleep! You, Daggoo! overboard you go, and
+cut me one from his small!"
+
+Here be it known, that though these wild fishermen do not, as a
+general thing, and according to the great military maxim, make the
+enemy defray the current expenses of the war (at least before
+realizing the proceeds of the voyage), yet now and then you find some
+of these Nantucketers who have a genuine relish for that particular
+part of the Sperm Whale designated by Stubb; comprising the tapering
+extremity of the body.
+
+About midnight that steak was cut and cooked; and lighted by two
+lanterns of sperm oil, Stubb stoutly stood up to his spermaceti
+supper at the capstan-head, as if that capstan were a sideboard. Nor
+was Stubb the only banqueter on whale's flesh that night. Mingling
+their mumblings with his own mastications, thousands on thousands of
+sharks, swarming round the dead leviathan, smackingly feasted on its
+fatness. The few sleepers below in their bunks were often startled
+by the sharp slapping of their tails against the hull, within a few
+inches of the sleepers' hearts. Peering over the side you could just
+see them (as before you heard them) wallowing in the sullen, black
+waters, and turning over on their backs as they scooped out huge
+globular pieces of the whale of the bigness of a human head. This
+particular feat of the shark seems all but miraculous. How at such
+an apparently unassailable surface, they contrive to gouge out such
+symmetrical mouthfuls, remains a part of the universal problem of all
+things. The mark they thus leave on the whale, may best be likened
+to the hollow made by a carpenter in countersinking for a screw.
+
+Though amid all the smoking horror and diabolism of a sea-fight,
+sharks will be seen longingly gazing up to the ship's decks, like
+hungry dogs round a table where red meat is being carved, ready to
+bolt down every killed man that is tossed to them; and though, while
+the valiant butchers over the deck-table are thus cannibally carving
+each other's live meat with carving-knives all gilded and tasselled,
+the sharks, also, with their jewel-hilted mouths, are quarrelsomely
+carving away under the table at the dead meat; and though, were you
+to turn the whole affair upside down, it would still be pretty much
+the same thing, that is to say, a shocking sharkish business enough
+for all parties; and though sharks also are the invariable outriders
+of all slave ships crossing the Atlantic, systematically trotting
+alongside, to be handy in case a parcel is to be carried anywhere, or
+a dead slave to be decently buried; and though one or two other like
+instances might be set down, touching the set terms, places, and
+occasions, when sharks do most socially congregate, and most
+hilariously feast; yet is there no conceivable time or occasion when
+you will find them in such countless numbers, and in gayer or more
+jovial spirits, than around a dead sperm whale, moored by night to a
+whaleship at sea. If you have never seen that sight, then suspend
+your decision about the propriety of devil-worship, and the
+expediency of conciliating the devil.
+
+But, as yet, Stubb heeded not the mumblings of the banquet that was
+going on so nigh him, no more than the sharks heeded the smacking of
+his own epicurean lips.
+
+"Cook, cook!--where's that old Fleece?" he cried at length, widening
+his legs still further, as if to form a more secure base for his
+supper; and, at the same time darting his fork into the dish, as if
+stabbing with his lance; "cook, you cook!--sail this way, cook!"
+
+The old black, not in any very high glee at having been previously
+roused from his warm hammock at a most unseasonable hour, came
+shambling along from his galley, for, like many old blacks, there was
+something the matter with his knee-pans, which he did not keep well
+scoured like his other pans; this old Fleece, as they called him,
+came shuffling and limping along, assisting his step with his tongs,
+which, after a clumsy fashion, were made of straightened iron hoops;
+this old Ebony floundered along, and in obedience to the word of
+command, came to a dead stop on the opposite side of Stubb's
+sideboard; when, with both hands folded before him, and resting on
+his two-legged cane, he bowed his arched back still further over, at
+the same time sideways inclining his head, so as to bring his best
+ear into play.
+
+"Cook," said Stubb, rapidly lifting a rather reddish morsel to his
+mouth, "don't you think this steak is rather overdone? You've been
+beating this steak too much, cook; it's too tender. Don't I always
+say that to be good, a whale-steak must be tough? There are those
+sharks now over the side, don't you see they prefer it tough and
+rare? What a shindy they are kicking up! Cook, go and talk to 'em;
+tell 'em they are welcome to help themselves civilly, and in
+moderation, but they must keep quiet. Blast me, if I can hear my own
+voice. Away, cook, and deliver my message. Here, take this
+lantern," snatching one from his sideboard; "now then, go and preach
+to 'em!"
+
+Sullenly taking the offered lantern, old Fleece limped across the
+deck to the bulwarks; and then, with one hand dropping his light low
+over the sea, so as to get a good view of his congregation, with the
+other hand he solemnly flourished his tongs, and leaning far over the
+side in a mumbling voice began addressing the sharks, while Stubb,
+softly crawling behind, overheard all that was said.
+
+"Fellow-critters: I'se ordered here to say dat you must stop dat dam
+noise dare. You hear? Stop dat dam smackin' ob de lips! Massa
+Stubb say dat you can fill your dam bellies up to de hatchings, but
+by Gor! you must stop dat dam racket!"
+
+"Cook," here interposed Stubb, accompanying the word with a sudden
+slap on the shoulder,--"Cook! why, damn your eyes, you mustn't swear
+that way when you're preaching. That's no way to convert sinners,
+cook!"
+
+"Who dat? Den preach to him yourself," sullenly turning to go.
+
+"No, cook; go on, go on."
+
+"Well, den, Belubed fellow-critters:"-
+
+"Right!" exclaimed Stubb, approvingly, "coax 'em to it; try that,"
+and Fleece continued.
+
+"Do you is all sharks, and by natur wery woracious, yet I zay to you,
+fellow-critters, dat dat woraciousness--'top dat dam slappin' ob de
+tail! How you tink to hear, spose you keep up such a dam slappin'
+and bitin' dare?"
+
+"Cook," cried Stubb, collaring him, "I won't have that swearing.
+Talk to 'em gentlemanly."
+
+Once more the sermon proceeded.
+
+"Your woraciousness, fellow-critters, I don't blame ye so much for;
+dat is natur, and can't be helped; but to gobern dat wicked natur,
+dat is de pint. You is sharks, sartin; but if you gobern de shark in
+you, why den you be angel; for all angel is not'ing more dan de shark
+well goberned. Now, look here, bred'ren, just try wonst to be cibil,
+a helping yourselbs from dat whale. Don't be tearin' de blubber out
+your neighbour's mout, I say. Is not one shark dood right as toder
+to dat whale? And, by Gor, none on you has de right to dat whale;
+dat whale belong to some one else. I know some o' you has berry brig
+mout, brigger dan oders; but den de brig mouts sometimes has de
+small bellies; so dat de brigness of de mout is not to swaller wid,
+but to bit off de blubber for de small fry ob sharks, dat can't get
+into de scrouge to help demselves."
+
+"Well done, old Fleece!" cried Stubb, "that's Christianity; go on."
+
+"No use goin' on; de dam willains will keep a scougin' and slappin'
+each oder, Massa Stubb; dey don't hear one word; no use a-preaching
+to such dam g'uttons as you call 'em, till dare bellies is full, and
+dare bellies is bottomless; and when dey do get 'em full, dey wont
+hear you den; for den dey sink in the sea, go fast to sleep on de
+coral, and can't hear noting at all, no more, for eber and eber."
+
+"Upon my soul, I am about of the same opinion; so give the
+benediction, Fleece, and I'll away to my supper."
+
+Upon this, Fleece, holding both hands over the fishy mob, raised his
+shrill voice, and cried--
+
+"Cussed fellow-critters! Kick up de damndest row as ever you can;
+fill your dam bellies 'till dey bust--and den die."
+
+"Now, cook," said Stubb, resuming his supper at the capstan; "stand
+just where you stood before, there, over against me, and pay
+particular attention."
+
+"All 'dention," said Fleece, again stooping over upon his tongs in
+the desired position.
+
+"Well," said Stubb, helping himself freely meanwhile; "I shall now go
+back to the subject of this steak. In the first place, how old are
+you, cook?"
+
+"What dat do wid de 'teak," said the old black, testily.
+
+"Silence! How old are you, cook?"
+
+"'Bout ninety, dey say," he gloomily muttered.
+
+"And you have lived in this world hard upon one hundred years, cook,
+and don't know yet how to cook a whale-steak?" rapidly bolting
+another mouthful at the last word, so that morsel seemed a
+continuation of the question. "Where were you born, cook?"
+
+"'Hind de hatchway, in ferry-boat, goin' ober de Roanoke."
+
+"Born in a ferry-boat! That's queer, too. But I want to know what
+country you were born in, cook!"
+
+"Didn't I say de Roanoke country?" he cried sharply.
+
+"No, you didn't, cook; but I'll tell you what I'm coming to, cook.
+You must go home and be born over again; you don't know how to cook a
+whale-steak yet."
+
+"Bress my soul, if I cook noder one," he growled, angrily, turning
+round to depart.
+
+"Come back here, cook;--here, hand me those tongs;--now take that bit
+of steak there, and tell me if you think that steak cooked as it
+should be? Take it, I say"--holding the tongs towards him--"take it,
+and taste it."
+
+Faintly smacking his withered lips over it for a moment, the old
+negro muttered, "Best cooked 'teak I eber taste; joosy, berry joosy."
+
+"Cook," said Stubb, squaring himself once more; "do you belong to the
+church?"
+
+"Passed one once in Cape-Down," said the old man sullenly.
+
+"And you have once in your life passed a holy church in Cape-Town,
+where you doubtless overheard a holy parson addressing his hearers as
+his beloved fellow-creatures, have you, cook! And yet you come here,
+and tell me such a dreadful lie as you did just now, eh?" said Stubb.
+"Where do you expect to go to, cook?"
+
+"Go to bed berry soon," he mumbled, half-turning as he spoke.
+
+"Avast! heave to! I mean when you die, cook. It's an awful
+question. Now what's your answer?"
+
+"When dis old brack man dies," said the negro slowly, changing his
+whole air and demeanor, "he hisself won't go nowhere; but some
+bressed angel will come and fetch him."
+
+"Fetch him? How? In a coach and four, as they fetched Elijah? And
+fetch him where?"
+
+"Up dere," said Fleece, holding his tongs straight over his head, and
+keeping it there very solemnly.
+
+"So, then, you expect to go up into our main-top, do you, cook, when
+you are dead? But don't you know the higher you climb, the colder it
+gets? Main-top, eh?"
+
+"Didn't say dat t'all," said Fleece, again in the sulks.
+
+"You said up there, didn't you? and now look yourself, and see where
+your tongs are pointing. But, perhaps you expect to get into heaven
+by crawling through the lubber's hole, cook; but, no, no, cook, you
+don't get there, except you go the regular way, round by the rigging.
+It's a ticklish business, but must be done, or else it's no go. But
+none of us are in heaven yet. Drop your tongs, cook, and hear my
+orders. Do ye hear? Hold your hat in one hand, and clap t'other
+a'top of your heart, when I'm giving my orders, cook. What! that
+your heart, there?--that's your gizzard! Aloft! aloft!--that's
+it--now you have it. Hold it there now, and pay attention."
+
+"All 'dention," said the old black, with both hands placed as
+desired, vainly wriggling his grizzled head, as if to get both ears
+in front at one and the same time.
+
+"Well then, cook, you see this whale-steak of yours was so very bad,
+that I have put it out of sight as soon as possible; you see that,
+don't you? Well, for the future, when you cook another whale-steak
+for my private table here, the capstan, I'll tell you what to do so
+as not to spoil it by overdoing. Hold the steak in one hand, and
+show a live coal to it with the other; that done, dish it; d'ye hear?
+And now to-morrow, cook, when we are cutting in the fish, be sure
+you stand by to get the tips of his fins; have them put in pickle.
+As for the ends of the flukes, have them soused, cook. There, now ye
+may go."
+
+But Fleece had hardly got three paces off, when he was recalled.
+
+"Cook, give me cutlets for supper to-morrow night in the mid-watch.
+D'ye hear? away you sail, then.--Halloa! stop! make a bow before you
+go.--Avast heaving again! Whale-balls for breakfast--don't forget."
+
+"Wish, by gor! whale eat him, 'stead of him eat whale. I'm bressed
+if he ain't more of shark dan Massa Shark hisself," muttered the old
+man, limping away; with which sage ejaculation he went to his
+hammock.
+
+
+
+CHAPTER 65
+
+The Whale as a Dish.
+
+
+That mortal man should feed upon the creature that feeds his lamp,
+and, like Stubb, eat him by his own light, as you may say; this seems
+so outlandish a thing that one must needs go a little into the
+history and philosophy of it.
+
+It is upon record, that three centuries ago the tongue of the Right
+Whale was esteemed a great delicacy in France, and commanded large
+prices there. Also, that in Henry VIIIth's time, a certain cook of
+the court obtained a handsome reward for inventing an admirable sauce
+to be eaten with barbacued porpoises, which, you remember, are a
+species of whale. Porpoises, indeed, are to this day considered fine
+eating. The meat is made into balls about the size of billiard
+balls, and being well seasoned and spiced might be taken for
+turtle-balls or veal balls. The old monks of Dunfermline were very
+fond of them. They had a great porpoise grant from the crown.
+
+The fact is, that among his hunters at least, the whale would by all
+hands be considered a noble dish, were there not so much of him; but
+when you come to sit down before a meat-pie nearly one hundred feet
+long, it takes away your appetite. Only the most unprejudiced of men
+like Stubb, nowadays partake of cooked whales; but the Esquimaux are
+not so fastidious. We all know how they live upon whales, and have
+rare old vintages of prime old train oil. Zogranda, one of their
+most famous doctors, recommends strips of blubber for infants, as
+being exceedingly juicy and nourishing. And this reminds me that
+certain Englishmen, who long ago were accidentally left in Greenland
+by a whaling vessel--that these men actually lived for several months
+on the mouldy scraps of whales which had been left ashore after
+trying out the blubber. Among the Dutch whalemen these scraps are
+called "fritters"; which, indeed, they greatly resemble, being brown
+and crisp, and smelling something like old Amsterdam housewives'
+dough-nuts or oly-cooks, when fresh. They have such an eatable look
+that the most self-denying stranger can hardly keep his hands off.
+
+But what further depreciates the whale as a civilized dish, is his
+exceeding richness. He is the great prize ox of the sea, too fat to
+be delicately good. Look at his hump, which would be as fine eating
+as the buffalo's (which is esteemed a rare dish), were it not such a
+solid pyramid of fat. But the spermaceti itself, how bland and
+creamy that is; like the transparent, half-jellied, white meat of a
+cocoanut in the third month of its growth, yet far too rich to supply
+a substitute for butter. Nevertheless, many whalemen have a method
+of absorbing it into some other substance, and then partaking of it.
+In the long try watches of the night it is a common thing for the
+seamen to dip their ship-biscuit into the huge oil-pots and let them
+fry there awhile. Many a good supper have I thus made.
+
+In the case of a small Sperm Whale the brains are accounted a fine
+dish. The casket of the skull is broken into with an axe, and the
+two plump, whitish lobes being withdrawn (precisely resembling two
+large puddings), they are then mixed with flour, and cooked into a
+most delectable mess, in flavor somewhat resembling calves' head,
+which is quite a dish among some epicures; and every one knows that
+some young bucks among the epicures, by continually dining upon
+calves' brains, by and by get to have a little brains of their own,
+so as to be able to tell a calf's head from their own heads; which,
+indeed, requires uncommon discrimination. And that is the reason why
+a young buck with an intelligent looking calf's head before him, is
+somehow one of the saddest sights you can see. The head looks a sort
+of reproachfully at him, with an "Et tu Brute!" expression.
+
+It is not, perhaps, entirely because the whale is so excessively
+unctuous that landsmen seem to regard the eating of him with
+abhorrence; that appears to result, in some way, from the
+consideration before mentioned: i.e. that a man should eat a newly
+murdered thing of the sea, and eat it too by its own light. But no
+doubt the first man that ever murdered an ox was regarded as a
+murderer; perhaps he was hung; and if he had been put on his trial by
+oxen, he certainly would have been; and he certainly deserved it if
+any murderer does. Go to the meat-market of a Saturday night and see
+the crowds of live bipeds staring up at the long rows of dead
+quadrupeds. Does not that sight take a tooth out of the cannibal's
+jaw? Cannibals? who is not a cannibal? I tell you it will be more
+tolerable for the Fejee that salted down a lean missionary in his
+cellar against a coming famine; it will be more tolerable for that
+provident Fejee, I say, in the day of judgment, than for thee,
+civilized and enlightened gourmand, who nailest geese to the ground
+and feastest on their bloated livers in thy pate-de-foie-gras.
+
+But Stubb, he eats the whale by its own light, does he? and that is
+adding insult to injury, is it? Look at your knife-handle, there, my
+civilized and enlightened gourmand dining off that roast beef, what
+is that handle made of?--what but the bones of the brother of the
+very ox you are eating? And what do you pick your teeth with, after
+devouring that fat goose? With a feather of the same fowl. And with
+what quill did the Secretary of the Society for the Suppression of
+Cruelty to Ganders formally indite his circulars? It is only within
+the last month or two that that society passed a resolution to
+patronise nothing but steel pens.
+
+
+
+CHAPTER 66
+
+The Shark Massacre.
+
+
+When in the Southern Fishery, a captured Sperm Whale, after long and
+weary toil, is brought alongside late at night, it is not, as a
+general thing at least, customary to proceed at once to the business
+of cutting him in. For that business is an exceedingly laborious
+one; is not very soon completed; and requires all hands to set about
+it. Therefore, the common usage is to take in all sail; lash the
+helm a'lee; and then send every one below to his hammock till
+daylight, with the reservation that, until that time, anchor-watches
+shall be kept; that is, two and two for an hour, each couple, the
+crew in rotation shall mount the deck to see that all goes well.
+
+But sometimes, especially upon the Line in the Pacific, this plan
+will not answer at all; because such incalculable hosts of sharks
+gather round the moored carcase, that were he left so for six hours,
+say, on a stretch, little more than the skeleton would be visible by
+morning. In most other parts of the ocean, however, where these fish
+do not so largely abound, their wondrous voracity can be at times
+considerably diminished, by vigorously stirring them up with sharp
+whaling-spades, a procedure notwithstanding, which, in some
+instances, only seems to tickle them into still greater activity.
+But it was not thus in the present case with the Pequod's sharks;
+though, to be sure, any man unaccustomed to such sights, to have
+looked over her side that night, would have almost thought the whole
+round sea was one huge cheese, and those sharks the maggots in it.
+
+Nevertheless, upon Stubb setting the anchor-watch after his supper
+was concluded; and when, accordingly, Queequeg and a forecastle
+seaman came on deck, no small excitement was created among the
+sharks; for immediately suspending the cutting stages over the side,
+and lowering three lanterns, so that they cast long gleams of light
+over the turbid sea, these two mariners, darting their long
+whaling-spades, kept up an incessant murdering of the sharks,* by
+striking the keen steel deep into their skulls, seemingly their only
+vital part. But in the foamy confusion of their mixed and struggling
+hosts, the marksmen could not always hit their mark; and this brought
+about new revelations of the incredible ferocity of the foe. They
+viciously snapped, not only at each other's disembowelments, but like
+flexible bows, bent round, and bit their own; till those entrails
+seemed swallowed over and over again by the same mouth, to be
+oppositely voided by the gaping wound. Nor was this all. It was
+unsafe to meddle with the corpses and ghosts of these creatures. A
+sort of generic or Pantheistic vitality seemed to lurk in their very
+joints and bones, after what might be called the individual life had
+departed. Killed and hoisted on deck for the sake of his skin, one
+of these sharks almost took poor Queequeg's hand off, when he tried
+to shut down the dead lid of his murderous jaw.
+
+
+*The whaling-spade used for cutting-in is made of the very best
+steel; is about the bigness of a man's spread hand; and in general
+shape, corresponds to the garden implement after which it is named;
+only its sides are perfectly flat, and its upper end considerably
+narrower than the lower. This weapon is always kept as sharp as
+possible; and when being used is occasionally honed, just like a
+razor. In its socket, a stiff pole, from twenty to thirty feet long,
+is inserted for a handle.
+
+
+"Queequeg no care what god made him shark," said the savage,
+agonizingly lifting his hand up and down; "wedder Fejee god or
+Nantucket god; but de god wat made shark must be one dam Ingin."
+
+
+
+CHAPTER 67
+
+Cutting In.
+
+
+It was a Saturday night, and such a Sabbath as followed! Ex officio
+professors of Sabbath breaking are all whalemen. The ivory Pequod
+was turned into what seemed a shamble; every sailor a butcher. You
+would have thought we were offering up ten thousand red oxen to the
+sea gods.
+
+In the first place, the enormous cutting tackles, among other
+ponderous things comprising a cluster of blocks generally painted
+green, and which no single man can possibly lift--this vast bunch of
+grapes was swayed up to the main-top and firmly lashed to the lower
+mast-head, the strongest point anywhere above a ship's deck. The end
+of the hawser-like rope winding through these intricacies, was then
+conducted to the windlass, and the huge lower block of the tackles
+was swung over the whale; to this block the great blubber hook,
+weighing some one hundred pounds, was attached. And now suspended in
+stages over the side, Starbuck and Stubb, the mates, armed with their
+long spades, began cutting a hole in the body for the insertion of
+the hook just above the nearest of the two side-fins. This done, a
+broad, semicircular line is cut round the hole, the hook is inserted,
+and the main body of the crew striking up a wild chorus, now commence
+heaving in one dense crowd at the windlass. When instantly, the
+entire ship careens over on her side; every bolt in her starts like
+the nail-heads of an old house in frosty weather; she trembles,
+quivers, and nods her frighted mast-heads to the sky. More and more
+she leans over to the whale, while every gasping heave of the
+windlass is answered by a helping heave from the billows; till at
+last, a swift, startling snap is heard; with a great swash the ship
+rolls upwards and backwards from the whale, and the triumphant tackle
+rises into sight dragging after it the disengaged semicircular end of
+the first strip of blubber. Now as the blubber envelopes the whale
+precisely as the rind does an orange, so is it stripped off from the
+body precisely as an orange is sometimes stripped by spiralizing it.
+For the strain constantly kept up by the windlass continually keeps
+the whale rolling over and over in the water, and as the blubber in
+one strip uniformly peels off along the line called the "scarf,"
+simultaneously cut by the spades of Starbuck and Stubb, the mates;
+and just as fast as it is thus peeled off, and indeed by that very
+act itself, it is all the time being hoisted higher and higher aloft
+till its upper end grazes the main-top; the men at the windlass then
+cease heaving, and for a moment or two the prodigious blood-dripping
+mass sways to and fro as if let down from the sky, and every one
+present must take good heed to dodge it when it swings, else it may
+box his ears and pitch him headlong overboard.
+
+One of the attending harpooneers now advances with a long, keen
+weapon called a boarding-sword, and watching his chance he
+dexterously slices out a considerable hole in the lower part of the
+swaying mass. Into this hole, the end of the second alternating
+great tackle is then hooked so as to retain a hold upon the blubber,
+in order to prepare for what follows. Whereupon, this accomplished
+swordsman, warning all hands to stand off, once more makes a
+scientific dash at the mass, and with a few sidelong, desperate,
+lunging slicings, severs it completely in twain; so that while the
+short lower part is still fast, the long upper strip, called a
+blanket-piece, swings clear, and is all ready for lowering. The
+heavers forward now resume their song, and while the one tackle is
+peeling and hoisting a second strip from the whale, the other is
+slowly slackened away, and down goes the first strip through the main
+hatchway right beneath, into an unfurnished parlor called the
+blubber-room. Into this twilight apartment sundry nimble hands keep
+coiling away the long blanket-piece as if it were a great live mass
+of plaited serpents. And thus the work proceeds; the two tackles
+hoisting and lowering simultaneously; both whale and windlass
+heaving, the heavers singing, the blubber-room gentlemen coiling, the
+mates scarfing, the ship straining, and all hands swearing
+occasionally, by way of assuaging the general friction.
+
+
+
+CHAPTER 68
+
+The Blanket.
+
+
+I have given no small attention to that not unvexed subject, the skin
+of the whale. I have had controversies about it with experienced
+whalemen afloat, and learned naturalists ashore. My original opinion
+remains unchanged; but it is only an opinion.
+
+The question is, what and where is the skin of the whale? Already
+you know what his blubber is. That blubber is something of the
+consistence of firm, close-grained beef, but tougher, more elastic
+and compact, and ranges from eight or ten to twelve and fifteen
+inches in thickness.
+
+Now, however preposterous it may at first seem to talk of any
+creature's skin as being of that sort of consistence and thickness,
+yet in point of fact these are no arguments against such a
+presumption; because you cannot raise any other dense enveloping
+layer from the whale's body but that same blubber; and the outermost
+enveloping layer of any animal, if reasonably dense, what can that be
+but the skin? True, from the unmarred dead body of the whale, you
+may scrape off with your hand an infinitely thin, transparent
+substance, somewhat resembling the thinnest shreds of isinglass, only
+it is almost as flexible and soft as satin; that is, previous to
+being dried, when it not only contracts and thickens, but becomes
+rather hard and brittle. I have several such dried bits, which I use
+for marks in my whale-books. It is transparent, as I said before;
+and being laid upon the printed page, I have sometimes pleased myself
+with fancying it exerted a magnifying influence. At any rate, it is
+pleasant to read about whales through their own spectacles, as you
+may say. But what I am driving at here is this. That same
+infinitely thin, isinglass substance, which, I admit, invests the
+entire body of the whale, is not so much to be regarded as the skin
+of the creature, as the skin of the skin, so to speak; for it were
+simply ridiculous to say, that the proper skin of the tremendous
+whale is thinner and more tender than the skin of a new-born child.
+But no more of this.
+
+Assuming the blubber to be the skin of the whale; then, when this
+skin, as in the case of a very large Sperm Whale, will yield the bulk
+of one hundred barrels of oil; and, when it is considered that, in
+quantity, or rather weight, that oil, in its expressed state, is only
+three fourths, and not the entire substance of the coat; some idea
+may hence be had of the enormousness of that animated mass, a mere
+part of whose mere integument yields such a lake of liquid as that.
+Reckoning ten barrels to the ton, you have ten tons for the net
+weight of only three quarters of the stuff of the whale's skin.
+
+In life, the visible surface of the Sperm Whale is not the least
+among the many marvels he presents. Almost invariably it is all over
+obliquely crossed and re-crossed with numberless straight marks in
+thick array, something like those in the finest Italian line
+engravings. But these marks do not seem to be impressed upon the
+isinglass substance above mentioned, but seem to be seen through it,
+as if they were engraved upon the body itself. Nor is this all. In
+some instances, to the quick, observant eye, those linear marks, as
+in a veritable engraving, but afford the ground for far other
+delineations. These are hieroglyphical; that is, if you call those
+mysterious cyphers on the walls of pyramids hieroglyphics, then that
+is the proper word to use in the present connexion. By my retentive
+memory of the hieroglyphics upon one Sperm Whale in particular, I was
+much struck with a plate representing the old Indian characters
+chiselled on the famous hieroglyphic palisades on the banks of the
+Upper Mississippi. Like those mystic rocks, too, the mystic-marked
+whale remains undecipherable. This allusion to the Indian rocks
+reminds me of another thing. Besides all the other phenomena which
+the exterior of the Sperm Whale presents, he not seldom displays the
+back, and more especially his flanks, effaced in great part of the
+regular linear appearance, by reason of numerous rude scratches,
+altogether of an irregular, random aspect. I should say that those
+New England rocks on the sea-coast, which Agassiz imagines to bear
+the marks of violent scraping contact with vast floating icebergs--I
+should say, that those rocks must not a little resemble the Sperm
+Whale in this particular. It also seems to me that such scratches in
+the whale are probably made by hostile contact with other whales; for
+I have most remarked them in the large, full-grown bulls of the
+species.
+
+A word or two more concerning this matter of the skin or blubber of
+the whale. It has already been said, that it is stript from him in
+long pieces, called blanket-pieces. Like most sea-terms, this one is
+very happy and significant. For the whale is indeed wrapt up in his
+blubber as in a real blanket or counterpane; or, still better, an
+Indian poncho slipt over his head, and skirting his extremity. It is
+by reason of this cosy blanketing of his body, that the whale is
+enabled to keep himself comfortable in all weathers, in all seas,
+times, and tides. What would become of a Greenland whale, say, in
+those shuddering, icy seas of the North, if unsupplied with his cosy
+surtout? True, other fish are found exceedingly brisk in those
+Hyperborean waters; but these, be it observed, are your cold-blooded,
+lungless fish, whose very bellies are refrigerators; creatures, that
+warm themselves under the lee of an iceberg, as a traveller in winter
+would bask before an inn fire; whereas, like man, the whale has lungs
+and warm blood. Freeze his blood, and he dies. How wonderful is it
+then--except after explanation--that this great monster, to whom
+corporeal warmth is as indispensable as it is to man; how wonderful
+that he should be found at home, immersed to his lips for life in
+those Arctic waters! where, when seamen fall overboard, they are
+sometimes found, months afterwards, perpendicularly frozen into the
+hearts of fields of ice, as a fly is found glued in amber. But more
+surprising is it to know, as has been proved by experiment, that the
+blood of a Polar whale is warmer than that of a Borneo negro in
+summer.
+
+It does seem to me, that herein we see the rare virtue of a strong
+individual vitality, and the rare virtue of thick walls, and the rare
+virtue of interior spaciousness. Oh, man! admire and model thyself
+after the whale! Do thou, too, remain warm among ice. Do thou, too,
+live in this world without being of it. Be cool at the equator; keep
+thy blood fluid at the Pole. Like the great dome of St. Peter's, and
+like the great whale, retain, O man! in all seasons a temperature of
+thine own.
+
+But how easy and how hopeless to teach these fine things! Of
+erections, how few are domed like St. Peter's! of creatures, how few
+vast as the whale!
+
+
+
+CHAPTER 69
+
+The Funeral.
+
+
+Haul in the chains! Let the carcase go astern!
+
+The vast tackles have now done their duty. The peeled white body of
+the beheaded whale flashes like a marble sepulchre; though changed in
+hue, it has not perceptibly lost anything in bulk. It is still
+colossal. Slowly it floats more and more away, the water round it
+torn and splashed by the insatiate sharks, and the air above vexed
+with rapacious flights of screaming fowls, whose beaks are like so
+many insulting poniards in the whale. The vast white headless
+phantom floats further and further from the ship, and every rod that
+it so floats, what seem square roods of sharks and cubic roods of
+fowls, augment the murderous din. For hours and hours from the
+almost stationary ship that hideous sight is seen. Beneath the
+unclouded and mild azure sky, upon the fair face of the pleasant sea,
+wafted by the joyous breezes, that great mass of death floats on and
+on, till lost in infinite perspectives.
+
+There's a most doleful and most mocking funeral! The sea-vultures
+all in pious mourning, the air-sharks all punctiliously in black or
+speckled. In life but few of them would have helped the whale, I
+ween, if peradventure he had needed it; but upon the banquet of his
+funeral they most piously do pounce. Oh, horrible vultureism of
+earth! from which not the mightiest whale is free.
+
+Nor is this the end. Desecrated as the body is, a vengeful ghost
+survives and hovers over it to scare. Espied by some timid
+man-of-war or blundering discovery-vessel from afar, when the
+distance obscuring the swarming fowls, nevertheless still shows the
+white mass floating in the sun, and the white spray heaving high
+against it; straightway the whale's unharming corpse, with trembling
+fingers is set down in the log--SHOALS, ROCKS, AND BREAKERS
+HEREABOUTS: BEWARE! And for years afterwards, perhaps, ships shun
+the place; leaping over it as silly sheep leap over a vacuum, because
+their leader originally leaped there when a stick was held. There's
+your law of precedents; there's your utility of traditions; there's
+the story of your obstinate survival of old beliefs never bottomed on
+the earth, and now not even hovering in the air! There's orthodoxy!
+
+Thus, while in life the great whale's body may have been a real
+terror to his foes, in his death his ghost becomes a powerless panic
+to a world.
+
+Are you a believer in ghosts, my friend? There are other ghosts than
+the Cock-Lane one, and far deeper men than Doctor Johnson who believe
+in them.
+
+
+
+CHAPTER 70
+
+The Sphynx.
+
+
+It should not have been omitted that previous to completely stripping
+the body of the leviathan, he was beheaded. Now, the beheading of
+the Sperm Whale is a scientific anatomical feat, upon which
+experienced whale surgeons very much pride themselves: and not
+without reason.
+
+Consider that the whale has nothing that can properly be called a
+neck; on the contrary, where his head and body seem to join, there,
+in that very place, is the thickest part of him. Remember, also,
+that the surgeon must operate from above, some eight or ten feet
+intervening between him and his subject, and that subject almost
+hidden in a discoloured, rolling, and oftentimes tumultuous and
+bursting sea. Bear in mind, too, that under these untoward
+circumstances he has to cut many feet deep in the flesh; and in that
+subterraneous manner, without so much as getting one single peep into
+the ever-contracting gash thus made, he must skilfully steer clear
+of all adjacent, interdicted parts, and exactly divide the spine at a
+critical point hard by its insertion into the skull. Do you not
+marvel, then, at Stubb's boast, that he demanded but ten minutes to
+behead a sperm whale?
+
+When first severed, the head is dropped astern and held there by a
+cable till the body is stripped. That done, if it belong to a small
+whale it is hoisted on deck to be deliberately disposed of. But,
+with a full grown leviathan this is impossible; for the sperm whale's
+head embraces nearly one third of his entire bulk, and completely to
+suspend such a burden as that, even by the immense tackles of a
+whaler, this were as vain a thing as to attempt weighing a Dutch barn
+in jewellers' scales.
+
+The Pequod's whale being decapitated and the body stripped, the head
+was hoisted against the ship's side--about half way out of the sea,
+so that it might yet in great part be buoyed up by its native
+element. And there with the strained craft steeply leaning over to it,
+by reason of the enormous downward drag from the lower mast-head, and
+every yard-arm on that side projecting like a crane over the waves;
+there, that blood-dripping head hung to the Pequod's waist like the
+giant Holofernes's from the girdle of Judith.
+
+When this last task was accomplished it was noon, and the seamen went
+below to their dinner. Silence reigned over the before tumultuous
+but now deserted deck. An intense copper calm, like a universal
+yellow lotus, was more and more unfolding its noiseless measureless
+leaves upon the sea.
+
+A short space elapsed, and up into this noiselessness came Ahab alone
+from his cabin. Taking a few turns on the quarter-deck, he paused to
+gaze over the side, then slowly getting into the main-chains he took
+Stubb's long spade--still remaining there after the whale's
+Decapitation--and striking it into the lower part of the
+half-suspended mass, placed its other end crutch-wise under one arm,
+and so stood leaning over with eyes attentively fixed on this head.
+
+It was a black and hooded head; and hanging there in the midst of so
+intense a calm, it seemed the Sphynx's in the desert. "Speak, thou
+vast and venerable head," muttered Ahab, "which, though ungarnished
+with a beard, yet here and there lookest hoary with mosses; speak,
+mighty head, and tell us the secret thing that is in thee. Of all
+divers, thou hast dived the deepest. That head upon which the upper
+sun now gleams, has moved amid this world's foundations. Where
+unrecorded names and navies rust, and untold hopes and anchors rot;
+where in her murderous hold this frigate earth is ballasted with
+bones of millions of the drowned; there, in that awful water-land,
+there was thy most familiar home. Thou hast been where bell or diver
+never went; hast slept by many a sailor's side, where sleepless
+mothers would give their lives to lay them down. Thou saw'st the
+locked lovers when leaping from their flaming ship; heart to heart
+they sank beneath the exulting wave; true to each other, when heaven
+seemed false to them. Thou saw'st the murdered mate when tossed by
+pirates from the midnight deck; for hours he fell into the deeper
+midnight of the insatiate maw; and his murderers still sailed on
+unharmed--while swift lightnings shivered the neighboring ship that
+would have borne a righteous husband to outstretched, longing arms.
+O head! thou hast seen enough to split the planets and make an
+infidel of Abraham, and not one syllable is thine!"
+
+"Sail ho!" cried a triumphant voice from the main-mast-head.
+
+"Aye? Well, now, that's cheering," cried Ahab, suddenly erecting
+himself, while whole thunder-clouds swept aside from his brow. "That
+lively cry upon this deadly calm might almost convert a better
+man.--Where away?"
+
+"Three points on the starboard bow, sir, and bringing down her breeze
+to us!
+
+"Better and better, man. Would now St. Paul would come along that
+way, and to my breezelessness bring his breeze! O Nature, and O soul
+of man! how far beyond all utterance are your linked analogies! not
+the smallest atom stirs or lives on matter, but has its cunning
+duplicate in mind."
+
+
+
+CHAPTER 71
+
+The Jeroboam's Story.
+
+
+Hand in hand, ship and breeze blew on; but the breeze came faster
+than the ship, and soon the Pequod began to rock.
+
+By and by, through the glass the stranger's boats and manned
+mast-heads proved her a whale-ship. But as she was so far to
+windward, and shooting by, apparently making a passage to some other
+ground, the Pequod could not hope to reach her. So the signal was
+set to see what response would be made.
+
+Here be it said, that like the vessels of military marines, the ships
+of the American Whale Fleet have each a private signal; all which
+signals being collected in a book with the names of the respective
+vessels attached, every captain is provided with it. Thereby, the
+whale commanders are enabled to recognise each other upon the ocean,
+even at considerable distances and with no small facility.
+
+The Pequod's signal was at last responded to by the stranger's
+setting her own; which proved the ship to be the Jeroboam of
+Nantucket. Squaring her yards, she bore down, ranged abeam under the
+Pequod's lee, and lowered a boat; it soon drew nigh; but, as the
+side-ladder was being rigged by Starbuck's order to accommodate the
+visiting captain, the stranger in question waved his hand from his
+boat's stern in token of that proceeding being entirely unnecessary.
+It turned out that the Jeroboam had a malignant epidemic on board,
+and that Mayhew, her captain, was fearful of infecting the Pequod's
+company. For, though himself and boat's crew remained untainted, and
+though his ship was half a rifle-shot off, and an incorruptible sea
+and air rolling and flowing between; yet conscientiously adhering to
+the timid quarantine of the land, he peremptorily refused to come
+into direct contact with the Pequod.
+
+But this did by no means prevent all communications. Preserving an
+interval of some few yards between itself and the ship, the
+Jeroboam's boat by the occasional use of its oars contrived to keep
+parallel to the Pequod, as she heavily forged through the sea (for by
+this time it blew very fresh), with her main-topsail aback; though,
+indeed, at times by the sudden onset of a large rolling wave, the
+boat would be pushed some way ahead; but would be soon skilfully
+brought to her proper bearings again. Subject to this, and other the
+like interruptions now and then, a conversation was sustained between
+the two parties; but at intervals not without still another
+interruption of a very different sort.
+
+Pulling an oar in the Jeroboam's boat, was a man of a singular
+appearance, even in that wild whaling life where individual
+notabilities make up all totalities. He was a small, short, youngish
+man, sprinkled all over his face with freckles, and wearing redundant
+yellow hair. A long-skirted, cabalistically-cut coat of a faded
+walnut tinge enveloped him; the overlapping sleeves of which were
+rolled up on his wrists. A deep, settled, fanatic delirium was in
+his eyes.
+
+So soon as this figure had been first descried, Stubb had
+exclaimed--"That's he! that's he!--the long-togged scaramouch the
+Town-Ho's company told us of!" Stubb here alluded to a strange story
+told of the Jeroboam, and a certain man among her crew, some time
+previous when the Pequod spoke the Town-Ho. According to this
+account and what was subsequently learned, it seemed that the
+scaramouch in question had gained a wonderful ascendency over almost
+everybody in the Jeroboam. His story was this:
+
+He had been originally nurtured among the crazy society of Neskyeuna
+Shakers, where he had been a great prophet; in their cracked, secret
+meetings having several times descended from heaven by the way of a
+trap-door, announcing the speedy opening of the seventh vial, which
+he carried in his vest-pocket; but, which, instead of containing
+gunpowder, was supposed to be charged with laudanum. A strange,
+apostolic whim having seized him, he had left Neskyeuna for
+Nantucket, where, with that cunning peculiar to craziness, he assumed
+a steady, common-sense exterior, and offered himself as a green-hand
+candidate for the Jeroboam's whaling voyage. They engaged him; but
+straightway upon the ship's getting out of sight of land, his
+insanity broke out in a freshet. He announced himself as the
+archangel Gabriel, and commanded the captain to jump overboard. He
+published his manifesto, whereby he set himself forth as the
+deliverer of the isles of the sea and vicar-general of all Oceanica.
+The unflinching earnestness with which he declared these things;--the
+dark, daring play of his sleepless, excited imagination, and all the
+preternatural terrors of real delirium, united to invest this Gabriel
+in the minds of the majority of the ignorant crew, with an atmosphere
+of sacredness. Moreover, they were afraid of him. As such a man,
+however, was not of much practical use in the ship, especially as he
+refused to work except when he pleased, the incredulous captain would
+fain have been rid of him; but apprised that that individual's
+intention was to land him in the first convenient port, the archangel
+forthwith opened all his seals and vials--devoting the ship and all
+hands to unconditional perdition, in case this intention was carried
+out. So strongly did he work upon his disciples among the crew, that
+at last in a body they went to the captain and told him if Gabriel
+was sent from the ship, not a man of them would remain. He was
+therefore forced to relinquish his plan. Nor would they permit
+Gabriel to be any way maltreated, say or do what he would; so that it
+came to pass that Gabriel had the complete freedom of the ship. The
+consequence of all this was, that the archangel cared little or
+nothing for the captain and mates; and since the epidemic had broken
+out, he carried a higher hand than ever; declaring that the plague,
+as he called it, was at his sole command; nor should it be stayed but
+according to his good pleasure. The sailors, mostly poor devils,
+cringed, and some of them fawned before him; in obedience to his
+instructions, sometimes rendering him personal homage, as to a god.
+Such things may seem incredible; but, however wondrous, they are
+true. Nor is the history of fanatics half so striking in respect to
+the measureless self-deception of the fanatic himself, as his
+measureless power of deceiving and bedevilling so many others. But
+it is time to return to the Pequod.
+
+"I fear not thy epidemic, man," said Ahab from the bulwarks, to
+Captain Mayhew, who stood in the boat's stern; "come on board."
+
+But now Gabriel started to his feet.
+
+"Think, think of the fevers, yellow and bilious! Beware of the
+horrible plague!"
+
+"Gabriel! Gabriel!" cried Captain Mayhew; "thou must either--" But
+that instant a headlong wave shot the boat far ahead, and its
+seethings drowned all speech.
+
+"Hast thou seen the White Whale?" demanded Ahab, when the boat
+drifted back.
+
+"Think, think of thy whale-boat, stoven and sunk! Beware of the
+horrible tail!"
+
+"I tell thee again, Gabriel, that--" But again the boat tore ahead
+as if dragged by fiends. Nothing was said for some moments, while a
+succession of riotous waves rolled by, which by one of those
+occasional caprices of the seas were tumbling, not heaving it.
+Meantime, the hoisted sperm whale's head jogged about very violently,
+and Gabriel was seen eyeing it with rather more apprehensiveness than
+his archangel nature seemed to warrant.
+
+When this interlude was over, Captain Mayhew began a dark story
+concerning Moby Dick; not, however, without frequent interruptions
+from Gabriel, whenever his name was mentioned, and the crazy sea that
+seemed leagued with him.
+
+It seemed that the Jeroboam had not long left home, when upon
+speaking a whale-ship, her people were reliably apprised of the
+existence of Moby Dick, and the havoc he had made. Greedily sucking
+in this intelligence, Gabriel solemnly warned the captain against
+attacking the White Whale, in case the monster should be seen; in his
+gibbering insanity, pronouncing the White Whale to be no less a being
+than the Shaker God incarnated; the Shakers receiving the Bible. But
+when, some year or two afterwards, Moby Dick was fairly sighted from
+the mast-heads, Macey, the chief mate, burned with ardour to encounter
+him; and the captain himself being not unwilling to let him have the
+opportunity, despite all the archangel's denunciations and
+forewarnings, Macey succeeded in persuading five men to man his boat.
+With them he pushed off; and, after much weary pulling, and many
+perilous, unsuccessful onsets, he at last succeeded in getting one
+iron fast. Meantime, Gabriel, ascending to the main-royal mast-head,
+was tossing one arm in frantic gestures, and hurling forth prophecies
+of speedy doom to the sacrilegious assailants of his divinity. Now,
+while Macey, the mate, was standing up in his boat's bow, and with
+all the reckless energy of his tribe was venting his wild
+exclamations upon the whale, and essaying to get a fair chance for
+his poised lance, lo! a broad white shadow rose from the sea; by its
+quick, fanning motion, temporarily taking the breath out of the
+bodies of the oarsmen. Next instant, the luckless mate, so full of
+furious life, was smitten bodily into the air, and making a long arc
+in his descent, fell into the sea at the distance of about fifty
+yards. Not a chip of the boat was harmed, nor a hair of any
+oarsman's head; but the mate for ever sank.
+
+It is well to parenthesize here, that of the fatal accidents in the
+Sperm-Whale Fishery, this kind is perhaps almost as frequent as any.
+Sometimes, nothing is injured but the man who is thus annihilated;
+oftener the boat's bow is knocked off, or the thigh-board, in which
+the headsman stands, is torn from its place and accompanies the body.
+But strangest of all is the circumstance, that in more instances
+than one, when the body has been recovered, not a single mark of
+violence is discernible; the man being stark dead.
+
+The whole calamity, with the falling form of Macey, was plainly
+descried from the ship. Raising a piercing shriek--"The vial! the
+vial!" Gabriel called off the terror-stricken crew from the further
+hunting of the whale. This terrible event clothed the archangel with
+added influence; because his credulous disciples believed that he had
+specifically fore-announced it, instead of only making a general
+prophecy, which any one might have done, and so have chanced to hit
+one of many marks in the wide margin allowed. He became a nameless
+terror to the ship.
+
+Mayhew having concluded his narration, Ahab put such questions to
+him, that the stranger captain could not forbear inquiring whether he
+intended to hunt the White Whale, if opportunity should offer. To
+which Ahab answered--"Aye." Straightway, then, Gabriel once more
+started to his feet, glaring upon the old man, and vehemently
+exclaimed, with downward pointed finger--"Think, think of the
+blasphemer--dead, and down there!--beware of the blasphemer's end!"
+
+Ahab stolidly turned aside; then said to Mayhew, "Captain, I have
+just bethought me of my letter-bag; there is a letter for one of thy
+officers, if I mistake not. Starbuck, look over the bag."
+
+Every whale-ship takes out a goodly number of letters for various
+ships, whose delivery to the persons to whom they may be addressed,
+depends upon the mere chance of encountering them in the four oceans.
+Thus, most letters never reach their mark; and many are only
+received after attaining an age of two or three years or more.
+
+Soon Starbuck returned with a letter in his hand. It was sorely
+tumbled, damp, and covered with a dull, spotted, green mould, in
+consequence of being kept in a dark locker of the cabin. Of such a
+letter, Death himself might well have been the post-boy.
+
+"Can'st not read it?" cried Ahab. "Give it me, man. Aye, aye, it's
+but a dim scrawl;--what's this?" As he was studying it out, Starbuck
+took a long cutting-spade pole, and with his knife slightly split the
+end, to insert the letter there, and in that way, hand it to the
+boat, without its coming any closer to the ship.
+
+Meantime, Ahab holding the letter, muttered, "Mr. Har--yes, Mr.
+Harry--(a woman's pinny hand,--the man's wife, I'll wager)--Aye--Mr.
+Harry Macey, Ship Jeroboam;--why it's Macey, and he's dead!"
+
+"Poor fellow! poor fellow! and from his wife," sighed Mayhew; "but
+let me have it."
+
+"Nay, keep it thyself," cried Gabriel to Ahab; "thou art soon going
+that way."
+
+"Curses throttle thee!" yelled Ahab. "Captain Mayhew, stand by now
+to receive it"; and taking the fatal missive from Starbuck's hands,
+he caught it in the slit of the pole, and reached it over towards the
+boat. But as he did so, the oarsmen expectantly desisted from
+rowing; the boat drifted a little towards the ship's stern; so that,
+as if by magic, the letter suddenly ranged along with Gabriel's eager
+hand. He clutched it in an instant, seized the boat-knife, and
+impaling the letter on it, sent it thus loaded back into the ship.
+It fell at Ahab's feet. Then Gabriel shrieked out to his comrades to
+give way with their oars, and in that manner the mutinous boat
+rapidly shot away from the Pequod.
+
+As, after this interlude, the seamen resumed their work upon the
+jacket of the whale, many strange things were hinted in reference to
+this wild affair.
+
+
+
+CHAPTER 72
+
+The Monkey-Rope.
+
+
+In the tumultuous business of cutting-in and attending to a whale,
+there is much running backwards and forwards among the crew. Now
+hands are wanted here, and then again hands are wanted there. There
+is no staying in any one place; for at one and the same time
+everything has to be done everywhere. It is much the same with him
+who endeavors the description of the scene. We must now retrace our
+way a little. It was mentioned that upon first breaking ground in
+the whale's back, the blubber-hook was inserted into the original
+hole there cut by the spades of the mates. But how did so clumsy and
+weighty a mass as that same hook get fixed in that hole? It was
+inserted there by my particular friend Queequeg, whose duty it was,
+as harpooneer, to descend upon the monster's back for the special
+purpose referred to. But in very many cases, circumstances require
+that the harpooneer shall remain on the whale till the whole tensing
+or stripping operation is concluded. The whale, be it observed, lies
+almost entirely submerged, excepting the immediate parts operated
+upon. So down there, some ten feet below the level of the deck, the
+poor harpooneer flounders about, half on the whale and half in the
+water, as the vast mass revolves like a tread-mill beneath him. On
+the occasion in question, Queequeg figured in the Highland costume--a
+shirt and socks--in which to my eyes, at least, he appeared to
+uncommon advantage; and no one had a better chance to observe him, as
+will presently be seen.
+
+Being the savage's bowsman, that is, the person who pulled the
+bow-oar in his boat (the second one from forward), it was my cheerful
+duty to attend upon him while taking that hard-scrabble scramble upon
+the dead whale's back. You have seen Italian organ-boys holding a
+dancing-ape by a long cord. Just so, from the ship's steep side, did
+I hold Queequeg down there in the sea, by what is technically called
+in the fishery a monkey-rope, attached to a strong strip of canvas
+belted round his waist.
+
+It was a humorously perilous business for both of us. For, before we
+proceed further, it must be said that the monkey-rope was fast at
+both ends; fast to Queequeg's broad canvas belt, and fast to my
+narrow leather one. So that for better or for worse, we two, for the
+time, were wedded; and should poor Queequeg sink to rise no more,
+then both usage and honour demanded, that instead of cutting the cord,
+it should drag me down in his wake. So, then, an elongated Siamese
+ligature united us. Queequeg was my own inseparable twin brother;
+nor could I any way get rid of the dangerous liabilities which the
+hempen bond entailed.
+
+So strongly and metaphysically did I conceive of my situation then,
+that while earnestly watching his motions, I seemed distinctly to
+perceive that my own individuality was now merged in a joint stock
+company of two; that my free will had received a mortal wound; and
+that another's mistake or misfortune might plunge innocent me into
+unmerited disaster and death. Therefore, I saw that here was a sort
+of interregnum in Providence; for its even-handed equity never could
+have so gross an injustice. And yet still further pondering--while I
+jerked him now and then from between the whale and ship, which would
+threaten to jam him--still further pondering, I say, I saw that this
+situation of mine was the precise situation of every mortal that
+breathes; only, in most cases, he, one way or other, has this Siamese
+connexion with a plurality of other mortals. If your banker breaks,
+you snap; if your apothecary by mistake sends you poison in your
+pills, you die. True, you may say that, by exceeding caution, you
+may possibly escape these and the multitudinous other evil chances of
+life. But handle Queequeg's monkey-rope heedfully as I would,
+sometimes he jerked it so, that I came very near sliding overboard.
+Nor could I possibly forget that, do what I would, I only had the
+management of one end of it.*
+
+
+*The monkey-rope is found in all whalers; but it was only in the
+Pequod that the monkey and his holder were ever tied together. This
+improvement upon the original usage was introduced by no less a man
+than Stubb, in order to afford the imperilled harpooneer the strongest
+possible guarantee for the faithfulness and vigilance of his
+monkey-rope holder.
+
+
+I have hinted that I would often jerk poor Queequeg from between the
+whale and the ship--where he would occasionally fall, from the
+incessant rolling and swaying of both. But this was not the only
+jamming jeopardy he was exposed to. Unappalled by the massacre made
+upon them during the night, the sharks now freshly and more keenly
+allured by the before pent blood which began to flow from the
+carcass--the rabid creatures swarmed round it like bees in a beehive.
+
+And right in among those sharks was Queequeg; who often pushed them
+aside with his floundering feet. A thing altogether incredible were
+it not that attracted by such prey as a dead whale, the otherwise
+miscellaneously carnivorous shark will seldom touch a man.
+
+Nevertheless, it may well be believed that since they have such a
+ravenous finger in the pie, it is deemed but wise to look sharp to
+them. Accordingly, besides the monkey-rope, with which I now and
+then jerked the poor fellow from too close a vicinity to the maw of
+what seemed a peculiarly ferocious shark--he was provided with still
+another protection. Suspended over the side in one of the stages,
+Tashtego and Daggoo continually flourished over his head a couple of
+keen whale-spades, wherewith they slaughtered as many sharks as they
+could reach. This procedure of theirs, to be sure, was very
+disinterested and benevolent of them. They meant Queequeg's best
+happiness, I admit; but in their hasty zeal to befriend him, and from
+the circumstance that both he and the sharks were at times half
+hidden by the blood-muddled water, those indiscreet spades of theirs
+would come nearer amputating a leg than a tall. But poor Queequeg, I
+suppose, straining and gasping there with that great iron hook--poor
+Queequeg, I suppose, only prayed to his Yojo, and gave up his life
+into the hands of his gods.
+
+Well, well, my dear comrade and twin-brother, thought I, as I drew in
+and then slacked off the rope to every swell of the sea--what matters
+it, after all? Are you not the precious image of each and all of us
+men in this whaling world? That unsounded ocean you gasp in, is
+Life; those sharks, your foes; those spades, your friends; and what
+between sharks and spades you are in a sad pickle and peril, poor
+lad.
+
+But courage! there is good cheer in store for you, Queequeg. For
+now, as with blue lips and blood-shot eyes the exhausted savage at
+last climbs up the chains and stands all dripping and involuntarily
+trembling over the side; the steward advances, and with a benevolent,
+consolatory glance hands him--what? Some hot Cognac? No! hands him,
+ye gods! hands him a cup of tepid ginger and water!
+
+"Ginger? Do I smell ginger?" suspiciously asked Stubb, coming near.
+"Yes, this must be ginger," peering into the as yet untasted cup.
+Then standing as if incredulous for a while, he calmly walked towards
+the astonished steward slowly saying, "Ginger? ginger? and will you
+have the goodness to tell me, Mr. Dough-Boy, where lies the virtue of
+ginger? Ginger! is ginger the sort of fuel you use, Dough-boy, to
+kindle a fire in this shivering cannibal? Ginger!--what the devil is
+ginger?--sea-coal? firewood?--lucifer
+matches?--tinder?--gunpowder?--what the devil is ginger, I say, that
+you offer this cup to our poor Queequeg here."
+
+"There is some sneaking Temperance Society movement about this
+business," he suddenly added, now approaching Starbuck, who had just
+come from forward. "Will you look at that kannakin, sir; smell of
+it, if you please." Then watching the mate's countenance, he added,
+"The steward, Mr. Starbuck, had the face to offer that calomel and
+jalap to Queequeg, there, this instant off the whale. Is the steward
+an apothecary, sir? and may I ask whether this is the sort of bitters
+by which he blows back the life into a half-drowned man?"
+
+"I trust not," said Starbuck, "it is poor stuff enough."
+
+"Aye, aye, steward," cried Stubb, "we'll teach you to drug it
+harpooneer; none of your apothecary's medicine here; you want to
+poison us, do ye? You have got out insurances on our lives and want
+to murder us all, and pocket the proceeds, do ye?"
+
+"It was not me," cried Dough-Boy, "it was Aunt Charity that brought
+the ginger on board; and bade me never give the harpooneers any
+spirits, but only this ginger-jub--so she called it."
+
+"Ginger-jub! you gingerly rascal! take that! and run along with ye to
+the lockers, and get something better. I hope I do no wrong, Mr.
+Starbuck. It is the captain's orders--grog for the harpooneer on a
+whale."
+
+"Enough," replied Starbuck, "only don't hit him again, but--"
+
+"Oh, I never hurt when I hit, except when I hit a whale or something
+of that sort; and this fellow's a weazel. What were you about
+saying, sir?"
+
+"Only this: go down with him, and get what thou wantest thyself."
+
+When Stubb reappeared, he came with a dark flask in one hand, and a
+sort of tea-caddy in the other. The first contained strong spirits,
+and was handed to Queequeg; the second was Aunt Charity's gift, and
+that was freely given to the waves.
+
+
+
+CHAPTER 73
+
+Stubb and Flask Kill a Right Whale; and Then Have a Talk Over Him.
+
+
+It must be borne in mind that all this time we have a Sperm Whale's
+prodigious head hanging to the Pequod's side. But we must let it
+continue hanging there a while till we can get a chance to attend to
+it. For the present other matters press, and the best we can do now
+for the head, is to pray heaven the tackles may hold.
+
+Now, during the past night and forenoon, the Pequod had gradually
+drifted into a sea, which, by its occasional patches of yellow brit,
+gave unusual tokens of the vicinity of Right Whales, a species of the
+Leviathan that but few supposed to be at this particular time lurking
+anywhere near. And though all hands commonly disdained the capture
+of those inferior creatures; and though the Pequod was not
+commissioned to cruise for them at all, and though she had passed
+numbers of them near the Crozetts without lowering a boat; yet now
+that a Sperm Whale had been brought alongside and beheaded, to the
+surprise of all, the announcement was made that a Right Whale should
+be captured that day, if opportunity offered.
+
+Nor was this long wanting. Tall spouts were seen to leeward; and two
+boats, Stubb's and Flask's, were detached in pursuit. Pulling
+further and further away, they at last became almost invisible to the
+men at the mast-head. But suddenly in the distance, they saw a great
+heap of tumultuous white water, and soon after news came from aloft
+that one or both the boats must be fast. An interval passed and the
+boats were in plain sight, in the act of being dragged right towards
+the ship by the towing whale. So close did the monster come to the
+hull, that at first it seemed as if he meant it malice; but suddenly
+going down in a maelstrom, within three rods of the planks, he wholly
+disappeared from view, as if diving under the keel. "Cut, cut!" was
+the cry from the ship to the boats, which, for one instant, seemed on
+the point of being brought with a deadly dash against the vessel's
+side. But having plenty of line yet in the tubs, and the whale not
+sounding very rapidly, they paid out abundance of rope, and at the
+same time pulled with all their might so as to get ahead of the ship.
+For a few minutes the struggle was intensely critical; for while
+they still slacked out the tightened line in one direction, and still
+plied their oars in another, the contending strain threatened to take
+them under. But it was only a few feet advance they sought to gain.
+And they stuck to it till they did gain it; when instantly, a swift
+tremor was felt running like lightning along the keel, as the
+strained line, scraping beneath the ship, suddenly rose to view under
+her bows, snapping and quivering; and so flinging off its drippings,
+that the drops fell like bits of broken glass on the water, while the
+whale beyond also rose to sight, and once more the boats were free to
+fly. But the fagged whale abated his speed, and blindly altering his
+course, went round the stern of the ship towing the two boats after
+him, so that they performed a complete circuit.
+
+Meantime, they hauled more and more upon their lines, till close
+flanking him on both sides, Stubb answered Flask with lance for
+lance; and thus round and round the Pequod the battle went, while the
+multitudes of sharks that had before swum round the Sperm Whale's
+body, rushed to the fresh blood that was spilled, thirstily drinking
+at every new gash, as the eager Israelites did at the new bursting
+fountains that poured from the smitten rock.
+
+At last his spout grew thick, and with a frightful roll and vomit, he
+turned upon his back a corpse.
+
+While the two headsmen were engaged in making fast cords to his
+flukes, and in other ways getting the mass in readiness for towing,
+some conversation ensued between them.
+
+"I wonder what the old man wants with this lump of foul lard," said
+Stubb, not without some disgust at the thought of having to do with
+so ignoble a leviathan.
+
+"Wants with it?" said Flask, coiling some spare line in the boat's
+bow, "did you never hear that the ship which but once has a Sperm
+Whale's head hoisted on her starboard side, and at the same time a
+Right Whale's on the larboard; did you never hear, Stubb, that that
+ship can never afterwards capsize?"
+
+"Why not?
+
+"I don't know, but I heard that gamboge ghost of a Fedallah saying
+so, and he seems to know all about ships' charms. But I sometimes
+think he'll charm the ship to no good at last. I don't half like
+that chap, Stubb. Did you ever notice how that tusk of his is a sort
+of carved into a snake's head, Stubb?"
+
+"Sink him! I never look at him at all; but if ever I get a chance of
+a dark night, and he standing hard by the bulwarks, and no one by;
+look down there, Flask"--pointing into the sea with a peculiar motion
+of both hands--"Aye, will I! Flask, I take that Fedallah to be the
+devil in disguise. Do you believe that cock and bull story about his
+having been stowed away on board ship? He's the devil, I say. The
+reason why you don't see his tail, is because he tucks it up out of
+sight; he carries it coiled away in his pocket, I guess. Blast him!
+now that I think of it, he's always wanting oakum to stuff into the
+toes of his boots."
+
+"He sleeps in his boots, don't he? He hasn't got any hammock; but
+I've seen him lay of nights in a coil of rigging."
+
+"No doubt, and it's because of his cursed tail; he coils it down, do
+ye see, in the eye of the rigging."
+
+"What's the old man have so much to do with him for?"
+
+"Striking up a swap or a bargain, I suppose."
+
+"Bargain?--about what?"
+
+"Why, do ye see, the old man is hard bent after that White Whale, and
+the devil there is trying to come round him, and get him to swap away
+his silver watch, or his soul, or something of that sort, and then
+he'll surrender Moby Dick."
+
+"Pooh! Stubb, you are skylarking; how can Fedallah do that?"
+
+"I don't know, Flask, but the devil is a curious chap, and a wicked
+one, I tell ye. Why, they say as how he went a sauntering into the
+old flag-ship once, switching his tail about devilish easy and
+gentlemanlike, and inquiring if the old governor was at home. Well,
+he was at home, and asked the devil what he wanted. The devil,
+switching his hoofs, up and says, 'I want John.' 'What for?' says
+the old governor. 'What business is that of yours,' says the devil,
+getting mad,--'I want to use him.' 'Take him,' says the
+governor--and by the Lord, Flask, if the devil didn't give John the
+Asiatic cholera before he got through with him, I'll eat this whale
+in one mouthful. But look sharp--ain't you all ready there? Well,
+then, pull ahead, and let's get the whale alongside."
+
+"I think I remember some such story as you were telling," said Flask,
+when at last the two boats were slowly advancing with their burden
+towards the ship, "but I can't remember where."
+
+"Three Spaniards? Adventures of those three bloody-minded soladoes?
+Did ye read it there, Flask? I guess ye did?"
+
+"No: never saw such a book; heard of it, though. But now, tell me,
+Stubb, do you suppose that that devil you was speaking of just now,
+was the same you say is now on board the Pequod?"
+
+"Am I the same man that helped kill this whale? Doesn't the devil
+live for ever; who ever heard that the devil was dead? Did you ever
+see any parson a wearing mourning for the devil? And if the devil
+has a latch-key to get into the admiral's cabin, don't you suppose he
+can crawl into a porthole? Tell me that, Mr. Flask?"
+
+"How old do you suppose Fedallah is, Stubb?"
+
+"Do you see that mainmast there?" pointing to the ship; "well, that's
+the figure one; now take all the hoops in the Pequod's hold, and
+string along in a row with that mast, for oughts, do you see; well,
+that wouldn't begin to be Fedallah's age. Nor all the coopers in
+creation couldn't show hoops enough to make oughts enough."
+
+"But see here, Stubb, I thought you a little boasted just now, that
+you meant to give Fedallah a sea-toss, if you got a good chance.
+Now, if he's so old as all those hoops of yours come to, and if he is
+going to live for ever, what good will it do to pitch him
+overboard--tell me that?
+
+"Give him a good ducking, anyhow."
+
+"But he'd crawl back."
+
+"Duck him again; and keep ducking him."
+
+"Suppose he should take it into his head to duck you, though--yes,
+and drown you--what then?"
+
+"I should like to see him try it; I'd give him such a pair of black
+eyes that he wouldn't dare to show his face in the admiral's cabin
+again for a long while, let alone down in the orlop there, where he
+lives, and hereabouts on the upper decks where he sneaks so much.
+Damn the devil, Flask; so you suppose I'm afraid of the devil? Who's
+afraid of him, except the old governor who daresn't catch him and put
+him in double-darbies, as he deserves, but lets him go about
+kidnapping people; aye, and signed a bond with him, that all the
+people the devil kidnapped, he'd roast for him? There's a governor!"
+
+"Do you suppose Fedallah wants to kidnap Captain Ahab?"
+
+"Do I suppose it? You'll know it before long, Flask. But I am going
+now to keep a sharp look-out on him; and if I see anything very
+suspicious going on, I'll just take him by the nape of his neck, and
+say--Look here, Beelzebub, you don't do it; and if he makes any fuss,
+by the Lord I'll make a grab into his pocket for his tail, take it to
+the capstan, and give him such a wrenching and heaving, that his tail
+will come short off at the stump--do you see; and then, I rather
+guess when he finds himself docked in that queer fashion, he'll sneak
+off without the poor satisfaction of feeling his tail between his
+legs."
+
+"And what will you do with the tail, Stubb?"
+
+"Do with it? Sell it for an ox whip when we get home;--what else?"
+
+"Now, do you mean what you say, and have been saying all along,
+Stubb?"
+
+"Mean or not mean, here we are at the ship."
+
+The boats were here hailed, to tow the whale on the larboard side,
+where fluke chains and other necessaries were already prepared for
+securing him.
+
+"Didn't I tell you so?" said Flask; "yes, you'll soon see this right
+whale's head hoisted up opposite that parmacetti's."
+
+In good time, Flask's saying proved true. As before, the Pequod
+steeply leaned over towards the sperm whale's head, now, by the
+counterpoise of both heads, she regained her even keel; though sorely
+strained, you may well believe. So, when on one side you hoist in
+Locke's head, you go over that way; but now, on the other side, hoist
+in Kant's and you come back again; but in very poor plight. Thus,
+some minds for ever keep trimming boat. Oh, ye foolish! throw all
+these thunder-heads overboard, and then you will float light and
+right.
+
+In disposing of the body of a right whale, when brought alongside the
+ship, the same preliminary proceedings commonly take place as in the
+case of a sperm whale; only, in the latter instance, the head is cut
+off whole, but in the former the lips and tongue are separately
+removed and hoisted on deck, with all the well known black bone
+attached to what is called the crown-piece. But nothing like this,
+in the present case, had been done. The carcases of both whales had
+dropped astern; and the head-laden ship not a little resembled a mule
+carrying a pair of overburdening panniers.
+
+Meantime, Fedallah was calmly eyeing the right whale's head, and ever
+and anon glancing from the deep wrinkles there to the lines in his
+own hand. And Ahab chanced so to stand, that the Parsee occupied his
+shadow; while, if the Parsee's shadow was there at all it seemed only
+to blend with, and lengthen Ahab's. As the crew toiled on,
+Laplandish speculations were bandied among them, concerning all these
+passing things.
+
+
+
+CHAPTER 74
+
+The Sperm Whale's Head--Contrasted View.
+
+
+Here, now, are two great whales, laying their heads together; let us
+join them, and lay together our own.
+
+Of the grand order of folio leviathans, the Sperm Whale and the Right
+Whale are by far the most noteworthy. They are the only whales
+regularly hunted by man. To the Nantucketer, they present the two
+extremes of all the known varieties of the whale. As the external
+difference between them is mainly observable in their heads; and as a
+head of each is this moment hanging from the Pequod's side; and as we
+may freely go from one to the other, by merely stepping across the
+deck:--where, I should like to know, will you obtain a better chance
+to study practical cetology than here?
+
+In the first place, you are struck by the general contrast between
+these heads. Both are massive enough in all conscience; but there
+is a certain mathematical symmetry in the Sperm Whale's which the
+Right Whale's sadly lacks. There is more character in the Sperm
+Whale's head. As you behold it, you involuntarily yield the immense
+superiority to him, in point of pervading dignity. In the present
+instance, too, this dignity is heightened by the pepper and salt
+colour of his head at the summit, giving token of advanced age and
+large experience. In short, he is what the fishermen technically
+call a "grey-headed whale."
+
+Let us now note what is least dissimilar in these heads--namely, the
+two most important organs, the eye and the ear. Far back on the side
+of the head, and low down, near the angle of either whale's jaw, if
+you narrowly search, you will at last see a lashless eye, which you
+would fancy to be a young colt's eye; so out of all proportion is it
+to the magnitude of the head.
+
+Now, from this peculiar sideway position of the whale's eyes, it is
+plain that he can never see an object which is exactly ahead, no more
+than he can one exactly astern. In a word, the position of the
+whale's eyes corresponds to that of a man's ears; and you may fancy,
+for yourself, how it would fare with you, did you sideways survey
+objects through your ears. You would find that you could only
+command some thirty degrees of vision in advance of the straight
+side-line of sight; and about thirty more behind it. If your
+bitterest foe were walking straight towards you, with dagger uplifted
+in broad day, you would not be able to see him, any more than if he
+were stealing upon you from behind. In a word, you would have two
+backs, so to speak; but, at the same time, also, two fronts (side
+fronts): for what is it that makes the front of a man--what, indeed,
+but his eyes?
+
+Moreover, while in most other animals that I can now think of, the
+eyes are so planted as imperceptibly to blend their visual power, so
+as to produce one picture and not two to the brain; the peculiar
+position of the whale's eyes, effectually divided as they are by many
+cubic feet of solid head, which towers between them like a great
+mountain separating two lakes in valleys; this, of course, must
+wholly separate the impressions which each independent organ imparts.
+The whale, therefore, must see one distinct picture on this side,
+and another distinct picture on that side; while all between must be
+profound darkness and nothingness to him. Man may, in effect, be
+said to look out on the world from a sentry-box with two joined
+sashes for his window. But with the whale, these two sashes are
+separately inserted, making two distinct windows, but sadly impairing
+the view. This peculiarity of the whale's eyes is a thing always to
+be borne in mind in the fishery; and to be remembered by the reader
+in some subsequent scenes.
+
+A curious and most puzzling question might be started concerning this
+visual matter as touching the Leviathan. But I must be content with
+a hint. So long as a man's eyes are open in the light, the act of
+seeing is involuntary; that is, he cannot then help mechanically
+seeing whatever objects are before him. Nevertheless, any one's
+experience will teach him, that though he can take in an
+undiscriminating sweep of things at one glance, it is quite
+impossible for him, attentively, and completely, to examine any two
+things--however large or however small--at one and the same instant
+of time; never mind if they lie side by side and touch each other.
+But if you now come to separate these two objects, and surround each
+by a circle of profound darkness; then, in order to see one of them,
+in such a manner as to bring your mind to bear on it, the other will
+be utterly excluded from your contemporary consciousness. How is it,
+then, with the whale? True, both his eyes, in themselves, must
+simultaneously act; but is his brain so much more comprehensive,
+combining, and subtle than man's, that he can at the same moment of
+time attentively examine two distinct prospects, one on one side of
+him, and the other in an exactly opposite direction? If he can, then
+is it as marvellous a thing in him, as if a man were able
+simultaneously to go through the demonstrations of two distinct
+problems in Euclid. Nor, strictly investigated, is there any
+incongruity in this comparison.
+
+It may be but an idle whim, but it has always seemed to me, that the
+extraordinary vacillations of movement displayed by some whales when
+beset by three or four boats; the timidity and liability to queer
+frights, so common to such whales; I think that all this indirectly
+proceeds from the helpless perplexity of volition, in which their
+divided and diametrically opposite powers of vision must involve
+them.
+
+But the ear of the whale is full as curious as the eye. If you are
+an entire stranger to their race, you might hunt over these two heads
+for hours, and never discover that organ. The ear has no external
+leaf whatever; and into the hole itself you can hardly insert a
+quill, so wondrously minute is it. It is lodged a little behind the
+eye. With respect to their ears, this important difference is to be
+observed between the sperm whale and the right. While the ear of
+the former has an external opening, that of the latter is entirely
+and evenly covered over with a membrane, so as to be quite
+imperceptible from without.
+
+Is it not curious, that so vast a being as the whale should see the
+world through so small an eye, and hear the thunder through an ear
+which is smaller than a hare's? But if his eyes were broad as the
+lens of Herschel's great telescope; and his ears capacious as the
+porches of cathedrals; would that make him any longer of sight, or
+sharper of hearing? Not at all.--Why then do you try to "enlarge"
+your mind? Subtilize it.
+
+Let us now with whatever levers and steam-engines we have at hand,
+cant over the sperm whale's head, that it may lie bottom up;
+then, ascending by a ladder to the summit, have a peep down the
+mouth; and were it not that the body is now completely separated from
+it, with a lantern we might descend into the great Kentucky Mammoth
+Cave of his stomach. But let us hold on here by this tooth, and look
+about us where we are. What a really beautiful and chaste-looking
+mouth! from floor to ceiling, lined, or rather papered with a
+glistening white membrane, glossy as bridal satins.
+
+But come out now, and look at this portentous lower jaw, which seems
+like the long narrow lid of an immense snuff-box, with the hinge at
+one end, instead of one side. If you pry it up, so as to get it
+overhead, and expose its rows of teeth, it seems a terrific
+portcullis; and such, alas! it proves to many a poor wight in the
+fishery, upon whom these spikes fall with impaling force. But far
+more terrible is it to behold, when fathoms down in the sea, you see
+some sulky whale, floating there suspended, with his prodigious jaw,
+some fifteen feet long, hanging straight down at right-angles with
+his body, for all the world like a ship's jib-boom. This whale is
+not dead; he is only dispirited; out of sorts, perhaps;
+hypochondriac; and so supine, that the hinges of his jaw have
+relaxed, leaving him there in that ungainly sort of plight, a
+reproach to all his tribe, who must, no doubt, imprecate lock-jaws
+upon him.
+
+In most cases this lower jaw--being easily unhinged by a practised
+artist--is disengaged and hoisted on deck for the purpose of
+extracting the ivory teeth, and furnishing a supply of that hard
+white whalebone with which the fishermen fashion all sorts of curious
+articles, including canes, umbrella-stocks, and handles to
+riding-whips.
+
+With a long, weary hoist the jaw is dragged on board, as if it were
+an anchor; and when the proper time comes--some few days after the
+other work--Queequeg, Daggoo, and Tashtego, being all accomplished
+dentists, are set to drawing teeth. With a keen cutting-spade,
+Queequeg lances the gums; then the jaw is lashed down to ringbolts,
+and a tackle being rigged from aloft, they drag out these teeth, as
+Michigan oxen drag stumps of old oaks out of wild wood lands. There
+are generally forty-two teeth in all; in old whales, much worn down,
+but undecayed; nor filled after our artificial fashion. The jaw is
+afterwards sawn into slabs, and piled away like joists for building
+houses.
+
+
+
+CHAPTER 75
+
+The Right Whale's Head--Contrasted View.
+
+
+Crossing the deck, let us now have a good long look at the Right
+Whale's head.
+
+As in general shape the noble Sperm Whale's head may be compared to a
+Roman war-chariot (especially in front, where it is so broadly
+rounded); so, at a broad view, the Right Whale's head bears a rather
+inelegant resemblance to a gigantic galliot-toed shoe. Two hundred
+years ago an old Dutch voyager likened its shape to that of a
+shoemaker's last. And in this same last or shoe, that old woman of
+the nursery tale, with the swarming brood, might very comfortably be
+lodged, she and all her progeny.
+
+But as you come nearer to this great head it begins to assume
+different aspects, according to your point of view. If you stand on
+its summit and look at these two F-shaped spoutholes, you would take
+the whole head for an enormous bass-viol, and these spiracles, the
+apertures in its sounding-board. Then, again, if you fix your eye
+upon this strange, crested, comb-like incrustation on the top of the
+mass--this green, barnacled thing, which the Greenlanders call the
+"crown," and the Southern fishers the "bonnet" of the Right Whale;
+fixing your eyes solely on this, you would take the head for the
+trunk of some huge oak, with a bird's nest in its crotch. At any
+rate, when you watch those live crabs that nestle here on this
+bonnet, such an idea will be almost sure to occur to you; unless,
+indeed, your fancy has been fixed by the technical term "crown" also
+bestowed upon it; in which case you will take great interest in
+thinking how this mighty monster is actually a diademed king of the
+sea, whose green crown has been put together for him in this
+marvellous manner. But if this whale be a king, he is a very sulky
+looking fellow to grace a diadem. Look at that hanging lower lip!
+what a huge sulk and pout is there! a sulk and pout, by carpenter's
+measurement, about twenty feet long and five feet deep; a sulk and
+pout that will yield you some 500 gallons of oil and more.
+
+A great pity, now, that this unfortunate whale should be hare-lipped.
+The fissure is about a foot across. Probably the mother during an
+important interval was sailing down the Peruvian coast, when
+earthquakes caused the beach to gape. Over this lip, as over a
+slippery threshold, we now slide into the mouth. Upon my word were I
+at Mackinaw, I should take this to be the inside of an Indian wigwam.
+Good Lord! is this the road that Jonah went? The roof is about
+twelve feet high, and runs to a pretty sharp angle, as if there were
+a regular ridge-pole there; while these ribbed, arched, hairy sides,
+present us with those wondrous, half vertical, scimetar-shaped slats
+of whalebone, say three hundred on a side, which depending from the
+upper part of the head or crown bone, form those Venetian blinds
+which have elsewhere been cursorily mentioned. The edges of these
+bones are fringed with hairy fibres, through which the Right Whale
+strains the water, and in whose intricacies he retains the small
+fish, when openmouthed he goes through the seas of brit in feeding
+time. In the central blinds of bone, as they stand in their natural
+order, there are certain curious marks, curves, hollows, and ridges,
+whereby some whalemen calculate the creature's age, as the age of an
+oak by its circular rings. Though the certainty of this criterion is
+far from demonstrable, yet it has the savor of analogical
+probability. At any rate, if we yield to it, we must grant a far
+greater age to the Right Whale than at first glance will seem
+reasonable.
+
+In old times, there seem to have prevailed the most curious fancies
+concerning these blinds. One voyager in Purchas calls them the
+wondrous "whiskers" inside of the whale's mouth;* another, "hogs'
+bristles"; a third old gentleman in Hackluyt uses the following
+elegant language: "There are about two hundred and fifty fins growing
+on each side of his upper CHOP, which arch over his tongue on each
+side of his mouth."
+
+
+*This reminds us that the Right Whale really has a sort of whisker,
+or rather a moustache, consisting of a few scattered white hairs on
+the upper part of the outer end of the lower jaw. Sometimes these
+tufts impart a rather brigandish expression to his otherwise solemn
+countenance.
+
+
+As every one knows, these same "hogs' bristles," "fins," "whiskers,"
+"blinds," or whatever you please, furnish to the ladies their busks
+and other stiffening contrivances. But in this particular, the
+demand has long been on the decline. It was in Queen Anne's time
+that the bone was in its glory, the farthingale being then all the
+fashion. And as those ancient dames moved about gaily, though in the
+jaws of the whale, as you may say; even so, in a shower, with the
+like thoughtlessness, do we nowadays fly under the same jaws for
+protection; the umbrella being a tent spread over the same bone.
+
+But now forget all about blinds and whiskers for a moment, and,
+standing in the Right Whale's mouth, look around you afresh. Seeing
+all these colonnades of bone so methodically ranged about, would you
+not think you were inside of the great Haarlem organ, and gazing
+upon its thousand pipes? For a carpet to the organ we have a rug of
+the softest Turkey--the tongue, which is glued, as it were, to the
+floor of the mouth. It is very fat and tender, and apt to tear in
+pieces in hoisting it on deck. This particular tongue now before us;
+at a passing glance I should say it was a six-barreler; that is, it
+will yield you about that amount of oil.
+
+Ere this, you must have plainly seen the truth of what I started
+with--that the Sperm Whale and the Right Whale have almost entirely
+different heads. To sum up, then: in the Right Whale's there is no
+great well of sperm; no ivory teeth at all; no long, slender mandible
+of a lower jaw, like the Sperm Whale's. Nor in the Sperm Whale are
+there any of those blinds of bone; no huge lower lip; and scarcely
+anything of a tongue. Again, the Right Whale has two external
+spout-holes, the Sperm Whale only one.
+
+Look your last, now, on these venerable hooded heads, while they yet
+lie together; for one will soon sink, unrecorded, in the sea; the
+other will not be very long in following.
+
+Can you catch the expression of the Sperm Whale's there? It is the
+same he died with, only some of the longer wrinkles in the forehead
+seem now faded away. I think his broad brow to be full of a
+prairie-like placidity, born of a speculative indifference as to
+death. But mark the other head's expression. See that amazing lower
+lip, pressed by accident against the vessel's side, so as firmly to
+embrace the jaw. Does not this whole head seem to speak of an
+enormous practical resolution in facing death? This Right Whale I
+take to have been a Stoic; the Sperm Whale, a Platonian, who might
+have taken up Spinoza in his latter years.
+
+
+
+CHAPTER 76
+
+The Battering-Ram.
+
+
+Ere quitting, for the nonce, the Sperm Whale's head, I would have
+you, as a sensible physiologist, simply--particularly remark its
+front aspect, in all its compacted collectedness. I would have you
+investigate it now with the sole view of forming to yourself some
+unexaggerated, intelligent estimate of whatever battering-ram power
+may be lodged there. Here is a vital point; for you must either
+satisfactorily settle this matter with yourself, or for ever remain
+an infidel as to one of the most appalling, but not the less true
+events, perhaps anywhere to be found in all recorded history.
+
+You observe that in the ordinary swimming position of the Sperm
+Whale, the front of his head presents an almost wholly vertical plane
+to the water; you observe that the lower part of that front slopes
+considerably backwards, so as to furnish more of a retreat for the
+long socket which receives the boom-like lower jaw; you observe that
+the mouth is entirely under the head, much in the same way, indeed,
+as though your own mouth were entirely under your chin. Moreover you
+observe that the whale has no external nose; and that what nose he
+has--his spout hole--is on the top of his head; you observe that his
+eyes and ears are at the sides of his head, nearly one third of his
+entire length from the front. Wherefore, you must now have perceived
+that the front of the Sperm Whale's head is a dead, blind wall,
+without a single organ or tender prominence of any sort whatsoever.
+Furthermore, you are now to consider that only in the extreme, lower,
+backward sloping part of the front of the head, is there the
+slightest vestige of bone; and not till you get near twenty feet from
+the forehead do you come to the full cranial development. So that
+this whole enormous boneless mass is as one wad. Finally, though, as
+will soon be revealed, its contents partly comprise the most delicate
+oil; yet, you are now to be apprised of the nature of the substance
+which so impregnably invests all that apparent effeminacy. In some
+previous place I have described to you how the blubber wraps the body
+of the whale, as the rind wraps an orange. Just so with the head;
+but with this difference: about the head this envelope, though not so
+thick, is of a boneless toughness, inestimable by any man who has not
+handled it. The severest pointed harpoon, the sharpest lance darted
+by the strongest human arm, impotently rebounds from it. It is as
+though the forehead of the Sperm Whale were paved with horses' hoofs.
+I do not think that any sensation lurks in it.
+
+Bethink yourself also of another thing. When two large, loaded
+Indiamen chance to crowd and crush towards each other in the
+docks, what do the sailors do? They do not suspend between them, at
+the point of coming contact, any merely hard substance, like iron or
+wood. No, they hold there a large, round wad of tow and cork,
+enveloped in the thickest and toughest of ox-hide. That bravely and
+uninjured takes the jam which would have snapped all their oaken
+handspikes and iron crow-bars. By itself this sufficiently
+illustrates the obvious fact I drive at. But supplementary to this,
+it has hypothetically occurred to me, that as ordinary fish possess
+what is called a swimming bladder in them, capable, at will, of
+distension or contraction; and as the Sperm Whale, as far as I know,
+has no such provision in him; considering, too, the otherwise
+inexplicable manner in which he now depresses his head altogether
+beneath the surface, and anon swims with it high elevated out of the
+water; considering the unobstructed elasticity of its envelope;
+considering the unique interior of his head; it has hypothetically
+occurred to me, I say, that those mystical lung-celled honeycombs
+there may possibly have some hitherto unknown and unsuspected
+connexion with the outer air, so as to be susceptible to atmospheric
+distension and contraction. If this be so, fancy the
+irresistibleness of that might, to which the most impalpable and
+destructive of all elements contributes.
+
+Now, mark. Unerringly impelling this dead, impregnable, uninjurable
+wall, and this most buoyant thing within; there swims behind it all a
+mass of tremendous life, only to be adequately estimated as piled
+wood is--by the cord; and all obedient to one volition, as the
+smallest insect. So that when I shall hereafter detail to you all
+the specialities and concentrations of potency everywhere lurking in
+this expansive monster; when I shall show you some of his more
+inconsiderable braining feats; I trust you will have renounced all
+ignorant incredulity, and be ready to abide by this; that though the
+Sperm Whale stove a passage through the Isthmus of Darien, and mixed
+the Atlantic with the Pacific, you would not elevate one hair of your
+eye-brow. For unless you own the whale, you are but a provincial and
+sentimentalist in Truth. But clear Truth is a thing for salamander
+giants only to encounter; how small the chances for the provincials
+then? What befell the weakling youth lifting the dread goddess's
+veil at Lais?
+
+
+
+CHAPTER 77
+
+The Great Heidelburgh Tun.
+
+
+Now comes the Baling of the Case. But to comprehend it aright, you
+must know something of the curious internal structure of the thing
+operated upon.
+
+Regarding the Sperm Whale's head as a solid oblong, you may, on an
+inclined plane, sideways divide it into two quoins,* whereof the
+lower is the bony structure, forming the cranium and jaws, and the
+upper an unctuous mass wholly free from bones; its broad forward end
+forming the expanded vertical apparent forehead of the whale. At the
+middle of the forehead horizontally subdivide this upper quoin, and
+then you have two almost equal parts, which before were naturally
+divided by an internal wall of a thick tendinous substance.
+
+
+*Quoin is not a Euclidean term. It belongs to the pure nautical
+mathematics. I know not that it has been defined before. A quoin is
+a solid which differs from a wedge in having its sharp end formed by
+the steep inclination of one side, instead of the mutual tapering of
+both sides.
+
+
+The lower subdivided part, called the junk, is one immense honeycomb
+of oil, formed by the crossing and recrossing, into ten thousand
+infiltrated cells, of tough elastic white fibres throughout its whole
+extent. The upper part, known as the Case, may be regarded as the
+great Heidelburgh Tun of the Sperm Whale. And as that famous great
+tierce is mystically carved in front, so the whale's vast plaited
+forehead forms innumerable strange devices for the emblematical
+adornment of his wondrous tun. Moreover, as that of Heidelburgh was
+always replenished with the most excellent of the wines of the
+Rhenish valleys, so the tun of the whale contains by far the most
+precious of all his oily vintages; namely, the highly-prized
+spermaceti, in its absolutely pure, limpid, and odoriferous state.
+Nor is this precious substance found unalloyed in any other part of
+the creature. Though in life it remains perfectly fluid, yet, upon
+exposure to the air, after death, it soon begins to concrete; sending
+forth beautiful crystalline shoots, as when the first thin delicate
+ice is just forming in water. A large whale's case generally yields
+about five hundred gallons of sperm, though from unavoidable
+circumstances, considerable of it is spilled, leaks, and dribbles
+away, or is otherwise irrevocably lost in the ticklish business of
+securing what you can.
+
+I know not with what fine and costly material the Heidelburgh Tun was
+coated within, but in superlative richness that coating could not
+possibly have compared with the silken pearl-coloured membrane, like
+the lining of a fine pelisse, forming the inner surface of the Sperm
+Whale's case.
+
+It will have been seen that the Heidelburgh Tun of the Sperm Whale
+embraces the entire length of the entire top of the head; and
+since--as has been elsewhere set forth--the head embraces one third
+of the whole length of the creature, then setting that length down at
+eighty feet for a good sized whale, you have more than twenty-six
+feet for the depth of the tun, when it is lengthwise hoisted up and
+down against a ship's side.
+
+As in decapitating the whale, the operator's instrument is brought
+close to the spot where an entrance is subsequently forced into the
+spermaceti magazine; he has, therefore, to be uncommonly heedful,
+lest a careless, untimely stroke should invade the sanctuary and
+wastingly let out its invaluable contents. It is this decapitated
+end of the head, also, which is at last elevated out of the water,
+and retained in that position by the enormous cutting tackles, whose
+hempen combinations, on one side, make quite a wilderness of ropes in
+that quarter.
+
+Thus much being said, attend now, I pray you, to that marvellous
+and--in this particular instance--almost fatal operation whereby the
+Sperm Whale's great Heidelburgh Tun is tapped.
+
+
+
+CHAPTER 78
+
+Cistern and Buckets.
+
+
+Nimble as a cat, Tashtego mounts aloft; and without altering his
+erect posture, runs straight out upon the overhanging mainyard-arm,
+to the part where it exactly projects over the hoisted Tun. He has
+carried with him a light tackle called a whip, consisting of only two
+parts, travelling through a single-sheaved block. Securing this
+block, so that it hangs down from the yard-arm, he swings one end of
+the rope, till it is caught and firmly held by a hand on deck.
+Then, hand-over-hand, down the other part, the Indian drops through
+the air, till dexterously he lands on the summit of the head.
+There--still high elevated above the rest of the company, to whom he
+vivaciously cries--he seems some Turkish Muezzin calling the good
+people to prayers from the top of a tower. A short-handled sharp
+spade being sent up to him, he diligently searches for the proper
+place to begin breaking into the Tun. In this business he proceeds
+very heedfully, like a treasure-hunter in some old house, sounding
+the walls to find where the gold is masoned in. By the time this
+cautious search is over, a stout iron-bound bucket, precisely like a
+well-bucket, has been attached to one end of the whip; while the
+other end, being stretched across the deck, is there held by two or
+three alert hands. These last now hoist the bucket within grasp of
+the Indian, to whom another person has reached up a very long pole.
+Inserting this pole into the bucket, Tashtego downward guides the
+bucket into the Tun, till it entirely disappears; then giving the
+word to the seamen at the whip, up comes the bucket again, all
+bubbling like a dairy-maid's pail of new milk. Carefully lowered
+from its height, the full-freighted vessel is caught by an appointed
+hand, and quickly emptied into a large tub. Then remounting aloft,
+it again goes through the same round until the deep cistern will
+yield no more. Towards the end, Tashtego has to ram his long pole
+harder and harder, and deeper and deeper into the Tun, until some
+twenty feet of the pole have gone down.
+
+Now, the people of the Pequod had been baling some time in this way;
+several tubs had been filled with the fragrant sperm; when all at
+once a queer accident happened. Whether it was that Tashtego, that
+wild Indian, was so heedless and reckless as to let go for a moment
+his one-handed hold on the great cabled tackles suspending the head;
+or whether the place where he stood was so treacherous and oozy; or
+whether the Evil One himself would have it to fall out so, without
+stating his particular reasons; how it was exactly, there is no
+telling now; but, on a sudden, as the eightieth or ninetieth bucket
+came suckingly up--my God! poor Tashtego--like the twin reciprocating
+bucket in a veritable well, dropped head-foremost down into this
+great Tun of Heidelburgh, and with a horrible oily gurgling, went
+clean out of sight!
+
+"Man overboard!" cried Daggoo, who amid the general consternation
+first came to his senses. "Swing the bucket this way!" and putting
+one foot into it, so as the better to secure his slippery hand-hold
+on the whip itself, the hoisters ran him high up to the top of the
+head, almost before Tashtego could have reached its interior bottom.
+Meantime, there was a terrible tumult. Looking over the side, they
+saw the before lifeless head throbbing and heaving just below the
+surface of the sea, as if that moment seized with some momentous
+idea; whereas it was only the poor Indian unconsciously revealing by
+those struggles the perilous depth to which he had sunk.
+
+At this instant, while Daggoo, on the summit of the head, was
+clearing the whip--which had somehow got foul of the great cutting
+tackles--a sharp cracking noise was heard; and to the unspeakable
+horror of all, one of the two enormous hooks suspending the head tore
+out, and with a vast vibration the enormous mass sideways swung, till
+the drunk ship reeled and shook as if smitten by an iceberg. The one
+remaining hook, upon which the entire strain now depended, seemed
+every instant to be on the point of giving way; an event still more
+likely from the violent motions of the head.
+
+"Come down, come down!" yelled the seamen to Daggoo, but with one
+hand holding on to the heavy tackles, so that if the head should
+drop, he would still remain suspended; the negro having cleared the
+foul line, rammed down the bucket into the now collapsed well,
+meaning that the buried harpooneer should grasp it, and so be hoisted
+out.
+
+"In heaven's name, man," cried Stubb, "are you ramming home a
+cartridge there?--Avast! How will that help him; jamming that
+iron-bound bucket on top of his head? Avast, will ye!"
+
+"Stand clear of the tackle!" cried a voice like the bursting of a
+rocket.
+
+Almost in the same instant, with a thunder-boom, the enormous mass
+dropped into the sea, like Niagara's Table-Rock into the whirlpool;
+the suddenly relieved hull rolled away from it, to far down her
+glittering copper; and all caught their breath, as half swinging--now
+over the sailors' heads, and now over the water--Daggoo, through a
+thick mist of spray, was dimly beheld clinging to the pendulous
+tackles, while poor, buried-alive Tashtego was sinking utterly down
+to the bottom of the sea! But hardly had the blinding vapour cleared
+away, when a naked figure with a boarding-sword in his hand, was for
+one swift moment seen hovering over the bulwarks. The next, a loud
+splash announced that my brave Queequeg had dived to the rescue. One
+packed rush was made to the side, and every eye counted every ripple,
+as moment followed moment, and no sign of either the sinker or the
+diver could be seen. Some hands now jumped into a boat alongside,
+and pushed a little off from the ship.
+
+"Ha! ha!" cried Daggoo, all at once, from his now quiet, swinging
+perch overhead; and looking further off from the side, we saw an arm
+thrust upright from the blue waves; a sight strange to see, as an arm
+thrust forth from the grass over a grave.
+
+"Both! both!--it is both!"--cried Daggoo again with a joyful shout;
+and soon after, Queequeg was seen boldly striking out with one hand,
+and with the other clutching the long hair of the Indian. Drawn into
+the waiting boat, they were quickly brought to the deck; but Tashtego
+was long in coming to, and Queequeg did not look very brisk.
+
+Now, how had this noble rescue been accomplished? Why, diving after
+the slowly descending head, Queequeg with his keen sword had made
+side lunges near its bottom, so as to scuttle a large hole there;
+then dropping his sword, had thrust his long arm far inwards and
+upwards, and so hauled out poor Tash by the head. He averred, that
+upon first thrusting in for him, a leg was presented; but well
+knowing that that was not as it ought to be, and might occasion great
+trouble;--he had thrust back the leg, and by a dexterous heave and
+toss, had wrought a somerset upon the Indian; so that with the next
+trial, he came forth in the good old way--head foremost. As for the
+great head itself, that was doing as well as could be expected.
+
+And thus, through the courage and great skill in obstetrics of
+Queequeg, the deliverance, or rather, delivery of Tashtego, was
+successfully accomplished, in the teeth, too, of the most untoward
+and apparently hopeless impediments; which is a lesson by no means to
+be forgotten. Midwifery should be taught in the same course with
+fencing and boxing, riding and rowing.
+
+I know that this queer adventure of the Gay-Header's will be sure to
+seem incredible to some landsmen, though they themselves may have
+either seen or heard of some one's falling into a cistern ashore; an
+accident which not seldom happens, and with much less reason too than
+the Indian's, considering the exceeding slipperiness of the curb of
+the Sperm Whale's well.
+
+But, peradventure, it may be sagaciously urged, how is this? We
+thought the tissued, infiltrated head of the Sperm Whale, was the
+lightest and most corky part about him; and yet thou makest it sink
+in an element of a far greater specific gravity than itself. We have
+thee there. Not at all, but I have ye; for at the time poor Tash
+fell in, the case had been nearly emptied of its lighter contents,
+leaving little but the dense tendinous wall of the well--a double
+welded, hammered substance, as I have before said, much heavier than
+the sea water, and a lump of which sinks in it like lead almost. But
+the tendency to rapid sinking in this substance was in the present
+instance materially counteracted by the other parts of the head
+remaining undetached from it, so that it sank very slowly and
+deliberately indeed, affording Queequeg a fair chance for performing
+his agile obstetrics on the run, as you may say. Yes, it was a
+running delivery, so it was.
+
+Now, had Tashtego perished in that head, it had been a very precious
+perishing; smothered in the very whitest and daintiest of fragrant
+spermaceti; coffined, hearsed, and tombed in the secret inner chamber
+and sanctum sanctorum of the whale. Only one sweeter end can readily
+be recalled--the delicious death of an Ohio honey-hunter, who seeking
+honey in the crotch of a hollow tree, found such exceeding store of
+it, that leaning too far over, it sucked him in, so that he died
+embalmed. How many, think ye, have likewise fallen into Plato's
+honey head, and sweetly perished there?
+
+
+
+CHAPTER 79
+
+The Prairie.
+
+
+To scan the lines of his face, or feel the bumps on the head of this
+Leviathan; this is a thing which no Physiognomist or Phrenologist has
+as yet undertaken. Such an enterprise would seem almost as hopeful
+as for Lavater to have scrutinized the wrinkles on the Rock of
+Gibraltar, or for Gall to have mounted a ladder and manipulated the
+Dome of the Pantheon. Still, in that famous work of his, Lavater
+not only treats of the various faces of men, but also attentively
+studies the faces of horses, birds, serpents, and fish; and dwells in
+detail upon the modifications of expression discernible therein. Nor
+have Gall and his disciple Spurzheim failed to throw out some hints
+touching the phrenological characteristics of other beings than man.
+Therefore, though I am but ill qualified for a pioneer, in the
+application of these two semi-sciences to the whale, I will do my
+endeavor. I try all things; I achieve what I can.
+
+Physiognomically regarded, the Sperm Whale is an anomalous creature.
+He has no proper nose. And since the nose is the central and most
+conspicuous of the features; and since it perhaps most modifies and
+finally controls their combined expression; hence it would seem that
+its entire absence, as an external appendage, must very largely
+affect the countenance of the whale. For as in landscape gardening,
+a spire, cupola, monument, or tower of some sort, is deemed almost
+indispensable to the completion of the scene; so no face can be
+physiognomically in keeping without the elevated open-work belfry of
+the nose. Dash the nose from Phidias's marble Jove, and what a sorry
+remainder! Nevertheless, Leviathan is of so mighty a magnitude, all
+his proportions are so stately, that the same deficiency which in the
+sculptured Jove were hideous, in him is no blemish at all. Nay, it
+is an added grandeur. A nose to the whale would have been
+impertinent. As on your physiognomical voyage you sail round his
+vast head in your jolly-boat, your noble conceptions of him are never
+insulted by the reflection that he has a nose to be pulled. A
+pestilent conceit, which so often will insist upon obtruding even
+when beholding the mightiest royal beadle on his throne.
+
+In some particulars, perhaps the most imposing physiognomical view
+to be had of the Sperm Whale, is that of the full front of his head.
+This aspect is sublime.
+
+In thought, a fine human brow is like the East when troubled with
+the morning. In the repose of the pasture, the curled brow of the
+bull has a touch of the grand in it. Pushing heavy cannon up
+mountain defiles, the elephant's brow is majestic. Human or animal,
+the mystical brow is as that great golden seal affixed by the German
+Emperors to their decrees. It signifies--"God: done this day by my
+hand." But in most creatures, nay in man himself, very often the
+brow is but a mere strip of alpine land lying along the snow line.
+Few are the foreheads which like Shakespeare's or Melancthon's rise
+so high, and descend so low, that the eyes themselves seem clear,
+eternal, tideless mountain lakes; and all above them in the forehead's
+wrinkles, you seem to track the antlered thoughts descending there to
+drink, as the Highland hunters track the snow prints of the deer.
+But in the great Sperm Whale, this high and mighty god-like dignity
+inherent in the brow is so immensely amplified, that gazing on it, in
+that full front view, you feel the Deity and the dread powers more
+forcibly than in beholding any other object in living nature. For
+you see no one point precisely; not one distinct feature is revealed;
+no nose, eyes, ears, or mouth; no face; he has none, proper; nothing
+but that one broad firmament of a forehead, pleated with riddles;
+dumbly lowering with the doom of boats, and ships, and men. Nor, in
+profile, does this wondrous brow diminish; though that way viewed its
+grandeur does not domineer upon you so. In profile, you plainly
+perceive that horizontal, semi-crescentic depression in the
+forehead's middle, which, in man, is Lavater's mark of genius.
+
+But how? Genius in the Sperm Whale? Has the Sperm Whale ever
+written a book, spoken a speech? No, his great genius is declared in
+his doing nothing particular to prove it. It is moreover declared in
+his pyramidical silence. And this reminds me that had the great
+Sperm Whale been known to the young Orient World, he would have been
+deified by their child-magian thoughts. They deified the crocodile
+of the Nile, because the crocodile is tongueless; and the Sperm Whale
+has no tongue, or at least it is so exceedingly small, as to be
+incapable of protrusion. If hereafter any highly cultured, poetical
+nation shall lure back to their birth-right, the merry May-day gods
+of old; and livingly enthrone them again in the now egotistical sky;
+in the now unhaunted hill; then be sure, exalted to Jove's high seat,
+the great Sperm Whale shall lord it.
+
+Champollion deciphered the wrinkled granite hieroglyphics. But there
+is no Champollion to decipher the Egypt of every man's and every
+being's face. Physiognomy, like every other human science, is but a
+passing fable. If then, Sir William Jones, who read in thirty
+languages, could not read the simplest peasant's face in its
+profounder and more subtle meanings, how may unlettered Ishmael hope
+to read the awful Chaldee of the Sperm Whale's brow? I but put that
+brow before you. Read it if you can.
+
+
+
+CHAPTER 80
+
+The Nut.
+
+
+If the Sperm Whale be physiognomically a Sphinx, to the phrenologist
+his brain seems that geometrical circle which it is impossible to
+square.
+
+In the full-grown creature the skull will measure at least twenty
+feet in length. Unhinge the lower jaw, and the side view of this
+skull is as the side of a moderately inclined plane resting
+throughout on a level base. But in life--as we have elsewhere
+seen--this inclined plane is angularly filled up, and almost squared
+by the enormous superincumbent mass of the junk and sperm. At the
+high end the skull forms a crater to bed that part of the mass; while
+under the long floor of this crater--in another cavity seldom
+exceeding ten inches in length and as many in depth--reposes the
+mere handful of this monster's brain. The brain is at least twenty
+feet from his apparent forehead in life; it is hidden away behind its
+vast outworks, like the innermost citadel within the amplified
+fortifications of Quebec. So like a choice casket is it secreted in
+him, that I have known some whalemen who peremptorily deny that the
+Sperm Whale has any other brain than that palpable semblance of one
+formed by the cubic-yards of his sperm magazine. Lying in strange
+folds, courses, and convolutions, to their apprehensions, it seems
+more in keeping with the idea of his general might to regard that
+mystic part of him as the seat of his intelligence.
+
+It is plain, then, that phrenologically the head of this Leviathan,
+in the creature's living intact state, is an entire delusion. As for
+his true brain, you can then see no indications of it, nor feel any.
+The whale, like all things that are mighty, wears a false brow to the
+common world.
+
+If you unload his skull of its spermy heaps and then take a rear view
+of its rear end, which is the high end, you will be struck by its
+resemblance to the human skull, beheld in the same situation, and
+from the same point of view. Indeed, place this reversed skull
+(scaled down to the human magnitude) among a plate of men's skulls,
+and you would involuntarily confound it with them; and remarking the
+depressions on one part of its summit, in phrenological phrase you
+would say--This man had no self-esteem, and no veneration. And by
+those negations, considered along with the affirmative fact of his
+prodigious bulk and power, you can best form to yourself the truest,
+though not the most exhilarating conception of what the most exalted
+potency is.
+
+But if from the comparative dimensions of the whale's proper brain,
+you deem it incapable of being adequately charted, then I have
+another idea for you. If you attentively regard almost any
+quadruped's spine, you will be struck with the resemblance of its
+vertebrae to a strung necklace of dwarfed skulls, all bearing
+rudimental resemblance to the skull proper. It is a German conceit,
+that the vertebrae are absolutely undeveloped skulls. But the
+curious external resemblance, I take it the Germans were not the
+first men to perceive. A foreign friend once pointed it out to me,
+in the skeleton of a foe he had slain, and with the vertebrae of
+which he was inlaying, in a sort of basso-relievo, the beaked prow
+of his canoe. Now, I consider that the phrenologists have omitted an
+important thing in not pushing their investigations from the
+cerebellum through the spinal canal. For I believe that much of a
+man's character will be found betokened in his backbone. I would
+rather feel your spine than your skull, whoever you are. A thin
+joist of a spine never yet upheld a full and noble soul. I rejoice
+in my spine, as in the firm audacious staff of that flag which I
+fling half out to the world.
+
+Apply this spinal branch of phrenology to the Sperm Whale. His
+cranial cavity is continuous with the first neck-vertebra; and in
+that vertebra the bottom of the spinal canal will measure ten inches
+across, being eight in height, and of a triangular figure with the
+base downwards. As it passes through the remaining vertebrae the
+canal tapers in size, but for a considerable distance remains of
+large capacity. Now, of course, this canal is filled with much the
+same strangely fibrous substance--the spinal cord--as the brain; and
+directly communicates with the brain. And what is still more, for
+many feet after emerging from the brain's cavity, the spinal cord
+remains of an undecreasing girth, almost equal to that of the brain.
+Under all these circumstances, would it be unreasonable to survey and
+map out the whale's spine phrenologically? For, viewed in this
+light, the wonderful comparative smallness of his brain proper is
+more than compensated by the wonderful comparative magnitude of his
+spinal cord.
+
+But leaving this hint to operate as it may with the phrenologists, I
+would merely assume the spinal theory for a moment, in reference to
+the Sperm Whale's hump. This august hump, if I mistake not, rises
+over one of the larger vertebrae, and is, therefore, in some sort,
+the outer convex mould of it. From its relative situation then, I
+should call this high hump the organ of firmness or indomitableness
+in the Sperm Whale. And that the great monster is indomitable, you
+will yet have reason to know.
+
+
+
+CHAPTER 81
+
+The Pequod Meets The Virgin.
+
+
+The predestinated day arrived, and we duly met the ship Jungfrau,
+Derick De Deer, master, of Bremen.
+
+At one time the greatest whaling people in the world, the Dutch and
+Germans are now among the least; but here and there at very wide
+intervals of latitude and longitude, you still occasionally meet with
+their flag in the Pacific.
+
+For some reason, the Jungfrau seemed quite eager to pay her respects.
+While yet some distance from the Pequod, she rounded to, and
+dropping a boat, her captain was impelled towards us, impatiently
+standing in the bows instead of the stern.
+
+"What has he in his hand there?" cried Starbuck, pointing to
+something wavingly held by the German. "Impossible!--a lamp-feeder!"
+
+"Not that," said Stubb, "no, no, it's a coffee-pot, Mr. Starbuck;
+he's coming off to make us our coffee, is the Yarman; don't you see
+that big tin can there alongside of him?--that's his boiling water.
+Oh! he's all right, is the Yarman."
+
+"Go along with you," cried Flask, "it's a lamp-feeder and an oil-can.
+He's out of oil, and has come a-begging."
+
+However curious it may seem for an oil-ship to be borrowing oil on
+the whale-ground, and however much it may invertedly contradict the
+old proverb about carrying coals to Newcastle, yet sometimes such a
+thing really happens; and in the present case Captain Derick De Deer
+did indubitably conduct a lamp-feeder as Flask did declare.
+
+As he mounted the deck, Ahab abruptly accosted him, without at all
+heeding what he had in his hand; but in his broken lingo, the German
+soon evinced his complete ignorance of the White Whale; immediately
+turning the conversation to his lamp-feeder and oil can, with some
+remarks touching his having to turn into his hammock at night in
+profound darkness--his last drop of Bremen oil being gone, and not a
+single flying-fish yet captured to supply the deficiency; concluding
+by hinting that his ship was indeed what in the Fishery is
+technically called a CLEAN one (that is, an empty one), well
+deserving the name of Jungfrau or the Virgin.
+
+His necessities supplied, Derick departed; but he had not gained his
+ship's side, when whales were almost simultaneously raised from the
+mast-heads of both vessels; and so eager for the chase was Derick,
+that without pausing to put his oil-can and lamp-feeder aboard, he
+slewed round his boat and made after the leviathan lamp-feeders.
+
+Now, the game having risen to leeward, he and the other three German
+boats that soon followed him, had considerably the start of the
+Pequod's keels. There were eight whales, an average pod. Aware of
+their danger, they were going all abreast with great speed straight
+before the wind, rubbing their flanks as closely as so many spans of
+horses in harness. They left a great, wide wake, as though
+continually unrolling a great wide parchment upon the sea.
+
+Full in this rapid wake, and many fathoms in the rear, swam a huge,
+humped old bull, which by his comparatively slow progress, as well as
+by the unusual yellowish incrustations overgrowing him, seemed
+afflicted with the jaundice, or some other infirmity. Whether this
+whale belonged to the pod in advance, seemed questionable; for it is
+not customary for such venerable leviathans to be at all social.
+Nevertheless, he stuck to their wake, though indeed their back water
+must have retarded him, because the white-bone or swell at his broad
+muzzle was a dashed one, like the swell formed when two hostile
+currents meet. His spout was short, slow, and laborious; coming
+forth with a choking sort of gush, and spending itself in torn
+shreds, followed by strange subterranean commotions in him, which
+seemed to have egress at his other buried extremity, causing the
+waters behind him to upbubble.
+
+"Who's got some paregoric?" said Stubb, "he has the stomach-ache, I'm
+afraid. Lord, think of having half an acre of stomach-ache! Adverse
+winds are holding mad Christmas in him, boys. It's the first foul
+wind I ever knew to blow from astern; but look, did ever whale yaw
+so before? it must be, he's lost his tiller."
+
+As an overladen Indiaman bearing down the Hindostan coast with a deck
+load of frightened horses, careens, buries, rolls, and wallows on her
+way; so did this old whale heave his aged bulk, and now and then
+partly turning over on his cumbrous rib-ends, expose the cause of his
+devious wake in the unnatural stump of his starboard fin. Whether he
+had lost that fin in battle, or had been born without it, it were
+hard to say.
+
+"Only wait a bit, old chap, and I'll give ye a sling for that wounded
+arm," cried cruel Flask, pointing to the whale-line near him.
+
+"Mind he don't sling thee with it," cried Starbuck. "Give way, or
+the German will have him."
+
+With one intent all the combined rival boats were pointed for this
+one fish, because not only was he the largest, and therefore the most
+valuable whale, but he was nearest to them, and the other whales were
+going with such great velocity, moreover, as almost to defy pursuit
+for the time. At this juncture the Pequod's keels had shot by the
+three German boats last lowered; but from the great start he had had,
+Derick's boat still led the chase, though every moment neared by his
+foreign rivals. The only thing they feared, was, that from being
+already so nigh to his mark, he would be enabled to dart his iron
+before they could completely overtake and pass him. As for Derick,
+he seemed quite confident that this would be the case, and
+occasionally with a deriding gesture shook his lamp-feeder at the
+other boats.
+
+"The ungracious and ungrateful dog!" cried Starbuck; "he mocks and
+dares me with the very poor-box I filled for him not five minutes
+ago!"--then in his old intense whisper--"Give way, greyhounds! Dog
+to it!"
+
+"I tell ye what it is, men"--cried Stubb to his crew--"it's against
+my religion to get mad; but I'd like to eat that villainous
+Yarman--Pull--won't ye? Are ye going to let that rascal beat ye? Do
+ye love brandy? A hogshead of brandy, then, to the best man. Come,
+why don't some of ye burst a blood-vessel? Who's that been dropping
+an anchor overboard--we don't budge an inch--we're becalmed. Halloo,
+here's grass growing in the boat's bottom--and by the Lord, the mast
+there's budding. This won't do, boys. Look at that Yarman! The
+short and long of it is, men, will ye spit fire or not?"
+
+"Oh! see the suds he makes!" cried Flask, dancing up and down--"What
+a hump--Oh, DO pile on the beef--lays like a log! Oh! my lads, DO
+spring--slap-jacks and quahogs for supper, you know, my lads--baked
+clams and muffins--oh, DO, DO, spring,--he's a hundred barreller--don't
+lose him now--don't oh, DON'T!--see that Yarman--Oh,
+won't ye pull for your duff, my lads--such a sog! such a sogger!
+Don't ye love sperm? There goes three thousand dollars, men!--a
+bank!--a whole bank! The bank of England!--Oh, DO, DO, DO!--What's
+that Yarman about now?"
+
+At this moment Derick was in the act of pitching his lamp-feeder at
+the advancing boats, and also his oil-can; perhaps with the double
+view of retarding his rivals' way, and at the same time economically
+accelerating his own by the momentary impetus of the backward toss.
+
+"The unmannerly Dutch dogger!" cried Stubb. "Pull now, men, like
+fifty thousand line-of-battle-ship loads of red-haired devils. What
+d'ye say, Tashtego; are you the man to snap your spine in
+two-and-twenty pieces for the honour of old Gayhead? What d'ye say?"
+
+"I say, pull like god-dam,"--cried the Indian.
+
+Fiercely, but evenly incited by the taunts of the German, the
+Pequod's three boats now began ranging almost abreast; and, so
+disposed, momentarily neared him. In that fine, loose, chivalrous
+attitude of the headsman when drawing near to his prey, the three
+mates stood up proudly, occasionally backing the after oarsman with
+an exhilarating cry of, "There she slides, now! Hurrah for the
+white-ash breeze! Down with the Yarman! Sail over him!"
+
+But so decided an original start had Derick had, that spite of all
+their gallantry, he would have proved the victor in this race, had
+not a righteous judgment descended upon him in a crab which caught
+the blade of his midship oarsman. While this clumsy lubber was
+striving to free his white-ash, and while, in consequence, Derick's
+boat was nigh to capsizing, and he thundering away at his men in a
+mighty rage;--that was a good time for Starbuck, Stubb, and Flask.
+With a shout, they took a mortal start forwards, and slantingly
+ranged up on the German's quarter. An instant more, and all four
+boats were diagonically in the whale's immediate wake, while
+stretching from them, on both sides, was the foaming swell that he
+made.
+
+It was a terrific, most pitiable, and maddening sight. The whale was
+now going head out, and sending his spout before him in a continual
+tormented jet; while his one poor fin beat his side in an agony of
+fright. Now to this hand, now to that, he yawed in his faltering
+flight, and still at every billow that he broke, he spasmodically
+sank in the sea, or sideways rolled towards the sky his one beating
+fin. So have I seen a bird with clipped wing making affrighted
+broken circles in the air, vainly striving to escape the piratical
+hawks. But the bird has a voice, and with plaintive cries will make
+known her fear; but the fear of this vast dumb brute of the sea, was
+chained up and enchanted in him; he had no voice, save that choking
+respiration through his spiracle, and this made the sight of him
+unspeakably pitiable; while still, in his amazing bulk, portcullis
+jaw, and omnipotent tail, there was enough to appal the stoutest man
+who so pitied.
+
+Seeing now that but a very few moments more would give the Pequod's
+boats the advantage, and rather than be thus foiled of his game,
+Derick chose to hazard what to him must have seemed a most unusually
+long dart, ere the last chance would for ever escape.
+
+But no sooner did his harpooneer stand up for the stroke, than all
+three tigers--Queequeg, Tashtego, Daggoo--instinctively sprang to
+their feet, and standing in a diagonal row, simultaneously pointed
+their barbs; and darted over the head of the German harpooneer, their
+three Nantucket irons entered the whale. Blinding vapours of foam and
+white-fire! The three boats, in the first fury of the whale's
+headlong rush, bumped the German's aside with such force, that both
+Derick and his baffled harpooneer were spilled out, and sailed over
+by the three flying keels.
+
+"Don't be afraid, my butter-boxes," cried Stubb, casting a passing
+glance upon them as he shot by; "ye'll be picked up presently--all
+right--I saw some sharks astern--St. Bernard's dogs, you
+know--relieve distressed travellers. Hurrah! this is the way to sail
+now. Every keel a sunbeam! Hurrah!--Here we go like three tin
+kettles at the tail of a mad cougar! This puts me in mind of
+fastening to an elephant in a tilbury on a plain--makes the
+wheel-spokes fly, boys, when you fasten to him that way; and there's
+danger of being pitched out too, when you strike a hill. Hurrah!
+this is the way a fellow feels when he's going to Davy Jones--all a
+rush down an endless inclined plane! Hurrah! this whale carries the
+everlasting mail!"
+
+But the monster's run was a brief one. Giving a sudden gasp, he
+tumultuously sounded. With a grating rush, the three lines flew
+round the loggerheads with such a force as to gouge deep grooves in
+them; while so fearful were the harpooneers that this rapid sounding
+would soon exhaust the lines, that using all their dexterous might,
+they caught repeated smoking turns with the rope to hold on; till at
+last--owing to the perpendicular strain from the lead-lined chocks of
+the boats, whence the three ropes went straight down into the
+blue--the gunwales of the bows were almost even with the water, while
+the three sterns tilted high in the air. And the whale soon ceasing
+to sound, for some time they remained in that attitude, fearful of
+expending more line, though the position was a little ticklish. But
+though boats have been taken down and lost in this way, yet it is
+this "holding on," as it is called; this hooking up by the sharp
+barbs of his live flesh from the back; this it is that often torments
+the Leviathan into soon rising again to meet the sharp lance of his
+foes. Yet not to speak of the peril of the thing, it is to be
+doubted whether this course is always the best; for it is but
+reasonable to presume, that the longer the stricken whale stays under
+water, the more he is exhausted. Because, owing to the enormous
+surface of him--in a full grown sperm whale something less than 2000
+square feet--the pressure of the water is immense. We all know what
+an astonishing atmospheric weight we ourselves stand up under; even
+here, above-ground, in the air; how vast, then, the burden of a
+whale, bearing on his back a column of two hundred fathoms of ocean!
+It must at least equal the weight of fifty atmospheres. One whaleman
+has estimated it at the weight of twenty line-of-battle ships, with
+all their guns, and stores, and men on board.
+
+As the three boats lay there on that gently rolling sea, gazing down
+into its eternal blue noon; and as not a single groan or cry of any
+sort, nay, not so much as a ripple or a bubble came up from its
+depths; what landsman would have thought, that beneath all that
+silence and placidity, the utmost monster of the seas was writhing
+and wrenching in agony! Not eight inches of perpendicular rope were
+visible at the bows. Seems it credible that by three such thin
+threads the great Leviathan was suspended like the big weight to an
+eight day clock. Suspended? and to what? To three bits of board.
+Is this the creature of whom it was once so triumphantly said--"Canst
+thou fill his skin with barbed irons? or his head with fish-spears?
+The sword of him that layeth at him cannot hold, the spear, the dart,
+nor the habergeon: he esteemeth iron as straw; the arrow cannot make
+him flee; darts are counted as stubble; he laugheth at the shaking of
+a spear!" This the creature? this he? Oh! that unfulfilments should
+follow the prophets. For with the strength of a thousand thighs in
+his tail, Leviathan had run his head under the mountains of the sea,
+to hide him from the Pequod's fish-spears!
+
+In that sloping afternoon sunlight, the shadows that the three boats
+sent down beneath the surface, must have been long enough and broad
+enough to shade half Xerxes' army. Who can tell how appalling to the
+wounded whale must have been such huge phantoms flitting over his
+head!
+
+"Stand by, men; he stirs," cried Starbuck, as the three lines
+suddenly vibrated in the water, distinctly conducting upwards to
+them, as by magnetic wires, the life and death throbs of the whale,
+so that every oarsman felt them in his seat. The next moment,
+relieved in great part from the downward strain at the bows, the
+boats gave a sudden bounce upwards, as a small icefield will, when a
+dense herd of white bears are scared from it into the sea.
+
+"Haul in! Haul in!" cried Starbuck again; "he's rising."
+
+The lines, of which, hardly an instant before, not one hand's breadth
+could have been gained, were now in long quick coils flung back all
+dripping into the boats, and soon the whale broke water within two
+ship's lengths of the hunters.
+
+His motions plainly denoted his extreme exhaustion. In most land
+animals there are certain valves or flood-gates in many of their
+veins, whereby when wounded, the blood is in some degree at least
+instantly shut off in certain directions. Not so with the whale; one
+of whose peculiarities it is to have an entire non-valvular structure
+of the blood-vessels, so that when pierced even by so small a point
+as a harpoon, a deadly drain is at once begun upon his whole
+arterial system; and when this is heightened by the extraordinary
+pressure of water at a great distance below the surface, his life may
+be said to pour from him in incessant streams. Yet so vast is the
+quantity of blood in him, and so distant and numerous its interior
+fountains, that he will keep thus bleeding and bleeding for a
+considerable period; even as in a drought a river will flow, whose
+source is in the well-springs of far-off and undiscernible hills.
+Even now, when the boats pulled upon this whale, and perilously drew
+over his swaying flukes, and the lances were darted into him, they
+were followed by steady jets from the new made wound, which kept
+continually playing, while the natural spout-hole in his head was
+only at intervals, however rapid, sending its affrighted moisture
+into the air. From this last vent no blood yet came, because no
+vital part of him had thus far been struck. His life, as they
+significantly call it, was untouched.
+
+As the boats now more closely surrounded him, the whole upper part of
+his form, with much of it that is ordinarily submerged, was plainly
+revealed. His eyes, or rather the places where his eyes had been,
+were beheld. As strange misgrown masses gather in the knot-holes of
+the noblest oaks when prostrate, so from the points which the whale's
+eyes had once occupied, now protruded blind bulbs, horribly pitiable
+to see. But pity there was none. For all his old age, and his one
+arm, and his blind eyes, he must die the death and be murdered, in
+order to light the gay bridals and other merry-makings of men, and
+also to illuminate the solemn churches that preach unconditional
+inoffensiveness by all to all. Still rolling in his blood, at last
+he partially disclosed a strangely discoloured bunch or protuberance,
+the size of a bushel, low down on the flank.
+
+"A nice spot," cried Flask; "just let me prick him there once."
+
+"Avast!" cried Starbuck, "there's no need of that!"
+
+But humane Starbuck was too late. At the instant of the dart an
+ulcerous jet shot from this cruel wound, and goaded by it into more
+than sufferable anguish, the whale now spouting thick blood, with
+swift fury blindly darted at the craft, bespattering them and their
+glorying crews all over with showers of gore, capsizing Flask's boat
+and marring the bows. It was his death stroke. For, by this time,
+so spent was he by loss of blood, that he helplessly rolled away from
+the wreck he had made; lay panting on his side, impotently flapped
+with his stumped fin, then over and over slowly revolved like a
+waning world; turned up the white secrets of his belly; lay like a
+log, and died. It was most piteous, that last expiring spout. As
+when by unseen hands the water is gradually drawn off from some
+mighty fountain, and with half-stifled melancholy gurglings the
+spray-column lowers and lowers to the ground--so the last long dying
+spout of the whale.
+
+Soon, while the crews were awaiting the arrival of the ship, the body
+showed symptoms of sinking with all its treasures unrifled.
+Immediately, by Starbuck's orders, lines were secured to it at
+different points, so that ere long every boat was a buoy; the sunken
+whale being suspended a few inches beneath them by the cords. By
+very heedful management, when the ship drew nigh, the whale was
+transferred to her side, and was strongly secured there by the
+stiffest fluke-chains, for it was plain that unless artificially
+upheld, the body would at once sink to the bottom.
+
+It so chanced that almost upon first cutting into him with the
+spade, the entire length of a corroded harpoon was found imbedded in
+his flesh, on the lower part of the bunch before described. But as
+the stumps of harpoons are frequently found in the dead bodies of
+captured whales, with the flesh perfectly healed around them, and no
+prominence of any kind to denote their place; therefore, there must
+needs have been some other unknown reason in the present case fully
+to account for the ulceration alluded to. But still more curious was
+the fact of a lance-head of stone being found in him, not far from
+the buried iron, the flesh perfectly firm about it. Who had darted
+that stone lance? And when? It might have been darted by some Nor'
+West Indian long before America was discovered.
+
+What other marvels might have been rummaged out of this monstrous
+cabinet there is no telling. But a sudden stop was put to further
+discoveries, by the ship's being unprecedentedly dragged over
+sideways to the sea, owing to the body's immensely increasing
+tendency to sink. However, Starbuck, who had the ordering of
+affairs, hung on to it to the last; hung on to it so resolutely,
+indeed, that when at length the ship would have been capsized, if
+still persisting in locking arms with the body; then, when the
+command was given to break clear from it, such was the immovable
+strain upon the timber-heads to which the fluke-chains and cables
+were fastened, that it was impossible to cast them off. Meantime
+everything in the Pequod was aslant. To cross to the other side of
+the deck was like walking up the steep gabled roof of a house. The
+ship groaned and gasped. Many of the ivory inlayings of her bulwarks
+and cabins were started from their places, by the unnatural
+dislocation. In vain handspikes and crows were brought to bear upon
+the immovable fluke-chains, to pry them adrift from the timberheads;
+and so low had the whale now settled that the submerged ends could
+not be at all approached, while every moment whole tons of
+ponderosity seemed added to the sinking bulk, and the ship seemed on
+the point of going over.
+
+"Hold on, hold on, won't ye?" cried Stubb to the body, "don't be in
+such a devil of a hurry to sink! By thunder, men, we must do
+something or go for it. No use prying there; avast, I say with your
+handspikes, and run one of ye for a prayer book and a pen-knife, and
+cut the big chains."
+
+"Knife? Aye, aye," cried Queequeg, and seizing the carpenter's heavy
+hatchet, he leaned out of a porthole, and steel to iron, began
+slashing at the largest fluke-chains. But a few strokes, full of
+sparks, were given, when the exceeding strain effected the rest.
+With a terrific snap, every fastening went adrift; the ship righted,
+the carcase sank.
+
+Now, this occasional inevitable sinking of the recently killed Sperm
+Whale is a very curious thing; nor has any fisherman yet adequately
+accounted for it. Usually the dead Sperm Whale floats with great
+buoyancy, with its side or belly considerably elevated above the
+surface. If the only whales that thus sank were old, meagre, and
+broken-hearted creatures, their pads of lard diminished and all their
+bones heavy and rheumatic; then you might with some reason assert
+that this sinking is caused by an uncommon specific gravity in the
+fish so sinking, consequent upon this absence of buoyant matter in
+him. But it is not so. For young whales, in the highest health, and
+swelling with noble aspirations, prematurely cut off in the warm
+flush and May of life, with all their panting lard about them; even
+these brawny, buoyant heroes do sometimes sink.
+
+Be it said, however, that the Sperm Whale is far less liable to this
+accident than any other species. Where one of that sort go down,
+twenty Right Whales do. This difference in the species is no doubt
+imputable in no small degree to the greater quantity of bone in the
+Right Whale; his Venetian blinds alone sometimes weighing more than a
+ton; from this incumbrance the Sperm Whale is wholly free. But there
+are instances where, after the lapse of many hours or several days,
+the sunken whale again rises, more buoyant than in life. But the
+reason of this is obvious. Gases are generated in him; he swells to
+a prodigious magnitude; becomes a sort of animal balloon. A
+line-of-battle ship could hardly keep him under then. In the Shore
+Whaling, on soundings, among the Bays of New Zealand, when a Right
+Whale gives token of sinking, they fasten buoys to him, with plenty
+of rope; so that when the body has gone down, they know where to look
+for it when it shall have ascended again.
+
+It was not long after the sinking of the body that a cry was heard
+from the Pequod's mast-heads, announcing that the Jungfrau was again
+lowering her boats; though the only spout in sight was that of a
+Fin-Back, belonging to the species of uncapturable whales, because of
+its incredible power of swimming. Nevertheless, the Fin-Back's spout
+is so similar to the Sperm Whale's, that by unskilful fishermen it is
+often mistaken for it. And consequently Derick and all his host were
+now in valiant chase of this unnearable brute. The Virgin crowding
+all sail, made after her four young keels, and thus they all
+disappeared far to leeward, still in bold, hopeful chase.
+
+Oh! many are the Fin-Backs, and many are the Dericks, my friend.
+
+
+
+CHAPTER 82
+
+The Honour and Glory of Whaling.
+
+
+There are some enterprises in which a careful disorderliness is the
+true method.
+
+The more I dive into this matter of whaling, and push my researches
+up to the very spring-head of it so much the more am I impressed with
+its great honourableness and antiquity; and especially when I find so
+many great demi-gods and heroes, prophets of all sorts, who one way
+or other have shed distinction upon it, I am transported with the
+reflection that I myself belong, though but subordinately, to so
+emblazoned a fraternity.
+
+The gallant Perseus, a son of Jupiter, was the first whaleman; and to
+the eternal honour of our calling be it said, that the first whale
+attacked by our brotherhood was not killed with any sordid intent.
+Those were the knightly days of our profession, when we only bore
+arms to succor the distressed, and not to fill men's lamp-feeders.
+Every one knows the fine story of Perseus and Andromeda; how the
+lovely Andromeda, the daughter of a king, was tied to a rock on the
+sea-coast, and as Leviathan was in the very act of carrying her off,
+Perseus, the prince of whalemen, intrepidly advancing, harpooned the
+monster, and delivered and married the maid. It was an admirable
+artistic exploit, rarely achieved by the best harpooneers of the
+present day; inasmuch as this Leviathan was slain at the very first
+dart. And let no man doubt this Arkite story; for in the ancient
+Joppa, now Jaffa, on the Syrian coast, in one of the Pagan temples,
+there stood for many ages the vast skeleton of a whale, which the
+city's legends and all the inhabitants asserted to be the identical
+bones of the monster that Perseus slew. When the Romans took Joppa,
+the same skeleton was carried to Italy in triumph. What seems most
+singular and suggestively important in this story, is this: it was
+from Joppa that Jonah set sail.
+
+Akin to the adventure of Perseus and Andromeda--indeed, by some
+supposed to be indirectly derived from it--is that famous story of
+St. George and the Dragon; which dragon I maintain to have been a
+whale; for in many old chronicles whales and dragons are strangely
+jumbled together, and often stand for each other. "Thou art as a
+lion of the waters, and as a dragon of the sea," saith Ezekiel;
+hereby, plainly meaning a whale; in truth, some versions of the Bible
+use that word itself. Besides, it would much subtract from the glory
+of the exploit had St. George but encountered a crawling reptile of
+the land, instead of doing battle with the great monster of the deep.
+Any man may kill a snake, but only a Perseus, a St. George, a
+Coffin, have the heart in them to march boldly up to a whale.
+
+Let not the modern paintings of this scene mislead us; for though the
+creature encountered by that valiant whaleman of old is vaguely
+represented of a griffin-like shape, and though the battle is
+depicted on land and the saint on horseback, yet considering the
+great ignorance of those times, when the true form of the whale was
+unknown to artists; and considering that as in Perseus' case, St.
+George's whale might have crawled up out of the sea on the beach; and
+considering that the animal ridden by St. George might have been only
+a large seal, or sea-horse; bearing all this in mind, it will not
+appear altogether incompatible with the sacred legend and the
+ancientest draughts of the scene, to hold this so-called dragon no
+other than the great Leviathan himself. In fact, placed before the
+strict and piercing truth, this whole story will fare like that fish,
+flesh, and fowl idol of the Philistines, Dagon by name; who being
+planted before the ark of Israel, his horse's head and both the palms
+of his hands fell off from him, and only the stump or fishy part of
+him remained. Thus, then, one of our own noble stamp, even a
+whaleman, is the tutelary guardian of England; and by good rights, we
+harpooneers of Nantucket should be enrolled in the most noble order
+of St. George. And therefore, let not the knights of that honourable
+company (none of whom, I venture to say, have ever had to do with a
+whale like their great patron), let them never eye a Nantucketer with
+disdain, since even in our woollen frocks and tarred trowsers we are
+much better entitled to St. George's decoration than they.
+
+Whether to admit Hercules among us or not, concerning this I long
+remained dubious: for though according to the Greek mythologies, that
+antique Crockett and Kit Carson--that brawny doer of rejoicing good
+deeds, was swallowed down and thrown up by a whale; still, whether
+that strictly makes a whaleman of him, that might be mooted. It
+nowhere appears that he ever actually harpooned his fish, unless,
+indeed, from the inside. Nevertheless, he may be deemed a sort of
+involuntary whaleman; at any rate the whale caught him, if he did not
+the whale. I claim him for one of our clan.
+
+But, by the best contradictory authorities, this Grecian story of
+Hercules and the whale is considered to be derived from the still
+more ancient Hebrew story of Jonah and the whale; and vice versa;
+certainly they are very similar. If I claim the demigod then, why
+not the prophet?
+
+Nor do heroes, saints, demigods, and prophets alone comprise the
+whole roll of our order. Our grand master is still to be named; for
+like royal kings of old times, we find the head waters of our
+fraternity in nothing short of the great gods themselves. That
+wondrous oriental story is now to be rehearsed from the Shaster,
+which gives us the dread Vishnoo, one of the three persons in the
+godhead of the Hindoos; gives us this divine Vishnoo himself for our
+Lord;--Vishnoo, who, by the first of his ten earthly incarnations,
+has for ever set apart and sanctified the whale. When Brahma, or the
+God of Gods, saith the Shaster, resolved to recreate the world after
+one of its periodical dissolutions, he gave birth to Vishnoo, to
+preside over the work; but the Vedas, or mystical books, whose
+perusal would seem to have been indispensable to Vishnoo before
+beginning the creation, and which therefore must have contained
+something in the shape of practical hints to young architects, these
+Vedas were lying at the bottom of the waters; so Vishnoo became
+incarnate in a whale, and sounding down in him to the uttermost
+depths, rescued the sacred volumes. Was not this Vishnoo a whaleman,
+then? even as a man who rides a horse is called a horseman?
+
+Perseus, St. George, Hercules, Jonah, and Vishnoo! there's a
+member-roll for you! What club but the whaleman's can head off like
+that?
+
+
+
+CHAPTER 83
+
+Jonah Historically Regarded.
+
+
+Reference was made to the historical story of Jonah and the whale in
+the preceding chapter. Now some Nantucketers rather distrust this
+historical story of Jonah and the whale. But then there were some
+sceptical Greeks and Romans, who, standing out from the orthodox
+pagans of their times, equally doubted the story of Hercules and the
+whale, and Arion and the dolphin; and yet their doubting those
+traditions did not make those traditions one whit the less facts, for
+all that.
+
+One old Sag-Harbor whaleman's chief reason for questioning the Hebrew
+story was this:--He had one of those quaint old-fashioned Bibles,
+embellished with curious, unscientific plates; one of which
+represented Jonah's whale with two spouts in his head--a peculiarity
+only true with respect to a species of the Leviathan (the Right
+Whale, and the varieties of that order), concerning which the
+fishermen have this saying, "A penny roll would choke him"; his
+swallow is so very small. But, to this, Bishop Jebb's anticipative
+answer is ready. It is not necessary, hints the Bishop, that we
+consider Jonah as tombed in the whale's belly, but as temporarily
+lodged in some part of his mouth. And this seems reasonable enough
+in the good Bishop. For truly, the Right Whale's mouth would
+accommodate a couple of whist-tables, and comfortably seat all the
+players. Possibly, too, Jonah might have ensconced himself in a
+hollow tooth; but, on second thoughts, the Right Whale is toothless.
+
+Another reason which Sag-Harbor (he went by that name) urged for his
+want of faith in this matter of the prophet, was something obscurely
+in reference to his incarcerated body and the whale's gastric juices.
+But this objection likewise falls to the ground, because a German
+exegetist supposes that Jonah must have taken refuge in the floating
+body of a DEAD whale--even as the French soldiers in the Russian
+campaign turned their dead horses into tents, and crawled into them.
+Besides, it has been divined by other continental commentators, that
+when Jonah was thrown overboard from the Joppa ship, he straightway
+effected his escape to another vessel near by, some vessel with a
+whale for a figure-head; and, I would add, possibly called "The
+Whale," as some craft are nowadays christened the "Shark," the
+"Gull," the "Eagle." Nor have there been wanting learned exegetists
+who have opined that the whale mentioned in the book of Jonah merely
+meant a life-preserver--an inflated bag of wind--which the endangered
+prophet swam to, and so was saved from a watery doom. Poor
+Sag-Harbor, therefore, seems worsted all round. But he had still
+another reason for his want of faith. It was this, if I remember
+right: Jonah was swallowed by the whale in the Mediterranean Sea, and
+after three days he was vomited up somewhere within three days'
+journey of Nineveh, a city on the Tigris, very much more than three
+days' journey across from the nearest point of the Mediterranean
+coast. How is that?
+
+But was there no other way for the whale to land the prophet within
+that short distance of Nineveh? Yes. He might have carried him
+round by the way of the Cape of Good Hope. But not to speak of the
+passage through the whole length of the Mediterranean, and another
+passage up the Persian Gulf and Red Sea, such a supposition would
+involve the complete circumnavigation of all Africa in three days,
+not to speak of the Tigris waters, near the site of Nineveh, being
+too shallow for any whale to swim in. Besides, this idea of Jonah's
+weathering the Cape of Good Hope at so early a day would wrest the
+honour of the discovery of that great headland from Bartholomew Diaz,
+its reputed discoverer, and so make modern history a liar.
+
+But all these foolish arguments of old Sag-Harbor only evinced his
+foolish pride of reason--a thing still more reprehensible in him,
+seeing that he had but little learning except what he had picked up
+from the sun and the sea. I say it only shows his foolish, impious
+pride, and abominable, devilish rebellion against the reverend
+clergy. For by a Portuguese Catholic priest, this very idea of
+Jonah's going to Nineveh via the Cape of Good Hope was advanced as a
+signal magnification of the general miracle. And so it was.
+Besides, to this day, the highly enlightened Turks devoutly believe
+in the historical story of Jonah. And some three centuries ago, an
+English traveller in old Harris's Voyages, speaks of a Turkish Mosque
+built in honour of Jonah, in which Mosque was a miraculous lamp that
+burnt without any oil.
+
+
+
+CHAPTER 84
+
+Pitchpoling.
+
+
+To make them run easily and swiftly, the axles of carriages are
+anointed; and for much the same purpose, some whalers perform an
+analogous operation upon their boat; they grease the bottom. Nor is
+it to be doubted that as such a procedure can do no harm, it may
+possibly be of no contemptible advantage; considering that oil and
+water are hostile; that oil is a sliding thing, and that the object
+in view is to make the boat slide bravely. Queequeg believed
+strongly in anointing his boat, and one morning not long after the
+German ship Jungfrau disappeared, took more than customary pains in
+that occupation; crawling under its bottom, where it hung over the
+side, and rubbing in the unctuousness as though diligently seeking to
+insure a crop of hair from the craft's bald keel. He seemed to be
+working in obedience to some particular presentiment. Nor did it
+remain unwarranted by the event.
+
+Towards noon whales were raised; but so soon as the ship sailed down
+to them, they turned and fled with swift precipitancy; a disordered
+flight, as of Cleopatra's barges from Actium.
+
+Nevertheless, the boats pursued, and Stubb's was foremost. By great
+exertion, Tashtego at last succeeded in planting one iron; but the
+stricken whale, without at all sounding, still continued his
+horizontal flight, with added fleetness. Such unintermitted
+strainings upon the planted iron must sooner or later inevitably
+extract it. It became imperative to lance the flying whale, or be
+content to lose him. But to haul the boat up to his flank was
+impossible, he swam so fast and furious. What then remained?
+
+Of all the wondrous devices and dexterities, the sleights of hand and
+countless subtleties, to which the veteran whaleman is so often
+forced, none exceed that fine manoeuvre with the lance called
+pitchpoling. Small sword, or broad sword, in all its exercises
+boasts nothing like it. It is only indispensable with an inveterate
+running whale; its grand fact and feature is the wonderful distance
+to which the long lance is accurately darted from a violently
+rocking, jerking boat, under extreme headway. Steel and wood
+included, the entire spear is some ten or twelve feet in length; the
+staff is much slighter than that of the harpoon, and also of a
+lighter material--pine. It is furnished with a small rope called a
+warp, of considerable length, by which it can be hauled back to the
+hand after darting.
+
+But before going further, it is important to mention here, that
+though the harpoon may be pitchpoled in the same way with the lance,
+yet it is seldom done; and when done, is still less frequently
+successful, on account of the greater weight and inferior length of
+the harpoon as compared with the lance, which in effect become
+serious drawbacks. As a general thing, therefore, you must first
+get fast to a whale, before any pitchpoling comes into play.
+
+Look now at Stubb; a man who from his humorous, deliberate coolness
+and equanimity in the direst emergencies, was specially qualified to
+excel in pitchpoling. Look at him; he stands upright in the tossed
+bow of the flying boat; wrapt in fleecy foam, the towing whale is
+forty feet ahead. Handling the long lance lightly, glancing twice or
+thrice along its length to see if it be exactly straight, Stubb
+whistlingly gathers up the coil of the warp in one hand, so as to
+secure its free end in his grasp, leaving the rest unobstructed.
+Then holding the lance full before his waistband's middle, he levels
+it at the whale; when, covering him with it, he steadily depresses
+the butt-end in his hand, thereby elevating the point till the weapon
+stands fairly balanced upon his palm, fifteen feet in the air. He
+minds you somewhat of a juggler, balancing a long staff on his chin.
+Next moment with a rapid, nameless impulse, in a superb lofty arch the
+bright steel spans the foaming distance, and quivers in the life spot
+of the whale. Instead of sparkling water, he now spouts red blood.
+
+"That drove the spigot out of him!" cried Stubb. "'Tis July's
+immortal Fourth; all fountains must run wine today! Would now, it
+were old Orleans whiskey, or old Ohio, or unspeakable old
+Monongahela! Then, Tashtego, lad, I'd have ye hold a canakin to the
+jet, and we'd drink round it! Yea, verily, hearts alive, we'd brew
+choice punch in the spread of his spout-hole there, and from that
+live punch-bowl quaff the living stuff."
+
+Again and again to such gamesome talk, the dexterous dart is
+repeated, the spear returning to its master like a greyhound held in
+skilful leash. The agonized whale goes into his flurry; the tow-line
+is slackened, and the pitchpoler dropping astern, folds his hands,
+and mutely watches the monster die.
+
+
+
+CHAPTER 85
+
+The Fountain.
+
+
+That for six thousand years--and no one knows how many millions of
+ages before--the great whales should have been spouting all over the
+sea, and sprinkling and mistifying the gardens of the deep, as with
+so many sprinkling or mistifying pots; and that for some centuries
+back, thousands of hunters should have been close by the fountain of
+the whale, watching these sprinklings and spoutings--that all this
+should be, and yet, that down to this blessed minute (fifteen and a
+quarter minutes past one o'clock P.M. of this sixteenth day of
+December, A.D. 1851), it should still remain a problem, whether these
+spoutings are, after all, really water, or nothing but vapour--this is
+surely a noteworthy thing.
+
+Let us, then, look at this matter, along with some interesting items
+contingent. Every one knows that by the peculiar cunning of their
+gills, the finny tribes in general breathe the air which at all times
+is combined with the element in which they swim; hence, a herring or
+a cod might live a century, and never once raise its head above the
+surface. But owing to his marked internal structure which gives him
+regular lungs, like a human being's, the whale can only live by
+inhaling the disengaged air in the open atmosphere. Wherefore the
+necessity for his periodical visits to the upper world. But he
+cannot in any degree breathe through his mouth, for, in his ordinary
+attitude, the Sperm Whale's mouth is buried at least eight feet
+beneath the surface; and what is still more, his windpipe has no
+connexion with his mouth. No, he breathes through his spiracle
+alone; and this is on the top of his head.
+
+If I say, that in any creature breathing is only a function
+indispensable to vitality, inasmuch as it withdraws from the air a
+certain element, which being subsequently brought into contact with
+the blood imparts to the blood its vivifying principle, I do not
+think I shall err; though I may possibly use some superfluous
+scientific words. Assume it, and it follows that if all the blood in
+a man could be aerated with one breath, he might then seal up his
+nostrils and not fetch another for a considerable time. That is to
+say, he would then live without breathing. Anomalous as it may seem,
+this is precisely the case with the whale, who systematically lives,
+by intervals, his full hour and more (when at the bottom) without
+drawing a single breath, or so much as in any way inhaling a particle
+of air; for, remember, he has no gills. How is this? Between his
+ribs and on each side of his spine he is supplied with a remarkable
+involved Cretan labyrinth of vermicelli-like vessels, which vessels,
+when he quits the surface, are completely distended with oxygenated
+blood. So that for an hour or more, a thousand fathoms in the sea,
+he carries a surplus stock of vitality in him, just as the camel
+crossing the waterless desert carries a surplus supply of drink for
+future use in its four supplementary stomachs. The anatomical fact
+of this labyrinth is indisputable; and that the supposition founded
+upon it is reasonable and true, seems the more cogent to me, when I
+consider the otherwise inexplicable obstinacy of that leviathan in
+HAVING HIS SPOUTINGS OUT, as the fishermen phrase it. This is what I
+mean. If unmolested, upon rising to the surface, the Sperm Whale
+will continue there for a period of time exactly uniform with all his
+other unmolested risings. Say he stays eleven minutes, and jets
+seventy times, that is, respires seventy breaths; then whenever he
+rises again, he will be sure to have his seventy breaths over again,
+to a minute. Now, if after he fetches a few breaths you alarm him,
+so that he sounds, he will be always dodging up again to make good
+his regular allowance of air. And not till those seventy breaths are
+told, will he finally go down to stay out his full term below.
+Remark, however, that in different individuals these rates are
+different; but in any one they are alike. Now, why should the whale
+thus insist upon having his spoutings out, unless it be to replenish
+his reservoir of air, ere descending for good? How obvious is it,
+too, that this necessity for the whale's rising exposes him to all
+the fatal hazards of the chase. For not by hook or by net could
+this vast leviathan be caught, when sailing a thousand fathoms
+beneath the sunlight. Not so much thy skill, then, O hunter, as the
+great necessities that strike the victory to thee!
+
+In man, breathing is incessantly going on--one breath only serving
+for two or three pulsations; so that whatever other business he has
+to attend to, waking or sleeping, breathe he must, or die he will.
+But the Sperm Whale only breathes about one seventh or Sunday of his
+time.
+
+It has been said that the whale only breathes through his spout-hole;
+if it could truthfully be added that his spouts are mixed with water,
+then I opine we should be furnished with the reason why his sense of
+smell seems obliterated in him; for the only thing about him that at
+all answers to his nose is that identical spout-hole; and being so
+clogged with two elements, it could not be expected to have the power
+of smelling. But owing to the mystery of the spout--whether it be
+water or whether it be vapour--no absolute certainty can as yet be
+arrived at on this head. Sure it is, nevertheless, that the Sperm
+Whale has no proper olfactories. But what does he want of them? No
+roses, no violets, no Cologne-water in the sea.
+
+Furthermore, as his windpipe solely opens into the tube of his
+spouting canal, and as that long canal--like the grand Erie Canal--is
+furnished with a sort of locks (that open and shut) for the downward
+retention of air or the upward exclusion of water, therefore the
+whale has no voice; unless you insult him by saying, that when he so
+strangely rumbles, he talks through his nose. But then again, what
+has the whale to say? Seldom have I known any profound being that
+had anything to say to this world, unless forced to stammer out
+something by way of getting a living. Oh! happy that the world is
+such an excellent listener!
+
+Now, the spouting canal of the Sperm Whale, chiefly intended as it is
+for the conveyance of air, and for several feet laid along,
+horizontally, just beneath the upper surface of his head, and a
+little to one side; this curious canal is very much like a gas-pipe
+laid down in a city on one side of a street. But the question
+returns whether this gas-pipe is also a water-pipe; in other words,
+whether the spout of the Sperm Whale is the mere vapour of the exhaled
+breath, or whether that exhaled breath is mixed with water taken in
+at the mouth, and discharged through the spiracle. It is certain
+that the mouth indirectly communicates with the spouting canal; but
+it cannot be proved that this is for the purpose of discharging water
+through the spiracle. Because the greatest necessity for so doing
+would seem to be, when in feeding he accidentally takes in water.
+But the Sperm Whale's food is far beneath the surface, and there he
+cannot spout even if he would. Besides, if you regard him very
+closely, and time him with your watch, you will find that when
+unmolested, there is an undeviating rhyme between the periods of his
+jets and the ordinary periods of respiration.
+
+But why pester one with all this reasoning on the subject? Speak
+out! You have seen him spout; then declare what the spout is; can
+you not tell water from air? My dear sir, in this world it is not so
+easy to settle these plain things. I have ever found your plain
+things the knottiest of all. And as for this whale spout, you might
+almost stand in it, and yet be undecided as to what it is precisely.
+
+The central body of it is hidden in the snowy sparkling mist
+enveloping it; and how can you certainly tell whether any water falls
+from it, when, always, when you are close enough to a whale to get a
+close view of his spout, he is in a prodigious commotion, the water
+cascading all around him. And if at such times you should think that
+you really perceived drops of moisture in the spout, how do you know
+that they are not merely condensed from its vapour; or how do you know
+that they are not those identical drops superficially lodged in the
+spout-hole fissure, which is countersunk into the summit of the
+whale's head? For even when tranquilly swimming through the mid-day
+sea in a calm, with his elevated hump sun-dried as a dromedary's in
+the desert; even then, the whale always carries a small basin of
+water on his head, as under a blazing sun you will sometimes see a
+cavity in a rock filled up with rain.
+
+Nor is it at all prudent for the hunter to be over curious touching
+the precise nature of the whale spout. It will not do for him to be
+peering into it, and putting his face in it. You cannot go with your
+pitcher to this fountain and fill it, and bring it away. For even
+when coming into slight contact with the outer, vapoury shreds of the
+jet, which will often happen, your skin will feverishly smart, from
+the acridness of the thing so touching it. And I know one, who
+coming into still closer contact with the spout, whether with some
+scientific object in view, or otherwise, I cannot say, the skin
+peeled off from his cheek and arm. Wherefore, among whalemen, the
+spout is deemed poisonous; they try to evade it. Another thing; I
+have heard it said, and I do not much doubt it, that if the jet is
+fairly spouted into your eyes, it will blind you. The wisest thing
+the investigator can do then, it seems to me, is to let this deadly
+spout alone.
+
+Still, we can hypothesize, even if we cannot prove and establish. My
+hypothesis is this: that the spout is nothing but mist. And besides
+other reasons, to this conclusion I am impelled, by considerations
+touching the great inherent dignity and sublimity of the Sperm Whale;
+I account him no common, shallow being, inasmuch as it is an
+undisputed fact that he is never found on soundings, or near shores;
+all other whales sometimes are. He is both ponderous and profound.
+And I am convinced that from the heads of all ponderous profound
+beings, such as Plato, Pyrrho, the Devil, Jupiter, Dante, and so on,
+there always goes up a certain semi-visible steam, while in the act
+of thinking deep thoughts. While composing a little treatise on
+Eternity, I had the curiosity to place a mirror before me; and ere
+long saw reflected there, a curious involved worming and undulation
+in the atmosphere over my head. The invariable moisture of my hair,
+while plunged in deep thought, after six cups of hot tea in my thin
+shingled attic, of an August noon; this seems an additional argument
+for the above supposition.
+
+And how nobly it raises our conceit of the mighty, misty monster, to
+behold him solemnly sailing through a calm tropical sea; his vast,
+mild head overhung by a canopy of vapour, engendered by his
+incommunicable contemplations, and that vapour--as you will sometimes
+see it--glorified by a rainbow, as if Heaven itself had put its seal
+upon his thoughts. For, d'ye see, rainbows do not visit the clear
+air; they only irradiate vapour. And so, through all the thick mists
+of the dim doubts in my mind, divine intuitions now and then shoot,
+enkindling my fog with a heavenly ray. And for this I thank God; for
+all have doubts; many deny; but doubts or denials, few along with
+them, have intuitions. Doubts of all things earthly, and intuitions
+of some things heavenly; this combination makes neither believer nor
+infidel, but makes a man who regards them both with equal eye.
+
+
+
+CHAPTER 86
+
+The Tail.
+
+
+Other poets have warbled the praises of the soft eye of the antelope,
+and the lovely plumage of the bird that never alights; less
+celestial, I celebrate a tail.
+
+Reckoning the largest sized Sperm Whale's tail to begin at that point
+of the trunk where it tapers to about the girth of a man, it
+comprises upon its upper surface alone, an area of at least fifty
+square feet. The compact round body of its root expands into two
+broad, firm, flat palms or flukes, gradually shoaling away to less
+than an inch in thickness. At the crotch or junction, these flukes
+slightly overlap, then sideways recede from each other like wings,
+leaving a wide vacancy between. In no living thing are the lines of
+beauty more exquisitely defined than in the crescentic borders of
+these flukes. At its utmost expansion in the full grown whale, the
+tail will considerably exceed twenty feet across.
+
+The entire member seems a dense webbed bed of welded sinews; but cut
+into it, and you find that three distinct strata compose it:--upper,
+middle, and lower. The fibres in the upper and lower layers, are
+long and horizontal; those of the middle one, very short, and running
+crosswise between the outside layers. This triune structure, as much
+as anything else, imparts power to the tail. To the student of old
+Roman walls, the middle layer will furnish a curious parallel to the
+thin course of tiles always alternating with the stone in those
+wonderful relics of the antique, and which undoubtedly contribute so
+much to the great strength of the masonry.
+
+But as if this vast local power in the tendinous tail were not
+enough, the whole bulk of the leviathan is knit over with a warp and
+woof of muscular fibres and filaments, which passing on either side
+the loins and running down into the flukes, insensibly blend with
+them, and largely contribute to their might; so that in the tail the
+confluent measureless force of the whole whale seems concentrated to
+a point. Could annihilation occur to matter, this were the thing to
+do it.
+
+Nor does this--its amazing strength, at all tend to cripple the
+graceful flexion of its motions; where infantileness of ease
+undulates through a Titanism of power. On the contrary, those
+motions derive their most appalling beauty from it. Real strength
+never impairs beauty or harmony, but it often bestows it; and in
+everything imposingly beautiful, strength has much to do with the
+magic. Take away the tied tendons that all over seem bursting from
+the marble in the carved Hercules, and its charm would be gone. As
+devout Eckerman lifted the linen sheet from the naked corpse of
+Goethe, he was overwhelmed with the massive chest of the man, that
+seemed as a Roman triumphal arch. When Angelo paints even God the
+Father in human form, mark what robustness is there. And whatever
+they may reveal of the divine love in the Son, the soft, curled,
+hermaphroditical Italian pictures, in which his idea has been most
+successfully embodied; these pictures, so destitute as they are of
+all brawniness, hint nothing of any power, but the mere negative,
+feminine one of submission and endurance, which on all hands it is
+conceded, form the peculiar practical virtues of his teachings.
+
+Such is the subtle elasticity of the organ I treat of, that whether
+wielded in sport, or in earnest, or in anger, whatever be the mood it
+be in, its flexions are invariably marked by exceeding grace.
+Therein no fairy's arm can transcend it.
+
+Five great motions are peculiar to it. First, when used as a fin for
+progression; Second, when used as a mace in battle; Third, in
+sweeping; Fourth, in lobtailing; Fifth, in peaking flukes.
+
+First: Being horizontal in its position, the Leviathan's tail acts in
+a different manner from the tails of all other sea creatures. It
+never wriggles. In man or fish, wriggling is a sign of inferiority.
+To the whale, his tail is the sole means of propulsion. Scroll-wise
+coiled forwards beneath the body, and then rapidly sprung backwards,
+it is this which gives that singular darting, leaping motion to the
+monster when furiously swimming. His side-fins only serve to steer
+by.
+
+Second: It is a little significant, that while one sperm whale only
+fights another sperm whale with his head and jaw, nevertheless, in
+his conflicts with man, he chiefly and contemptuously uses his tail.
+In striking at a boat, he swiftly curves away his flukes from it, and
+the blow is only inflicted by the recoil. If it be made in the
+unobstructed air, especially if it descend to its mark, the stroke is
+then simply irresistible. No ribs of man or boat can withstand it.
+Your only salvation lies in eluding it; but if it comes sideways
+through the opposing water, then partly owing to the light buoyancy
+of the whale boat, and the elasticity of its materials, a cracked
+rib or a dashed plank or two, a sort of stitch in the side, is
+generally the most serious result. These submerged side blows are so
+often received in the fishery, that they are accounted mere child's
+play. Some one strips off a frock, and the hole is stopped.
+
+Third: I cannot demonstrate it, but it seems to me, that in the whale
+the sense of touch is concentrated in the tail; for in this respect
+there is a delicacy in it only equalled by the daintiness of the
+elephant's trunk. This delicacy is chiefly evinced in the action of
+sweeping, when in maidenly gentleness the whale with a certain soft
+slowness moves his immense flukes from side to side upon the surface of
+the sea; and if he feel but a sailor's whisker, woe to that sailor,
+whiskers and all. What tenderness there is in that preliminary
+touch! Had this tail any prehensile power, I should straightway
+bethink me of Darmonodes' elephant that so frequented the
+flower-market, and with low salutations presented nosegays to
+damsels, and then caressed their zones. On more accounts than one, a
+pity it is that the whale does not possess this prehensile virtue in
+his tail; for I have heard of yet another elephant, that when wounded
+in the fight, curved round his trunk and extracted the dart.
+
+Fourth: Stealing unawares upon the whale in the fancied security of
+the middle of solitary seas, you find him unbent from the vast
+corpulence of his dignity, and kitten-like, he plays on the ocean as
+if it were a hearth. But still you see his power in his play. The
+broad palms of his tail are flirted high into the air; then smiting
+the surface, the thunderous concussion resounds for miles. You would
+almost think a great gun had been discharged; and if you noticed the
+light wreath of vapour from the spiracle at his other extremity, you
+would think that that was the smoke from the touch-hole.
+
+Fifth: As in the ordinary floating posture of the leviathan the
+flukes lie considerably below the level of his back, they are then
+completely out of sight beneath the surface; but when he is about to
+plunge into the deeps, his entire flukes with at least thirty feet of
+his body are tossed erect in the air, and so remain vibrating a
+moment, till they downwards shoot out of view. Excepting the sublime
+BREACH--somewhere else to be described--this peaking of the whale's
+flukes is perhaps the grandest sight to be seen in all animated
+nature. Out of the bottomless profundities the gigantic tail seems
+spasmodically snatching at the highest heaven. So in dreams, have I
+seen majestic Satan thrusting forth his tormented colossal claw from
+the flame Baltic of Hell. But in gazing at such scenes, it is all in
+all what mood you are in; if in the Dantean, the devils will occur to
+you; if in that of Isaiah, the archangels. Standing at the mast-head
+of my ship during a sunrise that crimsoned sky and sea, I once saw a
+large herd of whales in the east, all heading towards the sun, and
+for a moment vibrating in concert with peaked flukes. As it seemed
+to me at the time, such a grand embodiment of adoration of the gods
+was never beheld, even in Persia, the home of the fire worshippers.
+As Ptolemy Philopater testified of the African elephant, I then
+testified of the whale, pronouncing him the most devout of all
+beings. For according to King Juba, the military elephants of
+antiquity often hailed the morning with their trunks uplifted in the
+profoundest silence.
+
+The chance comparison in this chapter, between the whale and the
+elephant, so far as some aspects of the tail of the one and the trunk
+of the other are concerned, should not tend to place those two
+opposite organs on an equality, much less the creatures to which they
+respectively belong. For as the mightiest elephant is but a terrier
+to Leviathan, so, compared with Leviathan's tail, his trunk is but
+the stalk of a lily. The most direful blow from the elephant's trunk
+were as the playful tap of a fan, compared with the measureless crush
+and crash of the sperm whale's ponderous flukes, which in repeated
+instances have one after the other hurled entire boats with all their
+oars and crews into the air, very much as an Indian juggler tosses
+his balls.*
+
+
+*Though all comparison in the way of general bulk between the whale
+and the elephant is preposterous, inasmuch as in that particular the
+elephant stands in much the same respect to the whale that a dog does
+to the elephant; nevertheless, there are not wanting some points of
+curious similitude; among these is the spout. It is well known that
+the elephant will often draw up water or dust in his trunk, and then
+elevating it, jet it forth in a stream.
+
+
+The more I consider this mighty tail, the more do I deplore my
+inability to express it. At times there are gestures in it, which,
+though they would well grace the hand of man, remain wholly
+inexplicable. In an extensive herd, so remarkable, occasionally, are
+these mystic gestures, that I have heard hunters who have declared
+them akin to Free-Mason signs and symbols; that the whale, indeed, by
+these methods intelligently conversed with the world. Nor are there
+wanting other motions of the whale in his general body, full of
+strangeness, and unaccountable to his most experienced assailant.
+Dissect him how I may, then, I but go skin deep; I know him not,
+and never will. But if I know not even the tail of this whale, how
+understand his head? much more, how comprehend his face, when face he
+has none? Thou shalt see my back parts, my tail, he seems to say,
+but my face shall not be seen. But I cannot completely make out his
+back parts; and hint what he will about his face, I say again he has
+no face.
+
+
+
+CHAPTER 87
+
+The Grand Armada.
+
+
+The long and narrow peninsula of Malacca, extending south-eastward
+from the territories of Birmah, forms the most southerly point of all
+Asia. In a continuous line from that peninsula stretch the long
+islands of Sumatra, Java, Bally, and Timor; which, with many others,
+form a vast mole, or rampart, lengthwise connecting Asia with
+Australia, and dividing the long unbroken Indian ocean from the
+thickly studded oriental archipelagoes. This rampart is pierced by
+several sally-ports for the convenience of ships and whales;
+conspicuous among which are the straits of Sunda and Malacca. By the
+straits of Sunda, chiefly, vessels bound to China from the west,
+emerge into the China seas.
+
+Those narrow straits of Sunda divide Sumatra from Java; and standing
+midway in that vast rampart of islands, buttressed by that bold green
+promontory, known to seamen as Java Head; they not a little
+correspond to the central gateway opening into some vast walled
+empire: and considering the inexhaustible wealth of spices, and
+silks, and jewels, and gold, and ivory, with which the thousand
+islands of that oriental sea are enriched, it seems a significant
+provision of nature, that such treasures, by the very formation of
+the land, should at least bear the appearance, however ineffectual,
+of being guarded from the all-grasping western world. The shores of
+the Straits of Sunda are unsupplied with those domineering fortresses
+which guard the entrances to the Mediterranean, the Baltic, and the
+Propontis. Unlike the Danes, these Orientals do not demand the
+obsequious homage of lowered top-sails from the endless procession of
+ships before the wind, which for centuries past, by night and by day,
+have passed between the islands of Sumatra and Java, freighted with
+the costliest cargoes of the east. But while they freely waive a
+ceremonial like this, they do by no means renounce their claim to
+more solid tribute.
+
+Time out of mind the piratical proas of the Malays, lurking among the
+low shaded coves and islets of Sumatra, have sallied out upon the
+vessels sailing through the straits, fiercely demanding tribute at
+the point of their spears. Though by the repeated bloody
+chastisements they have received at the hands of European cruisers,
+the audacity of these corsairs has of late been somewhat repressed;
+yet, even at the present day, we occasionally hear of English and
+American vessels, which, in those waters, have been remorselessly
+boarded and pillaged.
+
+With a fair, fresh wind, the Pequod was now drawing nigh to these
+straits; Ahab purposing to pass through them into the Javan sea, and
+thence, cruising northwards, over waters known to be frequented here
+and there by the Sperm Whale, sweep inshore by the Philippine
+Islands, and gain the far coast of Japan, in time for the great
+whaling season there. By these means, the circumnavigating Pequod
+would sweep almost all the known Sperm Whale cruising grounds of the
+world, previous to descending upon the Line in the Pacific; where
+Ahab, though everywhere else foiled in his pursuit, firmly counted
+upon giving battle to Moby Dick, in the sea he was most known to
+frequent; and at a season when he might most reasonably be presumed
+to be haunting it.
+
+But how now? in this zoned quest, does Ahab touch no land? does his
+crew drink air? Surely, he will stop for water. Nay. For a long
+time, now, the circus-running sun has raced within his fiery ring,
+and needs no sustenance but what's in himself. So Ahab. Mark this,
+too, in the whaler. While other hulls are loaded down with alien
+stuff, to be transferred to foreign wharves; the world-wandering
+whale-ship carries no cargo but herself and crew, their weapons and
+their wants. She has a whole lake's contents bottled in her ample
+hold. She is ballasted with utilities; not altogether with unusable
+pig-lead and kentledge. She carries years' water in her. Clear old
+prime Nantucket water; which, when three years afloat, the
+Nantucketer, in the Pacific, prefers to drink before the brackish
+fluid, but yesterday rafted off in casks, from the Peruvian or Indian
+streams. Hence it is, that, while other ships may have gone to China
+from New York, and back again, touching at a score of ports, the
+whale-ship, in all that interval, may not have sighted one grain of
+soil; her crew having seen no man but floating seamen like
+themselves. So that did you carry them the news that another flood
+had come; they would only answer--"Well, boys, here's the ark!"
+
+Now, as many Sperm Whales had been captured off the western coast of
+Java, in the near vicinity of the Straits of Sunda; indeed, as most
+of the ground, roundabout, was generally recognised by the fishermen
+as an excellent spot for cruising; therefore, as the Pequod gained
+more and more upon Java Head, the look-outs were repeatedly hailed,
+and admonished to keep wide awake. But though the green palmy cliffs
+of the land soon loomed on the starboard bow, and with delighted
+nostrils the fresh cinnamon was snuffed in the air, yet not a single
+jet was descried. Almost renouncing all thought of falling in with
+any game hereabouts, the ship had well nigh entered the straits, when
+the customary cheering cry was heard from aloft, and ere long a
+spectacle of singular magnificence saluted us.
+
+But here be it premised, that owing to the unwearied activity with
+which of late they have been hunted over all four oceans, the Sperm
+Whales, instead of almost invariably sailing in small detached
+companies, as in former times, are now frequently met with in
+extensive herds, sometimes embracing so great a multitude, that it
+would almost seem as if numerous nations of them had sworn solemn
+league and covenant for mutual assistance and protection. To this
+aggregation of the Sperm Whale into such immense caravans, may be
+imputed the circumstance that even in the best cruising grounds, you
+may now sometimes sail for weeks and months together, without being
+greeted by a single spout; and then be suddenly saluted by what
+sometimes seems thousands on thousands.
+
+Broad on both bows, at the distance of some two or three miles, and
+forming a great semicircle, embracing one half of the level horizon,
+a continuous chain of whale-jets were up-playing and sparkling in the
+noon-day air. Unlike the straight perpendicular twin-jets of the
+Right Whale, which, dividing at top, fall over in two branches, like
+the cleft drooping boughs of a willow, the single forward-slanting
+spout of the Sperm Whale presents a thick curled bush of white mist,
+continually rising and falling away to leeward.
+
+Seen from the Pequod's deck, then, as she would rise on a high hill
+of the sea, this host of vapoury spouts, individually curling up into
+the air, and beheld through a blending atmosphere of bluish haze,
+showed like the thousand cheerful chimneys of some dense metropolis,
+descried of a balmy autumnal morning, by some horseman on a height.
+
+As marching armies approaching an unfriendly defile in the mountains,
+accelerate their march, all eagerness to place that perilous passage
+in their rear, and once more expand in comparative security upon the
+plain; even so did this vast fleet of whales now seem hurrying
+forward through the straits; gradually contracting the wings of their
+semicircle, and swimming on, in one solid, but still crescentic
+centre.
+
+Crowding all sail the Pequod pressed after them; the harpooneers
+handling their weapons, and loudly cheering from the heads of their
+yet suspended boats. If the wind only held, little doubt had they,
+that chased through these Straits of Sunda, the vast host would only
+deploy into the Oriental seas to witness the capture of not a few of
+their number. And who could tell whether, in that congregated
+caravan, Moby Dick himself might not temporarily be swimming, like
+the worshipped white-elephant in the coronation procession of the
+Siamese! So with stun-sail piled on stun-sail, we sailed along,
+driving these leviathans before us; when, of a sudden, the voice of
+Tashtego was heard, loudly directing attention to something in our
+wake.
+
+Corresponding to the crescent in our van, we beheld another in our
+rear. It seemed formed of detached white vapours, rising and falling
+something like the spouts of the whales; only they did not so
+completely come and go; for they constantly hovered, without finally
+disappearing. Levelling his glass at this sight, Ahab quickly
+revolved in his pivot-hole, crying, "Aloft there, and rig whips and
+buckets to wet the sails;--Malays, sir, and after us!"
+
+As if too long lurking behind the headlands, till the Pequod should
+fairly have entered the straits, these rascally Asiatics were now in
+hot pursuit, to make up for their over-cautious delay. But when the
+swift Pequod, with a fresh leading wind, was herself in hot chase;
+how very kind of these tawny philanthropists to assist in speeding
+her on to her own chosen pursuit,--mere riding-whips and rowels to
+her, that they were. As with glass under arm, Ahab to-and-fro paced
+the deck; in his forward turn beholding the monsters he chased, and
+in the after one the bloodthirsty pirates chasing him; some such
+fancy as the above seemed his. And when he glanced upon the green
+walls of the watery defile in which the ship was then sailing, and
+bethought him that through that gate lay the route to his vengeance,
+and beheld, how that through that same gate he was now both chasing
+and being chased to his deadly end; and not only that, but a herd of
+remorseless wild pirates and inhuman atheistical devils were
+infernally cheering him on with their curses;--when all these
+conceits had passed through his brain, Ahab's brow was left gaunt and
+ribbed, like the black sand beach after some stormy tide has been
+gnawing it, without being able to drag the firm thing from its place.
+
+But thoughts like these troubled very few of the reckless crew; and
+when, after steadily dropping and dropping the pirates astern, the
+Pequod at last shot by the vivid green Cockatoo Point on the Sumatra
+side, emerging at last upon the broad waters beyond; then, the
+harpooneers seemed more to grieve that the swift whales had been
+gaining upon the ship, than to rejoice that the ship had so
+victoriously gained upon the Malays. But still driving on in the
+wake of the whales, at length they seemed abating their speed;
+gradually the ship neared them; and the wind now dying away, word was
+passed to spring to the boats. But no sooner did the herd, by some
+presumed wonderful instinct of the Sperm Whale, become notified of
+the three keels that were after them,--though as yet a mile in their
+rear,--than they rallied again, and forming in close ranks and
+battalions, so that their spouts all looked like flashing lines of
+stacked bayonets, moved on with redoubled velocity.
+
+Stripped to our shirts and drawers, we sprang to the white-ash, and
+after several hours' pulling were almost disposed to renounce the
+chase, when a general pausing commotion among the whales gave
+animating token that they were now at last under the influence of
+that strange perplexity of inert irresolution, which, when the
+fishermen perceive it in the whale, they say he is gallied. The
+compact martial columns in which they had been hitherto rapidly and
+steadily swimming, were now broken up in one measureless rout; and
+like King Porus' elephants in the Indian battle with Alexander, they
+seemed going mad with consternation. In all directions expanding in
+vast irregular circles, and aimlessly swimming hither and thither, by
+their short thick spoutings, they plainly betrayed their distraction
+of panic. This was still more strangely evinced by those of their
+number, who, completely paralysed as it were, helplessly floated like
+water-logged dismantled ships on the sea. Had these Leviathans been
+but a flock of simple sheep, pursued over the pasture by three fierce
+wolves, they could not possibly have evinced such excessive dismay.
+But this occasional timidity is characteristic of almost all herding
+creatures. Though banding together in tens of thousands, the
+lion-maned buffaloes of the West have fled before a solitary
+horseman. Witness, too, all human beings, how when herded together
+in the sheepfold of a theatre's pit, they will, at the slightest
+alarm of fire, rush helter-skelter for the outlets, crowding,
+trampling, jamming, and remorselessly dashing each other to death.
+Best, therefore, withhold any amazement at the strangely gallied
+whales before us, for there is no folly of the beasts of the earth
+which is not infinitely outdone by the madness of men.
+
+Though many of the whales, as has been said, were in violent motion,
+yet it is to be observed that as a whole the herd neither advanced
+nor retreated, but collectively remained in one place. As is
+customary in those cases, the boats at once separated, each making
+for some one lone whale on the outskirts of the shoal. In about
+three minutes' time, Queequeg's harpoon was flung; the stricken fish
+darted blinding spray in our faces, and then running away with us like
+light, steered straight for the heart of the herd. Though such a
+movement on the part of the whale struck under such circumstances, is
+in no wise unprecedented; and indeed is almost always more or less
+anticipated; yet does it present one of the more perilous
+vicissitudes of the fishery. For as the swift monster drags you
+deeper and deeper into the frantic shoal, you bid adieu to
+circumspect life and only exist in a delirious throb.
+
+As, blind and deaf, the whale plunged forward, as if by sheer power
+of speed to rid himself of the iron leech that had fastened to him;
+as we thus tore a white gash in the sea, on all sides menaced as we
+flew, by the crazed creatures to and fro rushing about us; our beset
+boat was like a ship mobbed by ice-isles in a tempest, and striving
+to steer through their complicated channels and straits, knowing not at
+what moment it may be locked in and crushed.
+
+But not a bit daunted, Queequeg steered us manfully; now sheering off
+from this monster directly across our route in advance; now edging
+away from that, whose colossal flukes were suspended overhead, while
+all the time, Starbuck stood up in the bows, lance in hand, pricking
+out of our way whatever whales he could reach by short darts, for
+there was no time to make long ones. Nor were the oarsmen quite
+idle, though their wonted duty was now altogether dispensed with.
+They chiefly attended to the shouting part of the business. "Out of
+the way, Commodore!" cried one, to a great dromedary that of a sudden
+rose bodily to the surface, and for an instant threatened to swamp
+us. "Hard down with your tail, there!" cried a second to another,
+which, close to our gunwale, seemed calmly cooling himself with his
+own fan-like extremity.
+
+All whaleboats carry certain curious contrivances, originally
+invented by the Nantucket Indians, called druggs. Two thick squares
+of wood of equal size are stoutly clenched together, so that they
+cross each other's grain at right angles; a line of considerable
+length is then attached to the middle of this block, and the other
+end of the line being looped, it can in a moment be fastened to a
+harpoon. It is chiefly among gallied whales that this drugg is used.
+For then, more whales are close round you than you can possibly
+chase at one time. But sperm whales are not every day encountered;
+while you may, then, you must kill all you can. And if you cannot
+kill them all at once, you must wing them, so that they can be
+afterwards killed at your leisure. Hence it is, that at times like
+these the drugg, comes into requisition. Our boat was furnished with
+three of them. The first and second were successfully darted, and we
+saw the whales staggeringly running off, fettered by the enormous
+sidelong resistance of the towing drugg. They were cramped like
+malefactors with the chain and ball. But upon flinging the third, in
+the act of tossing overboard the clumsy wooden block, it caught under
+one of the seats of the boat, and in an instant tore it out and
+carried it away, dropping the oarsman in the boat's bottom as the
+seat slid from under him. On both sides the sea came in at the
+wounded planks, but we stuffed two or three drawers and shirts in,
+and so stopped the leaks for the time.
+
+It had been next to impossible to dart these drugged-harpoons, were
+it not that as we advanced into the herd, our whale's way greatly
+diminished; moreover, that as we went still further and further from
+the circumference of commotion, the direful disorders seemed waning.
+So that when at last the jerking harpoon drew out, and the towing
+whale sideways vanished; then, with the tapering force of his parting
+momentum, we glided between two whales into the innermost heart of
+the shoal, as if from some mountain torrent we had slid into a serene
+valley lake. Here the storms in the roaring glens between the
+outermost whales, were heard but not felt. In this central expanse
+the sea presented that smooth satin-like surface, called a sleek,
+produced by the subtle moisture thrown off by the whale in his more
+quiet moods. Yes, we were now in that enchanted calm which they say
+lurks at the heart of every commotion. And still in the distracted
+distance we beheld the tumults of the outer concentric circles, and
+saw successive pods of whales, eight or ten in each, swiftly going
+round and round, like multiplied spans of horses in a ring; and so
+closely shoulder to shoulder, that a Titanic circus-rider might
+easily have over-arched the middle ones, and so have gone round on
+their backs. Owing to the density of the crowd of reposing whales,
+more immediately surrounding the embayed axis of the herd, no
+possible chance of escape was at present afforded us. We must watch
+for a breach in the living wall that hemmed us in; the wall that had
+only admitted us in order to shut us up. Keeping at the centre of
+the lake, we were occasionally visited by small tame cows and calves;
+the women and children of this routed host.
+
+Now, inclusive of the occasional wide intervals between the revolving
+outer circles, and inclusive of the spaces between the various pods
+in any one of those circles, the entire area at this juncture,
+embraced by the whole multitude, must have contained at least two or
+three square miles. At any rate--though indeed such a test at such a
+time might be deceptive--spoutings might be discovered from our low
+boat that seemed playing up almost from the rim of the horizon. I
+mention this circumstance, because, as if the cows and calves had
+been purposely locked up in this innermost fold; and as if the wide
+extent of the herd had hitherto prevented them from learning the
+precise cause of its stopping; or, possibly, being so young,
+unsophisticated, and every way innocent and inexperienced; however it
+may have been, these smaller whales--now and then visiting our
+becalmed boat from the margin of the lake--evinced a wondrous
+fearlessness and confidence, or else a still becharmed panic which it
+was impossible not to marvel at. Like household dogs they came
+snuffling round us, right up to our gunwales, and touching them; till
+it almost seemed that some spell had suddenly domesticated them.
+Queequeg patted their foreheads; Starbuck scratched their backs with
+his lance; but fearful of the consequences, for the time refrained
+from darting it.
+
+But far beneath this wondrous world upon the surface, another and
+still stranger world met our eyes as we gazed over the side. For,
+suspended in those watery vaults, floated the forms of the nursing
+mothers of the whales, and those that by their enormous girth seemed
+shortly to become mothers. The lake, as I have hinted, was to a
+considerable depth exceedingly transparent; and as human infants
+while suckling will calmly and fixedly gaze away from the breast, as
+if leading two different lives at the time; and while yet drawing
+mortal nourishment, be still spiritually feasting upon some unearthly
+reminiscence;--even so did the young of these whales seem looking up
+towards us, but not at us, as if we were but a bit of Gulfweed in
+their new-born sight. Floating on their sides, the mothers also
+seemed quietly eyeing us. One of these little infants, that from
+certain queer tokens seemed hardly a day old, might have measured
+some fourteen feet in length, and some six feet in girth. He was a
+little frisky; though as yet his body seemed scarce yet recovered
+from that irksome position it had so lately occupied in the maternal
+reticule; where, tail to head, and all ready for the final spring,
+the unborn whale lies bent like a Tartar's bow. The delicate
+side-fins, and the palms of his flukes, still freshly retained the
+plaited crumpled appearance of a baby's ears newly arrived from
+foreign parts.
+
+"Line! line!" cried Queequeg, looking over the gunwale; "him fast!
+him fast!--Who line him! Who struck?--Two whale; one big, one
+little!"
+
+"What ails ye, man?" cried Starbuck.
+
+"Look-e here," said Queequeg, pointing down.
+
+As when the stricken whale, that from the tub has reeled out hundreds
+of fathoms of rope; as, after deep sounding, he floats up again, and
+shows the slackened curling line buoyantly rising and spiralling
+towards the air; so now, Starbuck saw long coils of the umbilical
+cord of Madame Leviathan, by which the young cub seemed still
+tethered to its dam. Not seldom in the rapid vicissitudes of the
+chase, this natural line, with the maternal end loose, becomes
+entangled with the hempen one, so that the cub is thereby trapped.
+Some of the subtlest secrets of the seas seemed divulged to us in
+this enchanted pond. We saw young Leviathan amours in the deep.*
+
+
+*The sperm whale, as with all other species of the Leviathan, but
+unlike most other fish, breeds indifferently at all seasons; after a
+gestation which may probably be set down at nine months, producing
+but one at a time; though in some few known instances giving birth to
+an Esau and Jacob:--a contingency provided for in suckling by two
+teats, curiously situated, one on each side of the anus; but the
+breasts themselves extend upwards from that. When by chance these
+precious parts in a nursing whale are cut by the hunter's lance, the
+mother's pouring milk and blood rivallingly discolour the sea for
+rods. The milk is very sweet and rich; it has been tasted by man; it
+might do well with strawberries. When overflowing with mutual
+esteem, the whales salute MORE HOMINUM.
+
+
+And thus, though surrounded by circle upon circle of consternations
+and affrights, did these inscrutable creatures at the centre freely
+and fearlessly indulge in all peaceful concernments; yea, serenely
+revelled in dalliance and delight. But even so, amid the tornadoed
+Atlantic of my being, do I myself still for ever centrally disport in
+mute calm; and while ponderous planets of unwaning woe revolve round
+me, deep down and deep inland there I still bathe me in eternal
+mildness of joy.
+
+Meanwhile, as we thus lay entranced, the occasional sudden frantic
+spectacles in the distance evinced the activity of the other boats,
+still engaged in drugging the whales on the frontier of the host; or
+possibly carrying on the war within the first circle, where abundance
+of room and some convenient retreats were afforded them. But the
+sight of the enraged drugged whales now and then blindly darting to
+and fro across the circles, was nothing to what at last met our eyes.
+It is sometimes the custom when fast to a whale more than commonly
+powerful and alert, to seek to hamstring him, as it were, by
+sundering or maiming his gigantic tail-tendon. It is done by darting
+a short-handled cutting-spade, to which is attached a rope for
+hauling it back again. A whale wounded (as we afterwards learned) in
+this part, but not effectually, as it seemed, had broken away from
+the boat, carrying along with him half of the harpoon line; and in
+the extraordinary agony of the wound, he was now dashing among the
+revolving circles like the lone mounted desperado Arnold, at the
+battle of Saratoga, carrying dismay wherever he went.
+
+But agonizing as was the wound of this whale, and an appalling
+spectacle enough, any way; yet the peculiar horror with which he
+seemed to inspire the rest of the herd, was owing to a cause which at
+first the intervening distance obscured from us. But at length we
+perceived that by one of the unimaginable accidents of the fishery,
+this whale had become entangled in the harpoon-line that he towed; he
+had also run away with the cutting-spade in him; and while the free
+end of the rope attached to that weapon, had permanently caught in
+the coils of the harpoon-line round his tail, the cutting-spade
+itself had worked loose from his flesh. So that tormented to
+madness, he was now churning through the water, violently flailing
+with his flexible tail, and tossing the keen spade about him,
+wounding and murdering his own comrades.
+
+This terrific object seemed to recall the whole herd from their
+stationary fright. First, the whales forming the margin of our lake
+began to crowd a little, and tumble against each other, as if lifted
+by half spent billows from afar; then the lake itself began faintly
+to heave and swell; the submarine bridal-chambers and nurseries
+vanished; in more and more contracting orbits the whales in the more
+central circles began to swim in thickening clusters. Yes, the long
+calm was departing. A low advancing hum was soon heard; and then
+like to the tumultuous masses of block-ice when the great river
+Hudson breaks up in Spring, the entire host of whales came tumbling
+upon their inner centre, as if to pile themselves up in one common
+mountain. Instantly Starbuck and Queequeg changed places; Starbuck
+taking the stern.
+
+"Oars! Oars!" he intensely whispered, seizing the helm--"gripe your
+oars, and clutch your souls, now! My God, men, stand by! Shove him
+off, you Queequeg--the whale there!--prick him!--hit him! Stand
+up--stand up, and stay so! Spring, men--pull, men; never mind their
+backs--scrape them!--scrape away!"
+
+The boat was now all but jammed between two vast black bulks, leaving
+a narrow Dardanelles between their long lengths. But by desperate
+endeavor we at last shot into a temporary opening; then giving way
+rapidly, and at the same time earnestly watching for another outlet.
+After many similar hair-breadth escapes, we at last swiftly glided
+into what had just been one of the outer circles, but now crossed by
+random whales, all violently making for one centre. This lucky
+salvation was cheaply purchased by the loss of Queequeg's hat, who,
+while standing in the bows to prick the fugitive whales, had his hat
+taken clean from his head by the air-eddy made by the sudden tossing
+of a pair of broad flukes close by.
+
+Riotous and disordered as the universal commotion now was, it soon
+resolved itself into what seemed a systematic movement; for having
+clumped together at last in one dense body, they then renewed their
+onward flight with augmented fleetness. Further pursuit was useless;
+but the boats still lingered in their wake to pick up what drugged
+whales might be dropped astern, and likewise to secure one which
+Flask had killed and waifed. The waif is a pennoned pole, two or
+three of which are carried by every boat; and which, when additional
+game is at hand, are inserted upright into the floating body of a
+dead whale, both to mark its place on the sea, and also as token of
+prior possession, should the boats of any other ship draw near.
+
+The result of this lowering was somewhat illustrative of that
+sagacious saying in the Fishery,--the more whales the less fish. Of
+all the drugged whales only one was captured. The rest contrived to
+escape for the time, but only to be taken, as will hereafter be seen,
+by some other craft than the Pequod.
+
+
+
+CHAPTER 88
+
+Schools and Schoolmasters.
+
+
+The previous chapter gave account of an immense body or herd of Sperm
+Whales, and there was also then given the probable cause inducing
+those vast aggregations.
+
+Now, though such great bodies are at times encountered, yet, as must
+have been seen, even at the present day, small detached bands are
+occasionally observed, embracing from twenty to fifty individuals
+each. Such bands are known as schools. They generally are of two
+sorts; those composed almost entirely of females, and those mustering
+none but young vigorous males, or bulls, as they are familiarly
+designated.
+
+In cavalier attendance upon the school of females, you invariably see
+a male of full grown magnitude, but not old; who, upon any alarm,
+evinces his gallantry by falling in the rear and covering the flight
+of his ladies. In truth, this gentleman is a luxurious Ottoman,
+swimming about over the watery world, surroundingly accompanied by
+all the solaces and endearments of the harem. The contrast between
+this Ottoman and his concubines is striking; because, while he is
+always of the largest leviathanic proportions, the ladies, even at
+full growth, are not more than one-third of the bulk of an
+average-sized male. They are comparatively delicate, indeed; I dare
+say, not to exceed half a dozen yards round the waist. Nevertheless,
+it cannot be denied, that upon the whole they are hereditarily
+entitled to EMBONPOINT.
+
+It is very curious to watch this harem and its lord in their indolent
+ramblings. Like fashionables, they are for ever on the move in
+leisurely search of variety. You meet them on the Line in time for
+the full flower of the Equatorial feeding season, having just
+returned, perhaps, from spending the summer in the Northern seas, and
+so cheating summer of all unpleasant weariness and warmth. By the
+time they have lounged up and down the promenade of the Equator
+awhile, they start for the Oriental waters in anticipation of the
+cool season there, and so evade the other excessive temperature of
+the year.
+
+When serenely advancing on one of these journeys, if any strange
+suspicious sights are seen, my lord whale keeps a wary eye on his
+interesting family. Should any unwarrantably pert young Leviathan
+coming that way, presume to draw confidentially close to one of the
+ladies, with what prodigious fury the Bashaw assails him, and chases
+him away! High times, indeed, if unprincipled young rakes like him
+are to be permitted to invade the sanctity of domestic bliss; though
+do what the Bashaw will, he cannot keep the most notorious Lothario
+out of his bed; for, alas! all fish bed in common. As ashore, the
+ladies often cause the most terrible duels among their rival
+admirers; just so with the whales, who sometimes come to deadly
+battle, and all for love. They fence with their long lower jaws,
+sometimes locking them together, and so striving for the supremacy
+like elks that warringly interweave their antlers. Not a few are
+captured having the deep scars of these encounters,--furrowed heads,
+broken teeth, scolloped fins; and in some instances, wrenched and
+dislocated mouths.
+
+But supposing the invader of domestic bliss to betake himself away at
+the first rush of the harem's lord, then is it very diverting to
+watch that lord. Gently he insinuates his vast bulk among them again
+and revels there awhile, still in tantalizing vicinity to young
+Lothario, like pious Solomon devoutly worshipping among his thousand
+concubines. Granting other whales to be in sight, the fishermen
+will seldom give chase to one of these Grand Turks; for these Grand
+Turks are too lavish of their strength, and hence their unctuousness
+is small. As for the sons and the daughters they beget, why, those sons
+and daughters must take care of themselves; at least, with only the
+maternal help. For like certain other omnivorous roving lovers that
+might be named, my Lord Whale has no taste for the nursery, however
+much for the bower; and so, being a great traveller, he leaves his
+anonymous babies all over the world; every baby an exotic. In good
+time, nevertheless, as the ardour of youth declines; as years and
+dumps increase; as reflection lends her solemn pauses; in short, as a
+general lassitude overtakes the sated Turk; then a love of ease and
+virtue supplants the love for maidens; our Ottoman enters upon the
+impotent, repentant, admonitory stage of life, forswears, disbands
+the harem, and grown to an exemplary, sulky old soul, goes about all
+alone among the meridians and parallels saying his prayers, and
+warning each young Leviathan from his amorous errors.
+
+Now, as the harem of whales is called by the fishermen a school, so
+is the lord and master of that school technically known as the
+schoolmaster. It is therefore not in strict character, however
+admirably satirical, that after going to school himself, he should
+then go abroad inculcating not what he learned there, but the folly
+of it. His title, schoolmaster, would very naturally seem derived
+from the name bestowed upon the harem itself, but some have surmised
+that the man who first thus entitled this sort of Ottoman whale, must
+have read the memoirs of Vidocq, and informed himself what sort of a
+country-schoolmaster that famous Frenchman was in his younger days,
+and what was the nature of those occult lessons he inculcated into
+some of his pupils.
+
+The same secludedness and isolation to which the schoolmaster whale
+betakes himself in his advancing years, is true of all aged Sperm
+Whales. Almost universally, a lone whale--as a solitary Leviathan is
+called--proves an ancient one. Like venerable moss-bearded Daniel
+Boone, he will have no one near him but Nature herself; and her he
+takes to wife in the wilderness of waters, and the best of wives she
+is, though she keeps so many moody secrets.
+
+The schools composing none but young and vigorous males, previously
+mentioned, offer a strong contrast to the harem schools. For while
+those female whales are characteristically timid, the young males, or
+forty-barrel-bulls, as they call them, are by far the most pugnacious
+of all Leviathans, and proverbially the most dangerous to encounter;
+excepting those wondrous grey-headed, grizzled whales, sometimes met,
+and these will fight you like grim fiends exasperated by a penal
+gout.
+
+The Forty-barrel-bull schools are larger than the harem schools.
+Like a mob of young collegians, they are full of fight, fun, and
+wickedness, tumbling round the world at such a reckless, rollicking
+rate, that no prudent underwriter would insure them any more than he
+would a riotous lad at Yale or Harvard. They soon relinquish this
+turbulence though, and when about three-fourths grown, break up, and
+separately go about in quest of settlements, that is, harems.
+
+Another point of difference between the male and female schools is
+still more characteristic of the sexes. Say you strike a
+Forty-barrel-bull--poor devil! all his comrades quit him. But strike
+a member of the harem school, and her companions swim around her with
+every token of concern, sometimes lingering so near her and so long,
+as themselves to fall a prey.
+
+
+
+CHAPTER 89
+
+Fast-Fish and Loose-Fish.
+
+
+The allusion to the waif and waif-poles in the last chapter but one,
+necessitates some account of the laws and regulations of the whale
+fishery, of which the waif may be deemed the grand symbol and badge.
+
+It frequently happens that when several ships are cruising in
+company, a whale may be struck by one vessel, then escape, and be
+finally killed and captured by another vessel; and herein are
+indirectly comprised many minor contingencies, all partaking of this
+one grand feature. For example,--after a weary and perilous chase
+and capture of a whale, the body may get loose from the ship by
+reason of a violent storm; and drifting far away to leeward, be
+retaken by a second whaler, who, in a calm, snugly tows it alongside,
+without risk of life or line. Thus the most vexatious and violent
+disputes would often arise between the fishermen, were there not some
+written or unwritten, universal, undisputed law applicable to all
+cases.
+
+Perhaps the only formal whaling code authorized by legislative
+enactment, was that of Holland. It was decreed by the States-General
+in A.D. 1695. But though no other nation has ever had any written
+whaling law, yet the American fishermen have been their own
+legislators and lawyers in this matter. They have provided a system
+which for terse comprehensiveness surpasses Justinian's Pandects and
+the By-laws of the Chinese Society for the Suppression of Meddling
+with other People's Business. Yes; these laws might be engraven on a
+Queen Anne's forthing, or the barb of a harpoon, and worn round the
+neck, so small are they.
+
+I. A Fast-Fish belongs to the party fast to it.
+
+II. A Loose-Fish is fair game for anybody who can soonest catch it.
+
+But what plays the mischief with this masterly code is the admirable
+brevity of it, which necessitates a vast volume of commentaries to
+expound it.
+
+First: What is a Fast-Fish? Alive or dead a fish is technically
+fast, when it is connected with an occupied ship or boat, by any
+medium at all controllable by the occupant or occupants,--a mast, an
+oar, a nine-inch cable, a telegraph wire, or a strand of cobweb, it
+is all the same. Likewise a fish is technically fast when it bears a
+waif, or any other recognised symbol of possession; so long as the
+party waifing it plainly evince their ability at any time to take it
+alongside, as well as their intention so to do.
+
+These are scientific commentaries; but the commentaries of the
+whalemen themselves sometimes consist in hard words and harder
+knocks--the Coke-upon-Littleton of the fist. True, among the more
+upright and honourable whalemen allowances are always made for
+peculiar cases, where it would be an outrageous moral injustice for
+one party to claim possession of a whale previously chased or killed
+by another party. But others are by no means so scrupulous.
+
+Some fifty years ago there was a curious case of whale-trover
+litigated in England, wherein the plaintiffs set forth that after a
+hard chase of a whale in the Northern seas; and when indeed they (the
+plaintiffs) had succeeded in harpooning the fish; they were at last,
+through peril of their lives, obliged to forsake not only their
+lines, but their boat itself. Ultimately the defendants (the crew of
+another ship) came up with the whale, struck, killed, seized, and
+finally appropriated it before the very eyes of the plaintiffs. And
+when those defendants were remonstrated with, their captain snapped
+his fingers in the plaintiffs' teeth, and assured them that by way of
+doxology to the deed he had done, he would now retain their line,
+harpoons, and boat, which had remained attached to the whale at the
+time of the seizure. Wherefore the plaintiffs now sued for the
+recovery of the value of their whale, line, harpoons, and boat.
+
+Mr. Erskine was counsel for the defendants; Lord Ellenborough was the
+judge. In the course of the defence, the witty Erskine went on to
+illustrate his position, by alluding to a recent crim. con. case,
+wherein a gentleman, after in vain trying to bridle his wife's
+viciousness, had at last abandoned her upon the seas of life; but in
+the course of years, repenting of that step, he instituted an action
+to recover possession of her. Erskine was on the other side; and he
+then supported it by saying, that though the gentleman had originally
+harpooned the lady, and had once had her fast, and only by reason of
+the great stress of her plunging viciousness, had at last abandoned
+her; yet abandon her he did, so that she became a loose-fish; and
+therefore when a subsequent gentleman re-harpooned her, the lady then
+became that subsequent gentleman's property, along with whatever
+harpoon might have been found sticking in her.
+
+Now in the present case Erskine contended that the examples of the
+whale and the lady were reciprocally illustrative of each other.
+
+These pleadings, and the counter pleadings, being duly heard, the
+very learned Judge in set terms decided, to wit,--That as for the
+boat, he awarded it to the plaintiffs, because they had merely
+abandoned it to save their lives; but that with regard to the
+controverted whale, harpoons, and line, they belonged to the
+defendants; the whale, because it was a Loose-Fish at the time of the
+final capture; and the harpoons and line because when the fish made
+off with them, it (the fish) acquired a property in those articles;
+and hence anybody who afterwards took the fish had a right to them.
+Now the defendants afterwards took the fish; ergo, the aforesaid
+articles were theirs.
+
+A common man looking at this decision of the very learned Judge,
+might possibly object to it. But ploughed up to the primary rock of
+the matter, the two great principles laid down in the twin whaling
+laws previously quoted, and applied and elucidated by Lord
+Ellenborough in the above cited case; these two laws touching
+Fast-Fish and Loose-Fish, I say, will, on reflection, be found the
+fundamentals of all human jurisprudence; for notwithstanding its
+complicated tracery of sculpture, the Temple of the Law, like the
+Temple of the Philistines, has but two props to stand on.
+
+Is it not a saying in every one's mouth, Possession is half of the
+law: that is, regardless of how the thing came into possession? But
+often possession is the whole of the law. What are the sinews and
+souls of Russian serfs and Republican slaves but Fast-Fish, whereof
+possession is the whole of the law? What to the rapacious landlord
+is the widow's last mite but a Fast-Fish? What is yonder undetected
+villain's marble mansion with a door-plate for a waif; what is that
+but a Fast-Fish? What is the ruinous discount which Mordecai, the
+broker, gets from poor Woebegone, the bankrupt, on a loan to
+keep Woebegone's family from starvation; what is that ruinous
+discount but a Fast-Fish? What is the Archbishop of Savesoul's
+income of L100,000 seized from the scant bread and cheese of
+hundreds of thousands of broken-backed laborers (all sure of heaven
+without any of Savesoul's help) what is that globular L100,000 but a
+Fast-Fish? What are the Duke of Dunder's hereditary towns and
+hamlets but Fast-Fish? What to that redoubted harpooneer, John Bull,
+is poor Ireland, but a Fast-Fish? What to that apostolic lancer,
+Brother Jonathan, is Texas but a Fast-Fish? And concerning all
+these, is not Possession the whole of the law?
+
+But if the doctrine of Fast-Fish be pretty generally applicable, the
+kindred doctrine of Loose-Fish is still more widely so. That is
+internationally and universally applicable.
+
+What was America in 1492 but a Loose-Fish, in which Columbus struck
+the Spanish standard by way of waifing it for his royal master and
+mistress? What was Poland to the Czar? What Greece to the Turk?
+What India to England? What at last will Mexico be to the United
+States? All Loose-Fish.
+
+What are the Rights of Man and the Liberties of the World but
+Loose-Fish? What all men's minds and opinions but Loose-Fish? What
+is the principle of religious belief in them but a Loose-Fish? What
+to the ostentatious smuggling verbalists are the thoughts of thinkers
+but Loose-Fish? What is the great globe itself but a Loose-Fish?
+And what are you, reader, but a Loose-Fish and a Fast-Fish, too?
+
+
+
+CHAPTER 90
+
+Heads or Tails.
+
+
+"De balena vero sufficit, si rex habeat caput, et regina caudam."
+BRACTON, L. 3, C. 3.
+
+
+Latin from the books of the Laws of England, which taken along with
+the context, means, that of all whales captured by anybody on the
+coast of that land, the King, as Honourary Grand Harpooneer, must have
+the head, and the Queen be respectfully presented with the tail. A
+division which, in the whale, is much like halving an apple; there is
+no intermediate remainder. Now as this law, under a modified form,
+is to this day in force in England; and as it offers in various
+respects a strange anomaly touching the general law of Fast and
+Loose-Fish, it is here treated of in a separate chapter, on the same
+courteous principle that prompts the English railways to be at the
+expense of a separate car, specially reserved for the accommodation
+of royalty. In the first place, in curious proof of the fact that
+the above-mentioned law is still in force, I proceed to lay before
+you a circumstance that happened within the last two years.
+
+It seems that some honest mariners of Dover, or Sandwich, or some one
+of the Cinque Ports, had after a hard chase succeeded in killing and
+beaching a fine whale which they had originally descried afar off
+from the shore. Now the Cinque Ports are partially or somehow under
+the jurisdiction of a sort of policeman or beadle, called a Lord
+Warden. Holding the office directly from the crown, I believe, all
+the royal emoluments incident to the Cinque Port territories become
+by assignment his. By some writers this office is called a sinecure.
+But not so. Because the Lord Warden is busily employed at times in
+fobbing his perquisites; which are his chiefly by virtue of that same
+fobbing of them.
+
+Now when these poor sun-burnt mariners, bare-footed, and with their
+trowsers rolled high up on their eely legs, had wearily hauled their
+fat fish high and dry, promising themselves a good L150 from the
+precious oil and bone; and in fantasy sipping rare tea with their
+wives, and good ale with their cronies, upon the strength of their
+respective shares; up steps a very learned and most Christian and
+charitable gentleman, with a copy of Blackstone under his arm; and
+laying it upon the whale's head, he says--"Hands off! this fish, my
+masters, is a Fast-Fish. I seize it as the Lord Warden's." Upon
+this the poor mariners in their respectful consternation--so truly
+English--knowing not what to say, fall to vigorously scratching their
+heads all round; meanwhile ruefully glancing from the whale to the
+stranger. But that did in nowise mend the matter, or at all soften
+the hard heart of the learned gentleman with the copy of Blackstone.
+At length one of them, after long scratching about for his ideas,
+made bold to speak,
+
+"Please, sir, who is the Lord Warden?"
+
+"The Duke."
+
+"But the duke had nothing to do with taking this fish?"
+
+"It is his."
+
+"We have been at great trouble, and peril, and some expense, and is
+all that to go to the Duke's benefit; we getting nothing at all for
+our pains but our blisters?"
+
+"It is his."
+
+"Is the Duke so very poor as to be forced to this desperate mode of
+getting a livelihood?"
+
+"It is his."
+
+"I thought to relieve my old bed-ridden mother by part of my share of
+this whale."
+
+"It is his."
+
+"Won't the Duke be content with a quarter or a half?"
+
+"It is his."
+
+In a word, the whale was seized and sold, and his Grace the Duke of
+Wellington received the money. Thinking that viewed in some
+particular lights, the case might by a bare possibility in some small
+degree be deemed, under the circumstances, a rather hard one, an
+honest clergyman of the town respectfully addressed a note to his
+Grace, begging him to take the case of those unfortunate mariners
+into full consideration. To which my Lord Duke in substance replied
+(both letters were published) that he had already done so, and
+received the money, and would be obliged to the reverend gentleman if
+for the future he (the reverend gentleman) would decline meddling
+with other people's business. Is this the still militant old man,
+standing at the corners of the three kingdoms, on all hands coercing
+alms of beggars?
+
+It will readily be seen that in this case the alleged right of the
+Duke to the whale was a delegated one from the Sovereign. We must
+needs inquire then on what principle the Sovereign is originally
+invested with that right. The law itself has already been set forth.
+But Plowdon gives us the reason for it. Says Plowdon, the whale so
+caught belongs to the King and Queen, "because of its superior
+excellence." And by the soundest commentators this has ever been
+held a cogent argument in such matters.
+
+But why should the King have the head, and the Queen the tail? A
+reason for that, ye lawyers!
+
+In his treatise on "Queen-Gold," or Queen-pinmoney, an old King's
+Bench author, one William Prynne, thus discourseth: "Ye tail is ye
+Queen's, that ye Queen's wardrobe may be supplied with ye whalebone."
+Now this was written at a time when the black limber bone of the
+Greenland or Right whale was largely used in ladies' bodices. But
+this same bone is not in the tail; it is in the head, which is a sad
+mistake for a sagacious lawyer like Prynne. But is the Queen a
+mermaid, to be presented with a tail? An allegorical meaning may
+lurk here.
+
+There are two royal fish so styled by the English law writers--the
+whale and the sturgeon; both royal property under certain
+limitations, and nominally supplying the tenth branch of the crown's
+ordinary revenue. I know not that any other author has hinted of the
+matter; but by inference it seems to me that the sturgeon must be
+divided in the same way as the whale, the King receiving the highly
+dense and elastic head peculiar to that fish, which, symbolically
+regarded, may possibly be humorously grounded upon some presumed
+congeniality. And thus there seems a reason in all things, even in
+law.
+
+
+
+CHAPTER 91
+
+The Pequod Meets The Rose-Bud.
+
+
+"In vain it was to rake for Ambergriese in the paunch of this
+Leviathan, insufferable fetor denying not inquiry."
+SIR T. BROWNE, V.E.
+
+
+It was a week or two after the last whaling scene recounted, and when
+we were slowly sailing over a sleepy, vapoury, mid-day sea, that the
+many noses on the Pequod's deck proved more vigilant discoverers than
+the three pairs of eyes aloft. A peculiar and not very pleasant
+smell was smelt in the sea.
+
+"I will bet something now," said Stubb, "that somewhere hereabouts
+are some of those drugged whales we tickled the other day. I thought
+they would keel up before long."
+
+Presently, the vapours in advance slid aside; and there in the
+distance lay a ship, whose furled sails betokened that some sort of
+whale must be alongside. As we glided nearer, the stranger showed
+French colours from his peak; and by the eddying cloud of vulture
+sea-fowl that circled, and hovered, and swooped around him, it was
+plain that the whale alongside must be what the fishermen call a
+blasted whale, that is, a whale that has died unmolested on the sea,
+and so floated an unappropriated corpse. It may well be conceived,
+what an unsavory odor such a mass must exhale; worse than an Assyrian
+city in the plague, when the living are incompetent to bury the
+departed. So intolerable indeed is it regarded by some, that no
+cupidity could persuade them to moor alongside of it. Yet are there
+those who will still do it; notwithstanding the fact that the oil
+obtained from such subjects is of a very inferior quality, and by no
+means of the nature of attar-of-rose.
+
+Coming still nearer with the expiring breeze, we saw that the
+Frenchman had a second whale alongside; and this second whale seemed
+even more of a nosegay than the first. In truth, it turned out to be
+one of those problematical whales that seem to dry up and die with a
+sort of prodigious dyspepsia, or indigestion; leaving their defunct
+bodies almost entirely bankrupt of anything like oil. Nevertheless,
+in the proper place we shall see that no knowing fisherman will ever
+turn up his nose at such a whale as this, however much he may shun
+blasted whales in general.
+
+The Pequod had now swept so nigh to the stranger, that Stubb vowed he
+recognised his cutting spade-pole entangled in the lines that were
+knotted round the tail of one of these whales.
+
+"There's a pretty fellow, now," he banteringly laughed, standing in
+the ship's bows, "there's a jackal for ye! I well know that these
+Crappoes of Frenchmen are but poor devils in the fishery; sometimes
+lowering their boats for breakers, mistaking them for Sperm Whale
+spouts; yes, and sometimes sailing from their port with their hold
+full of boxes of tallow candles, and cases of snuffers, foreseeing
+that all the oil they will get won't be enough to dip the Captain's
+wick into; aye, we all know these things; but look ye, here's a
+Crappo that is content with our leavings, the drugged whale there, I
+mean; aye, and is content too with scraping the dry bones of that
+other precious fish he has there. Poor devil! I say, pass round a
+hat, some one, and let's make him a present of a little oil for dear
+charity's sake. For what oil he'll get from that drugged whale
+there, wouldn't be fit to burn in a jail; no, not in a condemned
+cell. And as for the other whale, why, I'll agree to get more oil by
+chopping up and trying out these three masts of ours, than he'll get
+from that bundle of bones; though, now that I think of it, it may
+contain something worth a good deal more than oil; yes, ambergris. I
+wonder now if our old man has thought of that. It's worth trying.
+Yes, I'm for it;" and so saying he started for the quarter-deck.
+
+By this time the faint air had become a complete calm; so that
+whether or no, the Pequod was now fairly entrapped in the smell, with
+no hope of escaping except by its breezing up again. Issuing from
+the cabin, Stubb now called his boat's crew, and pulled off for the
+stranger. Drawing across her bow, he perceived that in accordance
+with the fanciful French taste, the upper part of her stem-piece was
+carved in the likeness of a huge drooping stalk, was painted green,
+and for thorns had copper spikes projecting from it here and there;
+the whole terminating in a symmetrical folded bulb of a bright red
+colour. Upon her head boards, in large gilt letters, he read "Bouton
+de Rose,"--Rose-button, or Rose-bud; and this was the romantic name
+of this aromatic ship.
+
+Though Stubb did not understand the BOUTON part of the inscription,
+yet the word ROSE, and the bulbous figure-head put together,
+sufficiently explained the whole to him.
+
+"A wooden rose-bud, eh?" he cried with his hand to his nose, "that
+will do very well; but how like all creation it smells!"
+
+Now in order to hold direct communication with the people on deck, he
+had to pull round the bows to the starboard side, and thus come close
+to the blasted whale; and so talk over it.
+
+Arrived then at this spot, with one hand still to his nose, he
+bawled--"Bouton-de-Rose, ahoy! are there any of you Bouton-de-Roses
+that speak English?"
+
+"Yes," rejoined a Guernsey-man from the bulwarks, who turned out to
+be the chief-mate.
+
+"Well, then, my Bouton-de-Rose-bud, have you seen the White Whale?"
+
+"WHAT whale?"
+
+"The WHITE Whale--a Sperm Whale--Moby Dick, have ye seen him?
+
+"Never heard of such a whale. Cachalot Blanche! White Whale--no."
+
+"Very good, then; good bye now, and I'll call again in a minute."
+
+Then rapidly pulling back towards the Pequod, and seeing Ahab leaning
+over the quarter-deck rail awaiting his report, he moulded his two
+hands into a trumpet and shouted--"No, Sir! No!" Upon which Ahab
+retired, and Stubb returned to the Frenchman.
+
+He now perceived that the Guernsey-man, who had just got into the
+chains, and was using a cutting-spade, had slung his nose in a sort
+of bag.
+
+"What's the matter with your nose, there?" said Stubb. "Broke it?"
+
+"I wish it was broken, or that I didn't have any nose at all!"
+answered the Guernsey-man, who did not seem to relish the job he was
+at very much. "But what are you holding YOURS for?"
+
+"Oh, nothing! It's a wax nose; I have to hold it on. Fine day,
+ain't it? Air rather gardenny, I should say; throw us a bunch of
+posies, will ye, Bouton-de-Rose?"
+
+"What in the devil's name do you want here?" roared the Guernseyman,
+flying into a sudden passion.
+
+"Oh! keep cool--cool? yes, that's the word! why don't you pack those
+whales in ice while you're working at 'em? But joking aside, though;
+do you know, Rose-bud, that it's all nonsense trying to get any oil
+out of such whales? As for that dried up one, there, he hasn't a
+gill in his whole carcase."
+
+"I know that well enough; but, d'ye see, the Captain here won't
+believe it; this is his first voyage; he was a Cologne manufacturer
+before. But come aboard, and mayhap he'll believe you, if he won't
+me; and so I'll get out of this dirty scrape."
+
+"Anything to oblige ye, my sweet and pleasant fellow," rejoined
+Stubb, and with that he soon mounted to the deck. There a queer
+scene presented itself. The sailors, in tasselled caps of red
+worsted, were getting the heavy tackles in readiness for the whales.
+But they worked rather slow and talked very fast, and seemed in
+anything but a good humor. All their noses upwardly projected from
+their faces like so many jib-booms. Now and then pairs of them would
+drop their work, and run up to the mast-head to get some fresh air.
+Some thinking they would catch the plague, dipped oakum in coal-tar,
+and at intervals held it to their nostrils. Others having broken the
+stems of their pipes almost short off at the bowl, were vigorously
+puffing tobacco-smoke, so that it constantly filled their
+olfactories.
+
+Stubb was struck by a shower of outcries and anathemas proceeding
+from the Captain's round-house abaft; and looking in that direction
+saw a fiery face thrust from behind the door, which was held ajar
+from within. This was the tormented surgeon, who, after in vain
+remonstrating against the proceedings of the day, had betaken himself
+to the Captain's round-house (CABINET he called it) to avoid the
+pest; but still, could not help yelling out his entreaties and
+indignations at times.
+
+Marking all this, Stubb argued well for his scheme, and turning to
+the Guernsey-man had a little chat with him, during which the
+stranger mate expressed his detestation of his Captain as a conceited
+ignoramus, who had brought them all into so unsavory and unprofitable
+a pickle. Sounding him carefully, Stubb further perceived that the
+Guernsey-man had not the slightest suspicion concerning the
+ambergris. He therefore held his peace on that head, but otherwise
+was quite frank and confidential with him, so that the two quickly
+concocted a little plan for both circumventing and satirizing the
+Captain, without his at all dreaming of distrusting their sincerity.
+According to this little plan of theirs, the Guernsey-man, under
+cover of an interpreter's office, was to tell the Captain what he
+pleased, but as coming from Stubb; and as for Stubb, he was to utter
+any nonsense that should come uppermost in him during the interview.
+
+By this time their destined victim appeared from his cabin. He was a
+small and dark, but rather delicate looking man for a sea-captain,
+with large whiskers and moustache, however; and wore a red cotton
+velvet vest with watch-seals at his side. To this gentleman, Stubb
+was now politely introduced by the Guernsey-man, who at once
+ostentatiously put on the aspect of interpreting between them.
+
+"What shall I say to him first?" said he.
+
+"Why," said Stubb, eyeing the velvet vest and the watch and seals,
+"you may as well begin by telling him that he looks a sort of babyish
+to me, though I don't pretend to be a judge."
+
+"He says, Monsieur," said the Guernsey-man, in French, turning to his
+captain, "that only yesterday his ship spoke a vessel, whose captain
+and chief-mate, with six sailors, had all died of a fever caught from
+a blasted whale they had brought alongside."
+
+Upon this the captain started, and eagerly desired to know more.
+
+"What now?" said the Guernsey-man to Stubb.
+
+"Why, since he takes it so easy, tell him that now I have eyed him
+carefully, I'm quite certain that he's no more fit to command a
+whale-ship than a St. Jago monkey. In fact, tell him from me he's a
+baboon."
+
+"He vows and declares, Monsieur, that the other whale, the dried one,
+is far more deadly than the blasted one; in fine, Monsieur, he
+conjures us, as we value our lives, to cut loose from these fish."
+
+Instantly the captain ran forward, and in a loud voice commanded his
+crew to desist from hoisting the cutting-tackles, and at once cast
+loose the cables and chains confining the whales to the ship.
+
+"What now?" said the Guernsey-man, when the Captain had returned to
+them.
+
+"Why, let me see; yes, you may as well tell him now that--that--in
+fact, tell him I've diddled him, and (aside to himself) perhaps
+somebody else."
+
+"He says, Monsieur, that he's very happy to have been of any service
+to us."
+
+Hearing this, the captain vowed that they were the grateful parties
+(meaning himself and mate) and concluded by inviting Stubb down
+into his cabin to drink a bottle of Bordeaux.
+
+"He wants you to take a glass of wine with him," said the
+interpreter.
+
+"Thank him heartily; but tell him it's against my principles to drink
+with the man I've diddled. In fact, tell him I must go."
+
+"He says, Monsieur, that his principles won't admit of his drinking;
+but that if Monsieur wants to live another day to drink, then
+Monsieur had best drop all four boats, and pull the ship away from
+these whales, for it's so calm they won't drift."
+
+By this time Stubb was over the side, and getting into his boat,
+hailed the Guernsey-man to this effect,--that having a long tow-line
+in his boat, he would do what he could to help them, by pulling out
+the lighter whale of the two from the ship's side. While the
+Frenchman's boats, then, were engaged in towing the ship one way,
+Stubb benevolently towed away at his whale the other way,
+ostentatiously slacking out a most unusually long tow-line.
+
+Presently a breeze sprang up; Stubb feigned to cast off from the
+whale; hoisting his boats, the Frenchman soon increased his distance,
+while the Pequod slid in between him and Stubb's whale. Whereupon
+Stubb quickly pulled to the floating body, and hailing the Pequod to
+give notice of his intentions, at once proceeded to reap the fruit of
+his unrighteous cunning. Seizing his sharp boat-spade, he commenced
+an excavation in the body, a little behind the side fin. You would
+almost have thought he was digging a cellar there in the sea; and
+when at length his spade struck against the gaunt ribs, it was like
+turning up old Roman tiles and pottery buried in fat English loam.
+His boat's crew were all in high excitement, eagerly helping their
+chief, and looking as anxious as gold-hunters.
+
+And all the time numberless fowls were diving, and ducking, and
+screaming, and yelling, and fighting around them. Stubb was
+beginning to look disappointed, especially as the horrible nosegay
+increased, when suddenly from out the very heart of this plague,
+there stole a faint stream of perfume, which flowed through the tide
+of bad smells without being absorbed by it, as one river will flow
+into and then along with another, without at all blending with it for
+a time.
+
+"I have it, I have it," cried Stubb, with delight, striking something
+in the subterranean regions, "a purse! a purse!"
+
+Dropping his spade, he thrust both hands in, and drew out handfuls of
+something that looked like ripe Windsor soap, or rich mottled old
+cheese; very unctuous and savory withal. You might easily dent it
+with your thumb; it is of a hue between yellow and ash colour. And
+this, good friends, is ambergris, worth a gold guinea an ounce to any
+druggist. Some six handfuls were obtained; but more was unavoidably
+lost in the sea, and still more, perhaps, might have been secured
+were it not for impatient Ahab's loud command to Stubb to desist, and
+come on board, else the ship would bid them good bye.
+
+
+
+CHAPTER 92
+
+Ambergris.
+
+
+Now this ambergris is a very curious substance, and so important as
+an article of commerce, that in 1791 a certain Nantucket-born Captain
+Coffin was examined at the bar of the English House of Commons on
+that subject. For at that time, and indeed until a comparatively
+late day, the precise origin of ambergris remained, like amber
+itself, a problem to the learned. Though the word ambergris is but
+the French compound for grey amber, yet the two substances are quite
+distinct. For amber, though at times found on the sea-coast, is also
+dug up in some far inland soils, whereas ambergris is never found
+except upon the sea. Besides, amber is a hard, transparent, brittle,
+odorless substance, used for mouth-pieces to pipes, for beads and
+ornaments; but ambergris is soft, waxy, and so highly fragrant and
+spicy, that it is largely used in perfumery, in pastiles, precious
+candles, hair-powders, and pomatum. The Turks use it in cooking, and
+also carry it to Mecca, for the same purpose that frankincense is
+carried to St. Peter's in Rome. Some wine merchants drop a few
+grains into claret, to flavor it.
+
+Who would think, then, that such fine ladies and gentlemen should
+regale themselves with an essence found in the inglorious bowels of a
+sick whale! Yet so it is. By some, ambergris is supposed to be the
+cause, and by others the effect, of the dyspepsia in the whale. How
+to cure such a dyspepsia it were hard to say, unless by administering
+three or four boat loads of Brandreth's pills, and then running out
+of harm's way, as laborers do in blasting rocks.
+
+I have forgotten to say that there were found in this ambergris,
+certain hard, round, bony plates, which at first Stubb thought might
+be sailors' trowsers buttons; but it afterwards turned out that they
+were nothing more than pieces of small squid bones embalmed in that
+manner.
+
+Now that the incorruption of this most fragrant ambergris should be
+found in the heart of such decay; is this nothing? Bethink thee of
+that saying of St. Paul in Corinthians, about corruption and
+incorruption; how that we are sown in dishonour, but raised in glory.
+And likewise call to mind that saying of Paracelsus about what it is
+that maketh the best musk. Also forget not the strange fact that of
+all things of ill-savor, Cologne-water, in its rudimental
+manufacturing stages, is the worst.
+
+I should like to conclude the chapter with the above appeal, but
+cannot, owing to my anxiety to repel a charge often made against
+whalemen, and which, in the estimation of some already biased minds,
+might be considered as indirectly substantiated by what has been said
+of the Frenchman's two whales. Elsewhere in this volume the
+slanderous aspersion has been disproved, that the vocation of whaling
+is throughout a slatternly, untidy business. But there is another
+thing to rebut. They hint that all whales always smell bad. Now how
+did this odious stigma originate?
+
+I opine, that it is plainly traceable to the first arrival of the
+Greenland whaling ships in London, more than two centuries ago.
+Because those whalemen did not then, and do not now, try out their
+oil at sea as the Southern ships have always done; but cutting up the
+fresh blubber in small bits, thrust it through the bung holes of
+large casks, and carry it home in that manner; the shortness of the
+season in those Icy Seas, and the sudden and violent storms to which
+they are exposed, forbidding any other course. The consequence is,
+that upon breaking into the hold, and unloading one of these whale
+cemeteries, in the Greenland dock, a savor is given forth somewhat
+similar to that arising from excavating an old city grave-yard, for
+the foundations of a Lying-in-Hospital.
+
+I partly surmise also, that this wicked charge against whalers may be
+likewise imputed to the existence on the coast of Greenland, in
+former times, of a Dutch village called Schmerenburgh or Smeerenberg,
+which latter name is the one used by the learned Fogo Von Slack, in
+his great work on Smells, a text-book on that subject. As its name
+imports (smeer, fat; berg, to put up), this village was founded in
+order to afford a place for the blubber of the Dutch whale fleet to
+be tried out, without being taken home to Holland for that purpose.
+It was a collection of furnaces, fat-kettles, and oil sheds; and when
+the works were in full operation certainly gave forth no very
+pleasant savor. But all this is quite different with a South Sea
+Sperm Whaler; which in a voyage of four years perhaps, after
+completely filling her hold with oil, does not, perhaps, consume
+fifty days in the business of boiling out; and in the state that it
+is casked, the oil is nearly scentless. The truth is, that living or
+dead, if but decently treated, whales as a species are by no means
+creatures of ill odor; nor can whalemen be recognised, as the people
+of the middle ages affected to detect a Jew in the company, by the
+nose. Nor indeed can the whale possibly be otherwise than fragrant,
+when, as a general thing, he enjoys such high health; taking
+abundance of exercise; always out of doors; though, it is true,
+seldom in the open air. I say, that the motion of a Sperm Whale's
+flukes above water dispenses a perfume, as when a musk-scented lady
+rustles her dress in a warm parlor. What then shall I liken the
+Sperm Whale to for fragrance, considering his magnitude? Must it not
+be to that famous elephant, with jewelled tusks, and redolent with
+myrrh, which was led out of an Indian town to do honour to Alexander
+the Great?
+
+
+
+CHAPTER 93
+
+The Castaway.
+
+
+It was but some few days after encountering the Frenchman, that a
+most significant event befell the most insignificant of the Pequod's
+crew; an event most lamentable; and which ended in providing the
+sometimes madly merry and predestinated craft with a living and ever
+accompanying prophecy of whatever shattered sequel might prove her
+own.
+
+Now, in the whale ship, it is not every one that goes in the boats.
+Some few hands are reserved called ship-keepers, whose province it is
+to work the vessel while the boats are pursuing the whale. As a
+general thing, these ship-keepers are as hardy fellows as the men
+comprising the boats' crews. But if there happen to be an unduly
+slender, clumsy, or timorous wight in the ship, that wight is certain
+to be made a ship-keeper. It was so in the Pequod with the little
+negro Pippin by nick-name, Pip by abbreviation. Poor Pip! ye have
+heard of him before; ye must remember his tambourine on that dramatic
+midnight, so gloomy-jolly.
+
+In outer aspect, Pip and Dough-Boy made a match, like a black pony
+and a white one, of equal developments, though of dissimilar colour,
+driven in one eccentric span. But while hapless Dough-Boy was by
+nature dull and torpid in his intellects, Pip, though over
+tender-hearted, was at bottom very bright, with that pleasant,
+genial, jolly brightness peculiar to his tribe; a tribe, which ever
+enjoy all holidays and festivities with finer, freer relish than any
+other race. For blacks, the year's calendar should show naught but
+three hundred and sixty-five Fourth of Julys and New Year's Days.
+Nor smile so, while I write that this little black was brilliant, for
+even blackness has its brilliancy; behold yon lustrous ebony,
+panelled in king's cabinets. But Pip loved life, and all life's
+peaceable securities; so that the panic-striking business in which he
+had somehow unaccountably become entrapped, had most sadly blurred
+his brightness; though, as ere long will be seen, what was thus
+temporarily subdued in him, in the end was destined to be luridly
+illumined by strange wild fires, that fictitiously showed him off to
+ten times the natural lustre with which in his native Tolland County
+in Connecticut, he had once enlivened many a fiddler's frolic on the
+green; and at melodious even-tide, with his gay ha-ha! had turned the
+round horizon into one star-belled tambourine. So, though in the
+clear air of day, suspended against a blue-veined neck, the
+pure-watered diamond drop will healthful glow; yet, when the cunning
+jeweller would show you the diamond in its most impressive lustre, he
+lays it against a gloomy ground, and then lights it up, not by the
+sun, but by some unnatural gases. Then come out those fiery
+effulgences, infernally superb; then the evil-blazing diamond, once
+the divinest symbol of the crystal skies, looks like some crown-jewel
+stolen from the King of Hell. But let us to the story.
+
+It came to pass, that in the ambergris affair Stubb's after-oarsman
+chanced so to sprain his hand, as for a time to become quite maimed;
+and, temporarily, Pip was put into his place.
+
+The first time Stubb lowered with him, Pip evinced much nervousness;
+but happily, for that time, escaped close contact with the whale; and
+therefore came off not altogether discreditably; though Stubb
+observing him, took care, afterwards, to exhort him to cherish his
+courageousness to the utmost, for he might often find it needful.
+
+Now upon the second lowering, the boat paddled upon the whale; and as
+the fish received the darted iron, it gave its customary rap, which
+happened, in this instance, to be right under poor Pip's seat. The
+involuntary consternation of the moment caused him to leap, paddle in
+hand, out of the boat; and in such a way, that part of the slack
+whale line coming against his chest, he breasted it overboard with
+him, so as to become entangled in it, when at last plumping into the
+water. That instant the stricken whale started on a fierce run, the
+line swiftly straightened; and presto! poor Pip came all foaming up
+to the chocks of the boat, remorselessly dragged there by the line,
+which had taken several turns around his chest and neck.
+
+Tashtego stood in the bows. He was full of the fire of the hunt. He
+hated Pip for a poltroon. Snatching the boat-knife from its sheath,
+he suspended its sharp edge over the line, and turning towards Stubb,
+exclaimed interrogatively, "Cut?" Meantime Pip's blue, choked face
+plainly looked, Do, for God's sake! All passed in a flash. In less
+than half a minute, this entire thing happened.
+
+"Damn him, cut!" roared Stubb; and so the whale was lost and Pip was
+saved.
+
+So soon as he recovered himself, the poor little negro was assailed
+by yells and execrations from the crew. Tranquilly permitting these
+irregular cursings to evaporate, Stubb then in a plain,
+business-like, but still half humorous manner, cursed Pip officially;
+and that done, unofficially gave him much wholesome advice. The
+substance was, Never jump from a boat, Pip, except--but all the rest
+was indefinite, as the soundest advice ever is. Now, in general,
+STICK TO THE BOAT, is your true motto in whaling; but cases will
+sometimes happen when LEAP FROM THE BOAT, is still better. Moreover,
+as if perceiving at last that if he should give undiluted
+conscientious advice to Pip, he would be leaving him too wide a
+margin to jump in for the future; Stubb suddenly dropped all advice,
+and concluded with a peremptory command, "Stick to the boat, Pip, or
+by the Lord, I won't pick you up if you jump; mind that. We can't
+afford to lose whales by the likes of you; a whale would sell for
+thirty times what you would, Pip, in Alabama. Bear that in mind, and
+don't jump any more." Hereby perhaps Stubb indirectly hinted, that
+though man loved his fellow, yet man is a money-making animal, which
+propensity too often interferes with his benevolence.
+
+But we are all in the hands of the Gods; and Pip jumped again. It
+was under very similar circumstances to the first performance; but
+this time he did not breast out the line; and hence, when the whale
+started to run, Pip was left behind on the sea, like a hurried
+traveller's trunk. Alas! Stubb was but too true to his word. It
+was a beautiful, bounteous, blue day; the spangled sea calm and
+cool, and flatly stretching away, all round, to the horizon, like
+gold-beater's skin hammered out to the extremest. Bobbing up and
+down in that sea, Pip's ebon head showed like a head of cloves. No
+boat-knife was lifted when he fell so rapidly astern. Stubb's
+inexorable back was turned upon him; and the whale was winged. In
+three minutes, a whole mile of shoreless ocean was between Pip and
+Stubb. Out from the centre of the sea, poor Pip turned his crisp,
+curling, black head to the sun, another lonely castaway, though the
+loftiest and the brightest.
+
+Now, in calm weather, to swim in the open ocean is as easy to the
+practised swimmer as to ride in a spring-carriage ashore. But the
+awful lonesomeness is intolerable. The intense concentration of self
+in the middle of such a heartless immensity, my God! who can tell it?
+Mark, how when sailors in a dead calm bathe in the open sea--mark
+how closely they hug their ship and only coast along her sides.
+
+But had Stubb really abandoned the poor little negro to his fate?
+No; he did not mean to, at least. Because there were two boats in
+his wake, and he supposed, no doubt, that they would of course come
+up to Pip very quickly, and pick him up; though, indeed, such
+considerations towards oarsmen jeopardized through their own
+timidity, is not always manifested by the hunters in all similar
+instances; and such instances not unfrequently occur; almost
+invariably in the fishery, a coward, so called, is marked with the
+same ruthless detestation peculiar to military navies and armies.
+
+But it so happened, that those boats, without seeing Pip, suddenly
+spying whales close to them on one side, turned, and gave chase; and
+Stubb's boat was now so far away, and he and all his crew so intent
+upon his fish, that Pip's ringed horizon began to expand around him
+miserably. By the merest chance the ship itself at last rescued him;
+but from that hour the little negro went about the deck an idiot;
+such, at least, they said he was. The sea had jeeringly kept his
+finite body up, but drowned the infinite of his soul. Not drowned
+entirely, though. Rather carried down alive to wondrous depths,
+where strange shapes of the unwarped primal world glided to and fro
+before his passive eyes; and the miser-merman, Wisdom, revealed his
+hoarded heaps; and among the joyous, heartless, ever-juvenile
+eternities, Pip saw the multitudinous, God-omnipresent, coral
+insects, that out of the firmament of waters heaved the colossal
+orbs. He saw God's foot upon the treadle of the loom, and spoke it;
+and therefore his shipmates called him mad. So man's insanity is
+heaven's sense; and wandering from all mortal reason, man comes at
+last to that celestial thought, which, to reason, is absurd and
+frantic; and weal or woe, feels then uncompromised, indifferent as
+his God.
+
+For the rest, blame not Stubb too hardly. The thing is common in
+that fishery; and in the sequel of the narrative, it will then be
+seen what like abandonment befell myself.
+
+
+
+CHAPTER 94
+
+A Squeeze of the Hand.
+
+
+That whale of Stubb's, so dearly purchased, was duly brought to the
+Pequod's side, where all those cutting and hoisting operations
+previously detailed, were regularly gone through, even to the baling
+of the Heidelburgh Tun, or Case.
+
+While some were occupied with this latter duty, others were employed
+in dragging away the larger tubs, so soon as filled with the sperm;
+and when the proper time arrived, this same sperm was carefully
+manipulated ere going to the try-works, of which anon.
+
+It had cooled and crystallized to such a degree, that when, with
+several others, I sat down before a large Constantine's bath of it, I
+found it strangely concreted into lumps, here and there rolling about
+in the liquid part. It was our business to squeeze these lumps back
+into fluid. A sweet and unctuous duty! No wonder that in old times
+this sperm was such a favourite cosmetic. Such a clearer! such a
+sweetener! such a softener! such a delicious molifier! After
+having my hands in it for only a few minutes, my fingers felt like
+eels, and began, as it were, to serpentine and spiralise.
+
+As I sat there at my ease, cross-legged on the deck; after the bitter
+exertion at the windlass; under a blue tranquil sky; the ship under
+indolent sail, and gliding so serenely along; as I bathed my hands
+among those soft, gentle globules of infiltrated tissues, woven
+almost within the hour; as they richly broke to my fingers, and
+discharged all their opulence, like fully ripe grapes their wine; as
+I snuffed up that uncontaminated aroma,--literally and truly, like
+the smell of spring violets; I declare to you, that for the time I
+lived as in a musky meadow; I forgot all about our horrible oath; in
+that inexpressible sperm, I washed my hands and my heart of it; I
+almost began to credit the old Paracelsan superstition that sperm is
+of rare virtue in allaying the heat of anger; while bathing in that
+bath, I felt divinely free from all ill-will, or petulance, or
+malice, of any sort whatsoever.
+
+Squeeze! squeeze! squeeze! all the morning long; I squeezed that
+sperm till I myself almost melted into it; I squeezed that sperm till
+a strange sort of insanity came over me; and I found myself
+unwittingly squeezing my co-laborers' hands in it, mistaking their
+hands for the gentle globules. Such an abounding, affectionate,
+friendly, loving feeling did this avocation beget; that at last I was
+continually squeezing their hands, and looking up into their eyes
+sentimentally; as much as to say,--Oh! my dear fellow beings, why
+should we longer cherish any social acerbities, or know the slightest
+ill-humor or envy! Come; let us squeeze hands all round; nay, let us
+all squeeze ourselves into each other; let us squeeze ourselves
+universally into the very milk and sperm of kindness.
+
+Would that I could keep squeezing that sperm for ever! For now,
+since by many prolonged, repeated experiences, I have perceived that
+in all cases man must eventually lower, or at least shift, his
+conceit of attainable felicity; not placing it anywhere in the
+intellect or the fancy; but in the wife, the heart, the bed, the
+table, the saddle, the fireside, the country; now that I have
+perceived all this, I am ready to squeeze case eternally. In
+thoughts of the visions of the night, I saw long rows of angels in
+paradise, each with his hands in a jar of spermaceti.
+
+Now, while discoursing of sperm, it behooves to speak of other things
+akin to it, in the business of preparing the sperm whale for the
+try-works.
+
+First comes white-horse, so called, which is obtained from the
+tapering part of the fish, and also from the thicker portions of his
+flukes. It is tough with congealed tendons--a wad of muscle--but
+still contains some oil. After being severed from the whale, the
+white-horse is first cut into portable oblongs ere going to the
+mincer. They look much like blocks of Berkshire marble.
+
+Plum-pudding is the term bestowed upon certain fragmentary parts of
+the whale's flesh, here and there adhering to the blanket of blubber,
+and often participating to a considerable degree in its unctuousness.
+It is a most refreshing, convivial, beautiful object to behold. As
+its name imports, it is of an exceedingly rich, mottled tint, with a
+bestreaked snowy and golden ground, dotted with spots of the deepest
+crimson and purple. It is plums of rubies, in pictures of citron.
+Spite of reason, it is hard to keep yourself from eating it. I
+confess, that once I stole behind the foremast to try it. It tasted
+something as I should conceive a royal cutlet from the thigh of Louis
+le Gros might have tasted, supposing him to have been killed the
+first day after the venison season, and that particular venison
+season contemporary with an unusually fine vintage of the vineyards
+of Champagne.
+
+There is another substance, and a very singular one, which turns up
+in the course of this business, but which I feel it to be very
+puzzling adequately to describe. It is called slobgollion; an
+appellation original with the whalemen, and even so is the nature of
+the substance. It is an ineffably oozy, stringy affair, most
+frequently found in the tubs of sperm, after a prolonged squeezing,
+and subsequent decanting. I hold it to be the wondrously thin,
+ruptured membranes of the case, coalescing.
+
+Gurry, so called, is a term properly belonging to right whalemen, but
+sometimes incidentally used by the sperm fishermen. It designates
+the dark, glutinous substance which is scraped off the back of the
+Greenland or right whale, and much of which covers the decks of those
+inferior souls who hunt that ignoble Leviathan.
+
+Nippers. Strictly this word is not indigenous to the whale's
+vocabulary. But as applied by whalemen, it becomes so. A whaleman's
+nipper is a short firm strip of tendinous stuff cut from the tapering
+part of Leviathan's tail: it averages an inch in thickness, and for
+the rest, is about the size of the iron part of a hoe. Edgewise
+moved along the oily deck, it operates like a leathern squilgee; and
+by nameless blandishments, as of magic, allures along with it all
+impurities.
+
+But to learn all about these recondite matters, your best way is at
+once to descend into the blubber-room, and have a long talk with its
+inmates. This place has previously been mentioned as the receptacle
+for the blanket-pieces, when stript and hoisted from the whale. When
+the proper time arrives for cutting up its contents, this apartment
+is a scene of terror to all tyros, especially by night. On one side,
+lit by a dull lantern, a space has been left clear for the workmen.
+They generally go in pairs,--a pike-and-gaffman and a spade-man.
+The whaling-pike is similar to a frigate's boarding-weapon of the
+same name. The gaff is something like a boat-hook. With his gaff,
+the gaffman hooks on to a sheet of blubber, and strives to hold it
+from slipping, as the ship pitches and lurches about. Meanwhile, the
+spade-man stands on the sheet itself, perpendicularly chopping it
+into the portable horse-pieces. This spade is sharp as hone can make
+it; the spademan's feet are shoeless; the thing he stands on will
+sometimes irresistibly slide away from him, like a sledge. If he
+cuts off one of his own toes, or one of his assistants', would you be
+very much astonished? Toes are scarce among veteran blubber-room
+men.
+
+
+
+CHAPTER 95
+
+The Cassock.
+
+
+Had you stepped on board the Pequod at a certain juncture of this
+post-mortemizing of the whale; and had you strolled forward nigh the
+windlass, pretty sure am I that you would have scanned with no small
+curiosity a very strange, enigmatical object, which you would have
+seen there, lying along lengthwise in the lee scuppers. Not the
+wondrous cistern in the whale's huge head; not the prodigy of his
+unhinged lower jaw; not the miracle of his symmetrical tail; none of
+these would so surprise you, as half a glimpse of that unaccountable
+cone,--longer than a Kentuckian is tall, nigh a foot in diameter at
+the base, and jet-black as Yojo, the ebony idol of Queequeg. And an
+idol, indeed, it is; or, rather, in old times, its likeness was.
+Such an idol as that found in the secret groves of Queen Maachah in
+Judea; and for worshipping which, King Asa, her son, did depose her,
+and destroyed the idol, and burnt it for an abomination at the brook
+Kedron, as darkly set forth in the 15th chapter of the First Book of
+Kings.
+
+Look at the sailor, called the mincer, who now comes along, and
+assisted by two allies, heavily backs the grandissimus, as the
+mariners call it, and with bowed shoulders, staggers off with it as
+if he were a grenadier carrying a dead comrade from the field.
+Extending it upon the forecastle deck, he now proceeds cylindrically
+to remove its dark pelt, as an African hunter the pelt of a boa.
+This done he turns the pelt inside out, like a pantaloon leg; gives
+it a good stretching, so as almost to double its diameter; and at
+last hangs it, well spread, in the rigging, to dry. Ere long, it is
+taken down; when removing some three feet of it, towards the pointed
+extremity, and then cutting two slits for arm-holes at the other end,
+he lengthwise slips himself bodily into it. The mincer now stands
+before you invested in the full canonicals of his calling.
+Immemorial to all his order, this investiture alone will adequately
+protect him, while employed in the peculiar functions of his office.
+
+That office consists in mincing the horse-pieces of blubber for the
+pots; an operation which is conducted at a curious wooden horse,
+planted endwise against the bulwarks, and with a capacious tub
+beneath it, into which the minced pieces drop, fast as the sheets
+from a rapt orator's desk. Arrayed in decent black; occupying a
+conspicuous pulpit; intent on bible leaves; what a candidate for an
+archbishopric, what a lad for a Pope were this mincer!*
+
+
+*Bible leaves! Bible leaves! This is the invariable cry from the
+mates to the mincer. It enjoins him to be careful, and cut his work
+into as thin slices as possible, inasmuch as by so doing the business
+of boiling out the oil is much accelerated, and its quantity
+considerably increased, besides perhaps improving it in quality.
+
+
+
+CHAPTER 96
+
+The Try-Works.
+
+
+Besides her hoisted boats, an American whaler is outwardly
+distinguished by her try-works. She presents the curious anomaly of
+the most solid masonry joining with oak and hemp in constituting the
+completed ship. It is as if from the open field a brick-kiln were
+transported to her planks.
+
+The try-works are planted between the foremast and mainmast, the
+most roomy part of the deck. The timbers beneath are of a peculiar
+strength, fitted to sustain the weight of an almost solid mass of
+brick and mortar, some ten feet by eight square, and five in height.
+The foundation does not penetrate the deck, but the masonry is firmly
+secured to the surface by ponderous knees of iron bracing it on all
+sides, and screwing it down to the timbers. On the flanks it is
+cased with wood, and at top completely covered by a large, sloping,
+battened hatchway. Removing this hatch we expose the great try-pots,
+two in number, and each of several barrels' capacity. When not in
+use, they are kept remarkably clean. Sometimes they are polished
+with soapstone and sand, till they shine within like silver
+punch-bowls. During the night-watches some cynical old sailors will
+crawl into them and coil themselves away there for a nap. While
+employed in polishing them--one man in each pot, side by side--many
+confidential communications are carried on, over the iron lips. It
+is a place also for profound mathematical meditation. It was in the
+left hand try-pot of the Pequod, with the soapstone diligently
+circling round me, that I was first indirectly struck by the
+remarkable fact, that in geometry all bodies gliding along the
+cycloid, my soapstone for example, will descend from any point in
+precisely the same time.
+
+Removing the fire-board from the front of the try-works, the bare
+masonry of that side is exposed, penetrated by the two iron mouths of
+the furnaces, directly underneath the pots. These mouths are fitted
+with heavy doors of iron. The intense heat of the fire is prevented
+from communicating itself to the deck, by means of a shallow
+reservoir extending under the entire inclosed surface of the works.
+By a tunnel inserted at the rear, this reservoir is kept replenished
+with water as fast as it evaporates. There are no external chimneys;
+they open direct from the rear wall. And here let us go back for a
+moment.
+
+It was about nine o'clock at night that the Pequod's try-works were
+first started on this present voyage. It belonged to Stubb to
+oversee the business.
+
+"All ready there? Off hatch, then, and start her. You cook, fire
+the works." This was an easy thing, for the carpenter had been
+thrusting his shavings into the furnace throughout the passage. Here
+be it said that in a whaling voyage the first fire in the try-works has
+to be fed for a time with wood. After that no wood is used, except
+as a means of quick ignition to the staple fuel. In a word, after
+being tried out, the crisp, shrivelled blubber, now called scraps or
+fritters, still contains considerable of its unctuous properties.
+These fritters feed the flames. Like a plethoric burning martyr, or
+a self-consuming misanthrope, once ignited, the whale supplies his
+own fuel and burns by his own body. Would that he consumed his own
+smoke! for his smoke is horrible to inhale, and inhale it you must,
+and not only that, but you must live in it for the time. It has an
+unspeakable, wild, Hindoo odor about it, such as may lurk in the
+vicinity of funereal pyres. It smells like the left wing of the day
+of judgment; it is an argument for the pit.
+
+By midnight the works were in full operation. We were clear from the
+carcase; sail had been made; the wind was freshening; the wild ocean
+darkness was intense. But that darkness was licked up by the fierce
+flames, which at intervals forked forth from the sooty flues, and
+illuminated every lofty rope in the rigging, as with the famed Greek
+fire. The burning ship drove on, as if remorselessly commissioned to
+some vengeful deed. So the pitch and sulphur-freighted brigs of the
+bold Hydriote, Canaris, issuing from their midnight harbors, with
+broad sheets of flame for sails, bore down upon the Turkish frigates,
+and folded them in conflagrations.
+
+The hatch, removed from the top of the works, now afforded a wide
+hearth in front of them. Standing on this were the Tartarean shapes
+of the pagan harpooneers, always the whale-ship's stokers. With huge
+pronged poles they pitched hissing masses of blubber into the
+scalding pots, or stirred up the fires beneath, till the snaky flames
+darted, curling, out of the doors to catch them by the feet. The
+smoke rolled away in sullen heaps. To every pitch of the ship there
+was a pitch of the boiling oil, which seemed all eagerness to leap
+into their faces. Opposite the mouth of the works, on the further
+side of the wide wooden hearth, was the windlass. This served for a
+sea-sofa. Here lounged the watch, when not otherwise employed,
+looking into the red heat of the fire, till their eyes felt scorched
+in their heads. Their tawny features, now all begrimed with smoke
+and sweat, their matted beards, and the contrasting barbaric
+brilliancy of their teeth, all these were strangely revealed in the
+capricious emblazonings of the works. As they narrated to each other
+their unholy adventures, their tales of terror told in words of
+mirth; as their uncivilized laughter forked upwards out of them, like
+the flames from the furnace; as to and fro, in their front, the
+harpooneers wildly gesticulated with their huge pronged forks and
+dippers; as the wind howled on, and the sea leaped, and the ship
+groaned and dived, and yet steadfastly shot her red hell further and
+further into the blackness of the sea and the night, and scornfully
+champed the white bone in her mouth, and viciously spat round her on
+all sides; then the rushing Pequod, freighted with savages, and laden
+with fire, and burning a corpse, and plunging into that blackness of
+darkness, seemed the material counterpart of her monomaniac
+commander's soul.
+
+So seemed it to me, as I stood at her helm, and for long hours
+silently guided the way of this fire-ship on the sea. Wrapped, for
+that interval, in darkness myself, I but the better saw the redness,
+the madness, the ghastliness of others. The continual sight of the
+fiend shapes before me, capering half in smoke and half in fire,
+these at last begat kindred visions in my soul, so soon as I began to
+yield to that unaccountable drowsiness which ever would come over me
+at a midnight helm.
+
+But that night, in particular, a strange (and ever since
+inexplicable) thing occurred to me. Starting from a brief standing
+sleep, I was horribly conscious of something fatally wrong. The
+jaw-bone tiller smote my side, which leaned against it; in my ears
+was the low hum of sails, just beginning to shake in the wind; I
+thought my eyes were open; I was half conscious of putting my fingers
+to the lids and mechanically stretching them still further apart.
+But, spite of all this, I could see no compass before me to steer by;
+though it seemed but a minute since I had been watching the card, by
+the steady binnacle lamp illuminating it. Nothing seemed before me
+but a jet gloom, now and then made ghastly by flashes of redness.
+Uppermost was the impression, that whatever swift, rushing thing I
+stood on was not so much bound to any haven ahead as rushing from all
+havens astern. A stark, bewildered feeling, as of death, came over
+me. Convulsively my hands grasped the tiller, but with the crazy
+conceit that the tiller was, somehow, in some enchanted way,
+inverted. My God! what is the matter with me? thought I. Lo! in my
+brief sleep I had turned myself about, and was fronting the ship's
+stern, with my back to her prow and the compass. In an instant I
+faced back, just in time to prevent the vessel from flying up into
+the wind, and very probably capsizing her. How glad and how grateful
+the relief from this unnatural hallucination of the night, and the
+fatal contingency of being brought by the lee!
+
+Look not too long in the face of the fire, O man! Never dream with
+thy hand on the helm! Turn not thy back to the compass; accept the
+first hint of the hitching tiller; believe not the artificial fire,
+when its redness makes all things look ghastly. To-morrow, in the
+natural sun, the skies will be bright; those who glared like devils
+in the forking flames, the morn will show in far other, at least
+gentler, relief; the glorious, golden, glad sun, the only true
+lamp--all others but liars!
+
+Nevertheless the sun hides not Virginia's Dismal Swamp, nor Rome's
+accursed Campagna, nor wide Sahara, nor all the millions of miles of
+deserts and of griefs beneath the moon. The sun hides not the ocean,
+which is the dark side of this earth, and which is two thirds of this
+earth. So, therefore, that mortal man who hath more of joy than
+sorrow in him, that mortal man cannot be true--not true, or
+undeveloped. With books the same. The truest of all men was the Man
+of Sorrows, and the truest of all books is Solomon's, and
+Ecclesiastes is the fine hammered steel of woe. "All is vanity."
+ALL. This wilful world hath not got hold of unchristian Solomon's
+wisdom yet. But he who dodges hospitals and jails, and walks fast
+crossing graveyards, and would rather talk of operas than hell;
+calls Cowper, Young, Pascal, Rousseau, poor devils all of sick men;
+and throughout a care-free lifetime swears by Rabelais as passing
+wise, and therefore jolly;--not that man is fitted to sit down on
+tomb-stones, and break the green damp mould with unfathomably
+wondrous Solomon.
+
+But even Solomon, he says, "the man that wandereth out of the way of
+understanding shall remain" (I.E., even while living) "in the
+congregation of the dead." Give not thyself up, then, to fire, lest
+it invert thee, deaden thee; as for the time it did me. There is a
+wisdom that is woe; but there is a woe that is madness. And there is
+a Catskill eagle in some souls that can alike dive down into the
+blackest gorges, and soar out of them again and become invisible in
+the sunny spaces. And even if he for ever flies within the gorge,
+that gorge is in the mountains; so that even in his lowest swoop the
+mountain eagle is still higher than other birds upon the plain, even
+though they soar.
+
+
+
+CHAPTER 97
+
+The Lamp.
+
+
+Had you descended from the Pequod's try-works to the Pequod's
+forecastle, where the off duty watch were sleeping, for one single
+moment you would have almost thought you were standing in some
+illuminated shrine of canonized kings and counsellors. There they
+lay in their triangular oaken vaults, each mariner a chiselled
+muteness; a score of lamps flashing upon his hooded eyes.
+
+In merchantmen, oil for the sailor is more scarce than the milk of
+queens. To dress in the dark, and eat in the dark, and stumble in
+darkness to his pallet, this is his usual lot. But the whaleman, as
+he seeks the food of light, so he lives in light. He makes his berth
+an Aladdin's lamp, and lays him down in it; so that in the pitchiest
+night the ship's black hull still houses an illumination.
+
+See with what entire freedom the whaleman takes his handful of
+lamps--often but old bottles and vials, though--to the copper cooler
+at the try-works, and replenishes them there, as mugs of ale at a
+vat. He burns, too, the purest of oil, in its unmanufactured, and,
+therefore, unvitiated state; a fluid unknown to solar, lunar, or
+astral contrivances ashore. It is sweet as early grass butter in
+April. He goes and hunts for his oil, so as to be sure of its
+freshness and genuineness, even as the traveller on the prairie hunts
+up his own supper of game.
+
+
+
+CHAPTER 98
+
+Stowing Down and Clearing Up.
+
+
+Already has it been related how the great leviathan is afar off
+descried from the mast-head; how he is chased over the watery moors,
+and slaughtered in the valleys of the deep; how he is then towed
+alongside and beheaded; and how (on the principle which entitled the
+headsman of old to the garments in which the beheaded was killed) his
+great padded surtout becomes the property of his executioner; how, in
+due time, he is condemned to the pots, and, like Shadrach, Meshach,
+and Abednego, his spermaceti, oil, and bone pass unscathed through
+the fire;--but now it remains to conclude the last chapter of this
+part of the description by rehearsing--singing, if I may--the
+romantic proceeding of decanting off his oil into the casks and
+striking them down into the hold, where once again leviathan returns
+to his native profundities, sliding along beneath the surface as
+before; but, alas! never more to rise and blow.
+
+While still warm, the oil, like hot punch, is received into the
+six-barrel casks; and while, perhaps, the ship is pitching and
+rolling this way and that in the midnight sea, the enormous casks are
+slewed round and headed over, end for end, and sometimes perilously
+scoot across the slippery deck, like so many land slides, till at
+last man-handled and stayed in their course; and all round the hoops,
+rap, rap, go as many hammers as can play upon them, for now, EX
+OFFICIO, every sailor is a cooper.
+
+At length, when the last pint is casked, and all is cool, then the
+great hatchways are unsealed, the bowels of the ship are thrown open,
+and down go the casks to their final rest in the sea. This done, the
+hatches are replaced, and hermetically closed, like a closet walled
+up.
+
+In the sperm fishery, this is perhaps one of the most remarkable
+incidents in all the business of whaling. One day the planks stream
+with freshets of blood and oil; on the sacred quarter-deck enormous
+masses of the whale's head are profanely piled; great rusty casks lie
+about, as in a brewery yard; the smoke from the try-works has
+besooted all the bulwarks; the mariners go about suffused with
+unctuousness; the entire ship seems great leviathan himself; while on
+all hands the din is deafening.
+
+But a day or two after, you look about you, and prick your ears in
+this self-same ship; and were it not for the tell-tale boats and
+try-works, you would all but swear you trod some silent merchant
+vessel, with a most scrupulously neat commander. The unmanufactured
+sperm oil possesses a singularly cleansing virtue. This is the
+reason why the decks never look so white as just after what they call
+an affair of oil. Besides, from the ashes of the burned scraps of
+the whale, a potent lye is readily made; and whenever any
+adhesiveness from the back of the whale remains clinging to the side,
+that lye quickly exterminates it. Hands go diligently along the
+bulwarks, and with buckets of water and rags restore them to their
+full tidiness. The soot is brushed from the lower rigging. All the
+numerous implements which have been in use are likewise faithfully
+cleansed and put away. The great hatch is scrubbed and placed upon
+the try-works, completely hiding the pots; every cask is out of
+sight; all tackles are coiled in unseen nooks; and when by the
+combined and simultaneous industry of almost the entire ship's
+company, the whole of this conscientious duty is at last concluded,
+then the crew themselves proceed to their own ablutions; shift
+themselves from top to toe; and finally issue to the immaculate deck,
+fresh and all aglow, as bridegrooms new-leaped from out the daintiest
+Holland.
+
+Now, with elated step, they pace the planks in twos and threes, and
+humorously discourse of parlors, sofas, carpets, and fine cambrics;
+propose to mat the deck; think of having hanging to the top; object
+not to taking tea by moonlight on the piazza of the forecastle. To
+hint to such musked mariners of oil, and bone, and blubber, were
+little short of audacity. They know not the thing you distantly
+allude to. Away, and bring us napkins!
+
+But mark: aloft there, at the three mast heads, stand three men
+intent on spying out more whales, which, if caught, infallibly will
+again soil the old oaken furniture, and drop at least one small
+grease-spot somewhere. Yes; and many is the time, when, after the
+severest uninterrupted labors, which know no night; continuing
+straight through for ninety-six hours; when from the boat, where they
+have swelled their wrists with all day rowing on the Line,--they only
+step to the deck to carry vast chains, and heave the heavy windlass,
+and cut and slash, yea, and in their very sweatings to be smoked and
+burned anew by the combined fires of the equatorial sun and the
+equatorial try-works; when, on the heel of all this, they have
+finally bestirred themselves to cleanse the ship, and make a spotless
+dairy room of it; many is the time the poor fellows, just buttoning
+the necks of their clean frocks, are startled by the cry of "There
+she blows!" and away they fly to fight another whale, and go through
+the whole weary thing again. Oh! my friends, but this is
+man-killing! Yet this is life. For hardly have we mortals by long
+toilings extracted from this world's vast bulk its small but
+valuable sperm; and then, with weary patience, cleansed ourselves
+from its defilements, and learned to live here in clean tabernacles
+of the soul; hardly is this done, when--THERE SHE BLOWS!--the ghost
+is spouted up, and away we sail to fight some other world, and go
+through young life's old routine again.
+
+Oh! the metempsychosis! Oh! Pythagoras, that in bright Greece, two
+thousand years ago, did die, so good, so wise, so mild; I sailed with
+thee along the Peruvian coast last voyage--and, foolish as I am,
+taught thee, a green simple boy, how to splice a rope!
+
+
+
+CHAPTER 99
+
+The Doubloon.
+
+
+Ere now it has been related how Ahab was wont to pace his
+quarter-deck, taking regular turns at either limit, the binnacle and
+mainmast; but in the multiplicity of other things requiring narration
+it has not been added how that sometimes in these walks, when most
+plunged in his mood, he was wont to pause in turn at each spot, and
+stand there strangely eyeing the particular object before him. When
+he halted before the binnacle, with his glance fastened on the
+pointed needle in the compass, that glance shot like a javelin with
+the pointed intensity of his purpose; and when resuming his walk he
+again paused before the mainmast, then, as the same riveted glance
+fastened upon the riveted gold coin there, he still wore the same
+aspect of nailed firmness, only dashed with a certain wild longing,
+if not hopefulness.
+
+But one morning, turning to pass the doubloon, he seemed to be newly
+attracted by the strange figures and inscriptions stamped on it, as
+though now for the first time beginning to interpret for himself in
+some monomaniac way whatever significance might lurk in them. And
+some certain significance lurks in all things, else all things are
+little worth, and the round world itself but an empty cipher, except
+to sell by the cartload, as they do hills about Boston, to fill up
+some morass in the Milky Way.
+
+Now this doubloon was of purest, virgin gold, raked somewhere out of
+the heart of gorgeous hills, whence, east and west, over golden
+sands, the head-waters of many a Pactolus flows. And though now
+nailed amidst all the rustiness of iron bolts and the verdigris of
+copper spikes, yet, untouchable and immaculate to any foulness, it
+still preserved its Quito glow. Nor, though placed amongst a
+ruthless crew and every hour passed by ruthless hands, and through
+the livelong nights shrouded with thick darkness which might cover
+any pilfering approach, nevertheless every sunrise found the doubloon
+where the sunset left it last. For it was set apart and sanctified
+to one awe-striking end; and however wanton in their sailor ways, one
+and all, the mariners revered it as the white whale's talisman.
+Sometimes they talked it over in the weary watch by night, wondering
+whose it was to be at last, and whether he would ever live to spend
+it.
+
+Now those noble golden coins of South America are as medals of the
+sun and tropic token-pieces. Here palms, alpacas, and volcanoes;
+sun's disks and stars; ecliptics, horns-of-plenty, and rich banners
+waving, are in luxuriant profusion stamped; so that the precious gold
+seems almost to derive an added preciousness and enhancing glories,
+by passing through those fancy mints, so Spanishly poetic.
+
+It so chanced that the doubloon of the Pequod was a most wealthy
+example of these things. On its round border it bore the letters,
+REPUBLICA DEL ECUADOR: QUITO. So this bright coin came from a
+country planted in the middle of the world, and beneath the great
+equator, and named after it; and it had been cast midway up the
+Andes, in the unwaning clime that knows no autumn. Zoned by those
+letters you saw the likeness of three Andes' summits; from one a
+flame; a tower on another; on the third a crowing cock; while arching
+over all was a segment of the partitioned zodiac, the signs all
+marked with their usual cabalistics, and the keystone sun entering
+the equinoctial point at Libra.
+
+Before this equatorial coin, Ahab, not unobserved by others, was now
+pausing.
+
+"There's something ever egotistical in mountain-tops and towers, and
+all other grand and lofty things; look here,--three peaks as proud as
+Lucifer. The firm tower, that is Ahab; the volcano, that is Ahab;
+the courageous, the undaunted, and victorious fowl, that, too, is
+Ahab; all are Ahab; and this round gold is but the image of the
+rounder globe, which, like a magician's glass, to each and every man
+in turn but mirrors back his own mysterious self. Great pains, small
+gains for those who ask the world to solve them; it cannot solve
+itself. Methinks now this coined sun wears a ruddy face; but see!
+aye, he enters the sign of storms, the equinox! and but six months
+before he wheeled out of a former equinox at Aries! From storm to
+storm! So be it, then. Born in throes, 't is fit that man should
+live in pains and die in pangs! So be it, then! Here's stout stuff
+for woe to work on. So be it, then."
+
+"No fairy fingers can have pressed the gold, but devil's claws must have
+left their mouldings there since yesterday," murmured Starbuck to
+himself, leaning against the bulwarks. "The old man seems to read
+Belshazzar's awful writing. I have never marked the coin
+inspectingly. He goes below; let me read. A dark valley between
+three mighty, heaven-abiding peaks, that almost seem the Trinity, in
+some faint earthly symbol. So in this vale of Death, God girds us
+round; and over all our gloom, the sun of Righteousness still shines
+a beacon and a hope. If we bend down our eyes, the dark vale shows
+her mouldy soil; but if we lift them, the bright sun meets our glance
+half way, to cheer. Yet, oh, the great sun is no fixture; and if, at
+midnight, we would fain snatch some sweet solace from him, we gaze
+for him in vain! This coin speaks wisely, mildly, truly, but still
+sadly to me. I will quit it, lest Truth shake me falsely."
+
+"There now's the old Mogul," soliloquized Stubb by the try-works,
+"he's been twigging it; and there goes Starbuck from the same, and
+both with faces which I should say might be somewhere within nine
+fathoms long. And all from looking at a piece of gold, which did I
+have it now on Negro Hill or in Corlaer's Hook, I'd not look at it
+very long ere spending it. Humph! in my poor, insignificant opinion,
+I regard this as queer. I have seen doubloons before now in my
+voyagings; your doubloons of old Spain, your doubloons of Peru, your
+doubloons of Chili, your doubloons of Bolivia, your doubloons of
+Popayan; with plenty of gold moidores and pistoles, and joes, and
+half joes, and quarter joes. What then should there be in this
+doubloon of the Equator that is so killing wonderful? By Golconda!
+let me read it once. Halloa! here's signs and wonders truly! That,
+now, is what old Bowditch in his Epitome calls the zodiac, and what
+my almanac below calls ditto. I'll get the almanac and as I have
+heard devils can be raised with Daboll's arithmetic, I'll try my hand
+at raising a meaning out of these queer curvicues here with the
+Massachusetts calendar. Here's the book. Let's see now. Signs and
+wonders; and the sun, he's always among 'em. Hem, hem, hem; here
+they are--here they go--all alive:--Aries, or the Ram; Taurus, or the
+Bull and Jimimi! here's Gemini himself, or the Twins. Well; the sun
+he wheels among 'em. Aye, here on the coin he's just crossing the
+threshold between two of twelve sitting-rooms all in a ring. Book!
+you lie there; the fact is, you books must know your places. You'll
+do to give us the bare words and facts, but we come in to supply the
+thoughts. That's my small experience, so far as the Massachusetts
+calendar, and Bowditch's navigator, and Daboll's arithmetic go.
+Signs and wonders, eh? Pity if there is nothing wonderful in signs,
+and significant in wonders! There's a clue somewhere; wait a bit;
+hist--hark! By Jove, I have it! Look you, Doubloon, your zodiac
+here is the life of man in one round chapter; and now I'll read it
+off, straight out of the book. Come, Almanack! To begin: there's
+Aries, or the Ram--lecherous dog, he begets us; then, Taurus, or the
+Bull--he bumps us the first thing; then Gemini, or the Twins--that
+is, Virtue and Vice; we try to reach Virtue, when lo! comes Cancer
+the Crab, and drags us back; and here, going from Virtue, Leo, a
+roaring Lion, lies in the path--he gives a few fierce bites and surly
+dabs with his paw; we escape, and hail Virgo, the Virgin! that's our
+first love; we marry and think to be happy for aye, when pop comes
+Libra, or the Scales--happiness weighed and found wanting; and while
+we are very sad about that, Lord! how we suddenly jump, as Scorpio,
+or the Scorpion, stings us in the rear; we are curing the wound, when
+whang come the arrows all round; Sagittarius, or the Archer, is
+amusing himself. As we pluck out the shafts, stand aside! here's
+the battering-ram, Capricornus, or the Goat; full tilt, he comes
+rushing, and headlong we are tossed; when Aquarius, or the
+Water-bearer, pours out his whole deluge and drowns us; and to wind
+up with Pisces, or the Fishes, we sleep. There's a sermon now, writ
+in high heaven, and the sun goes through it every year, and yet comes
+out of it all alive and hearty. Jollily he, aloft there, wheels
+through toil and trouble; and so, alow here, does jolly Stubb. Oh,
+jolly's the word for aye! Adieu, Doubloon! But stop; here comes
+little King-Post; dodge round the try-works, now, and let's hear what
+he'll have to say. There; he's before it; he'll out with something
+presently. So, so; he's beginning."
+
+"I see nothing here, but a round thing made of gold, and whoever
+raises a certain whale, this round thing belongs to him. So, what's
+all this staring been about? It is worth sixteen dollars, that's
+true; and at two cents the cigar, that's nine hundred and sixty
+cigars. I won't smoke dirty pipes like Stubb, but I like cigars, and
+here's nine hundred and sixty of them; so here goes Flask aloft to
+spy 'em out."
+
+"Shall I call that wise or foolish, now; if it be really wise it has
+a foolish look to it; yet, if it be really foolish, then has it a
+sort of wiseish look to it. But, avast; here comes our old
+Manxman--the old hearse-driver, he must have been, that is, before he
+took to the sea. He luffs up before the doubloon; halloa, and goes
+round on the other side of the mast; why, there's a horse-shoe nailed
+on that side; and now he's back again; what does that mean? Hark!
+he's muttering--voice like an old worn-out coffee-mill. Prick ears,
+and listen!"
+
+"If the White Whale be raised, it must be in a month and a day, when
+the sun stands in some one of these signs. I've studied signs, and
+know their marks; they were taught me two score years ago, by the old
+witch in Copenhagen. Now, in what sign will the sun then be? The
+horse-shoe sign; for there it is, right opposite the gold. And
+what's the horse-shoe sign? The lion is the horse-shoe sign--the
+roaring and devouring lion. Ship, old ship! my old head shakes to
+think of thee."
+
+"There's another rendering now; but still one text. All sorts of men
+in one kind of world, you see. Dodge again! here comes Queequeg--all
+tattooing--looks like the signs of the Zodiac himself. What says the
+Cannibal? As I live he's comparing notes; looking at his thigh bone;
+thinks the sun is in the thigh, or in the calf, or in the bowels, I
+suppose, as the old women talk Surgeon's Astronomy in the back
+country. And by Jove, he's found something there in the vicinity of
+his thigh--I guess it's Sagittarius, or the Archer. No: he don't
+know what to make of the doubloon; he takes it for an old button off
+some king's trowsers. But, aside again! here comes that ghost-devil,
+Fedallah; tail coiled out of sight as usual, oakum in the toes of his
+pumps as usual. What does he say, with that look of his? Ah, only
+makes a sign to the sign and bows himself; there is a sun on the
+coin--fire worshipper, depend upon it. Ho! more and more. This way
+comes Pip--poor boy! would he had died, or I; he's half horrible to
+me. He too has been watching all of these interpreters--myself
+included--and look now, he comes to read, with that unearthly idiot
+face. Stand away again and hear him. Hark!"
+
+"I look, you look, he looks; we look, ye look, they look."
+
+"Upon my soul, he's been studying Murray's Grammar! Improving his
+mind, poor fellow! But what's that he says now--hist!"
+
+"I look, you look, he looks; we look, ye look, they look."
+
+"Why, he's getting it by heart--hist! again."
+
+"I look, you look, he looks; we look, ye look, they look."
+
+"Well, that's funny."
+
+"And I, you, and he; and we, ye, and they, are all bats; and I'm a
+crow, especially when I stand a'top of this pine tree here. Caw!
+caw! caw! caw! caw! caw! Ain't I a crow? And where's the
+scare-crow? There he stands; two bones stuck into a pair of old
+trowsers, and two more poked into the sleeves of an old jacket."
+
+"Wonder if he means me?--complimentary!--poor lad!--I could go hang
+myself. Any way, for the present, I'll quit Pip's vicinity. I can
+stand the rest, for they have plain wits; but he's too crazy-witty
+for my sanity. So, so, I leave him muttering."
+
+"Here's the ship's navel, this doubloon here, and they are all on
+fire to unscrew it. But, unscrew your navel, and what's the
+consequence? Then again, if it stays here, that is ugly, too, for
+when aught's nailed to the mast it's a sign that things grow
+desperate. Ha, ha! old Ahab! the White Whale; he'll nail ye! This
+is a pine tree. My father, in old Tolland county, cut down a pine
+tree once, and found a silver ring grown over in it; some old
+darkey's wedding ring. How did it get there? And so they'll say in
+the resurrection, when they come to fish up this old mast, and find a
+doubloon lodged in it, with bedded oysters for the shaggy bark. Oh,
+the gold! the precious, precious, gold! the green miser'll hoard ye
+soon! Hish! hish! God goes 'mong the worlds blackberrying. Cook!
+ho, cook! and cook us! Jenny! hey, hey, hey, hey, hey, Jenny, Jenny!
+and get your hoe-cake done!"
+
+
+
+CHAPTER 100
+
+Leg and Arm.
+
+The Pequod, of Nantucket, Meets the Samuel Enderby, of London.
+
+
+"Ship, ahoy! Hast seen the White Whale?"
+
+So cried Ahab, once more hailing a ship showing English colours,
+bearing down under the stern. Trumpet to mouth, the old man was
+standing in his hoisted quarter-boat, his ivory leg plainly revealed
+to the stranger captain, who was carelessly reclining in his own
+boat's bow. He was a darkly-tanned, burly, good-natured,
+fine-looking man, of sixty or thereabouts, dressed in a spacious
+roundabout, that hung round him in festoons of blue pilot-cloth; and
+one empty arm of this jacket streamed behind him like the broidered
+arm of a hussar's surcoat.
+
+"Hast seen the White Whale!"
+
+"See you this?" and withdrawing it from the folds that had hidden
+it, he held up a white arm of sperm whale bone, terminating in a
+wooden head like a mallet.
+
+"Man my boat!" cried Ahab, impetuously, and tossing about the oars
+near him--"Stand by to lower!"
+
+In less than a minute, without quitting his little craft, he and his
+crew were dropped to the water, and were soon alongside of the
+stranger. But here a curious difficulty presented itself. In the
+excitement of the moment, Ahab had forgotten that since the loss of
+his leg he had never once stepped on board of any vessel at sea but
+his own, and then it was always by an ingenious and very handy
+mechanical contrivance peculiar to the Pequod, and a thing not to be
+rigged and shipped in any other vessel at a moment's warning. Now,
+it is no very easy matter for anybody--except those who are almost
+hourly used to it, like whalemen--to clamber up a ship's side from a
+boat on the open sea; for the great swells now lift the boat high up
+towards the bulwarks, and then instantaneously drop it half way down
+to the kelson. So, deprived of one leg, and the strange ship of
+course being altogether unsupplied with the kindly invention, Ahab
+now found himself abjectly reduced to a clumsy landsman again;
+hopelessly eyeing the uncertain changeful height he could hardly hope
+to attain.
+
+It has before been hinted, perhaps, that every little untoward
+circumstance that befell him, and which indirectly sprang from his
+luckless mishap, almost invariably irritated or exasperated Ahab.
+And in the present instance, all this was heightened by the sight of
+the two officers of the strange ship, leaning over the side, by the
+perpendicular ladder of nailed cleets there, and swinging towards him
+a pair of tastefully-ornamented man-ropes; for at first they did not
+seem to bethink them that a one-legged man must be too much of a
+cripple to use their sea bannisters. But this awkwardness only
+lasted a minute, because the strange captain, observing at a glance
+how affairs stood, cried out, "I see, I see!--avast heaving there!
+Jump, boys, and swing over the cutting-tackle."
+
+As good luck would have it, they had had a whale alongside a day or
+two previous, and the great tackles were still aloft, and the massive
+curved blubber-hook, now clean and dry, was still attached to the
+end. This was quickly lowered to Ahab, who at once comprehending it
+all, slid his solitary thigh into the curve of the hook (it was like
+sitting in the fluke of an anchor, or the crotch of an apple tree),
+and then giving the word, held himself fast, and at the same time
+also helped to hoist his own weight, by pulling hand-over-hand upon
+one of the running parts of the tackle. Soon he was carefully swung
+inside the high bulwarks, and gently landed upon the capstan head.
+With his ivory arm frankly thrust forth in welcome, the other captain
+advanced, and Ahab, putting out his ivory leg, and crossing the ivory
+arm (like two sword-fish blades) cried out in his walrus way, "Aye,
+aye, hearty! let us shake bones together!--an arm and a leg!--an arm
+that never can shrink, d'ye see; and a leg that never can run. Where
+did'st thou see the White Whale?--how long ago?"
+
+"The White Whale," said the Englishman, pointing his ivory arm
+towards the East, and taking a rueful sight along it, as if it had
+been a telescope; "there I saw him, on the Line, last season."
+
+"And he took that arm off, did he?" asked Ahab, now sliding down from
+the capstan, and resting on the Englishman's shoulder, as he did so.
+
+"Aye, he was the cause of it, at least; and that leg, too?"
+
+"Spin me the yarn," said Ahab; "how was it?"
+
+"It was the first time in my life that I ever cruised on the Line,"
+began the Englishman. "I was ignorant of the White Whale at that
+time. Well, one day we lowered for a pod of four or five whales, and
+my boat fastened to one of them; a regular circus horse he was, too,
+that went milling and milling round so, that my boat's crew could
+only trim dish, by sitting all their sterns on the outer gunwale.
+Presently up breaches from the bottom of the sea a bouncing great
+whale, with a milky-white head and hump, all crows' feet and
+wrinkles."
+
+"It was he, it was he!" cried Ahab, suddenly letting out his
+suspended breath.
+
+"And harpoons sticking in near his starboard fin."
+
+"Aye, aye--they were mine--MY irons," cried Ahab, exultingly--"but
+on!"
+
+"Give me a chance, then," said the Englishman, good-humoredly.
+"Well, this old great-grandfather, with the white head and hump, runs
+all afoam into the pod, and goes to snapping furiously at my
+fast-line!
+
+"Aye, I see!--wanted to part it; free the fast-fish--an old trick--I
+know him."
+
+"How it was exactly," continued the one-armed commander, "I do not
+know; but in biting the line, it got foul of his teeth, caught there
+somehow; but we didn't know it then; so that when we afterwards
+pulled on the line, bounce we came plump on to his hump! instead of
+the other whale's; that went off to windward, all fluking. Seeing
+how matters stood, and what a noble great whale it was--the noblest
+and biggest I ever saw, sir, in my life--I resolved to capture him,
+spite of the boiling rage he seemed to be in. And thinking the
+hap-hazard line would get loose, or the tooth it was tangled to
+might draw (for I have a devil of a boat's crew for a pull on a
+whale-line); seeing all this, I say, I jumped into my first mate's
+boat--Mr. Mounttop's here (by the way, Captain--Mounttop;
+Mounttop--the captain);--as I was saying, I jumped into Mounttop's
+boat, which, d'ye see, was gunwale and gunwale with mine, then; and
+snatching the first harpoon, let this old great-grandfather have it.
+But, Lord, look you, sir--hearts and souls alive, man--the next
+instant, in a jiff, I was blind as a bat--both eyes out--all befogged
+and bedeadened with black foam--the whale's tail looming straight up
+out of it, perpendicular in the air, like a marble steeple. No use
+sterning all, then; but as I was groping at midday, with a blinding
+sun, all crown-jewels; as I was groping, I say, after the second
+iron, to toss it overboard--down comes the tail like a Lima tower,
+cutting my boat in two, leaving each half in splinters; and, flukes
+first, the white hump backed through the wreck, as though it was all
+chips. We all struck out. To escape his terrible flailings, I
+seized hold of my harpoon-pole sticking in him, and for a moment
+clung to that like a sucking fish. But a combing sea dashed me off,
+and at the same instant, the fish, taking one good dart forwards,
+went down like a flash; and the barb of that cursed second iron
+towing along near me caught me here" (clapping his hand just below
+his shoulder); "yes, caught me just here, I say, and bore me down to
+Hell's flames, I was thinking; when, when, all of a sudden, thank the
+good God, the barb ript its way along the flesh--clear along the
+whole length of my arm--came out nigh my wrist, and up I
+floated;--and that gentleman there will tell you the rest (by the
+way, captain--Dr. Bunger, ship's surgeon: Bunger, my lad,--the
+captain). Now, Bunger boy, spin your part of the yarn."
+
+The professional gentleman thus familiarly pointed out, had been all
+the time standing near them, with nothing specific visible, to denote
+his gentlemanly rank on board. His face was an exceedingly round but
+sober one; he was dressed in a faded blue woollen frock or shirt, and
+patched trowsers; and had thus far been dividing his attention
+between a marlingspike he held in one hand, and a pill-box held in
+the other, occasionally casting a critical glance at the ivory limbs
+of the two crippled captains. But, at his superior's introduction of
+him to Ahab, he politely bowed, and straightway went on to do his
+captain's bidding.
+
+"It was a shocking bad wound," began the whale-surgeon; "and, taking
+my advice, Captain Boomer here, stood our old Sammy--"
+
+"Samuel Enderby is the name of my ship," interrupted the one-armed
+captain, addressing Ahab; "go on, boy."
+
+"Stood our old Sammy off to the northward, to get out of the blazing
+hot weather there on the Line. But it was no use--I did all I could;
+sat up with him nights; was very severe with him in the matter of
+diet--"
+
+"Oh, very severe!" chimed in the patient himself; then suddenly
+altering his voice, "Drinking hot rum toddies with me every night,
+till he couldn't see to put on the bandages; and sending me to bed,
+half seas over, about three o'clock in the morning. Oh, ye stars! he
+sat up with me indeed, and was very severe in my diet. Oh! a great
+watcher, and very dietetically severe, is Dr. Bunger. (Bunger, you
+dog, laugh out! why don't ye? You know you're a precious jolly
+rascal.) But, heave ahead, boy, I'd rather be killed by you than kept
+alive by any other man."
+
+"My captain, you must have ere this perceived, respected sir"--said
+the imperturbable godly-looking Bunger, slightly bowing to Ahab--"is
+apt to be facetious at times; he spins us many clever things of that
+sort. But I may as well say--en passant, as the French remark--that
+I myself--that is to say, Jack Bunger, late of the reverend
+clergy--am a strict total abstinence man; I never drink--"
+
+"Water!" cried the captain; "he never drinks it; it's a sort of fits
+to him; fresh water throws him into the hydrophobia; but go on--go on
+with the arm story."
+
+"Yes, I may as well," said the surgeon, coolly. "I was about
+observing, sir, before Captain Boomer's facetious interruption, that
+spite of my best and severest endeavors, the wound kept getting worse
+and worse; the truth was, sir, it was as ugly gaping wound as surgeon
+ever saw; more than two feet and several inches long. I measured it
+with the lead line. In short, it grew black; I knew what was
+threatened, and off it came. But I had no hand in shipping that
+ivory arm there; that thing is against all rule"--pointing at it with
+the marlingspike--"that is the captain's work, not mine; he ordered
+the carpenter to make it; he had that club-hammer there put to the
+end, to knock some one's brains out with, I suppose, as he tried mine
+once. He flies into diabolical passions sometimes. Do ye see this
+dent, sir"--removing his hat, and brushing aside his hair, and
+exposing a bowl-like cavity in his skull, but which bore not the
+slightest scarry trace, or any token of ever having been a
+wound--"Well, the captain there will tell you how that came here;
+he knows."
+
+"No, I don't," said the captain, "but his mother did; he was born
+with it. Oh, you solemn rogue, you--you Bunger! was there ever such
+another Bunger in the watery world? Bunger, when you die, you ought
+to die in pickle, you dog; you should be preserved to future ages,
+you rascal."
+
+"What became of the White Whale?" now cried Ahab, who thus far had
+been impatiently listening to this by-play between the two
+Englishmen.
+
+"Oh!" cried the one-armed captain, "oh, yes! Well; after he sounded,
+we didn't see him again for some time; in fact, as I before hinted, I
+didn't then know what whale it was that had served me such a trick,
+till some time afterwards, when coming back to the Line, we heard
+about Moby Dick--as some call him--and then I knew it was he."
+
+"Did'st thou cross his wake again?"
+
+"Twice."
+
+"But could not fasten?"
+
+"Didn't want to try to: ain't one limb enough? What should I do
+without this other arm? And I'm thinking Moby Dick doesn't bite so
+much as he swallows."
+
+"Well, then," interrupted Bunger, "give him your left arm for bait to
+get the right. Do you know, gentlemen"--very gravely and
+mathematically bowing to each Captain in succession--"Do you know,
+gentlemen, that the digestive organs of the whale are so inscrutably
+constructed by Divine Providence, that it is quite impossible for him
+to completely digest even a man's arm? And he knows it too. So that
+what you take for the White Whale's malice is only his awkwardness.
+For he never means to swallow a single limb; he only thinks to
+terrify by feints. But sometimes he is like the old juggling fellow,
+formerly a patient of mine in Ceylon, that making believe swallow
+jack-knives, once upon a time let one drop into him in good earnest,
+and there it stayed for a twelvemonth or more; when I gave him an
+emetic, and he heaved it up in small tacks, d'ye see. No possible
+way for him to digest that jack-knife, and fully incorporate it into
+his general bodily system. Yes, Captain Boomer, if you are quick
+enough about it, and have a mind to pawn one arm for the sake of the
+privilege of giving decent burial to the other, why in that case
+the arm is yours; only let the whale have another chance at you
+shortly, that's all."
+
+"No, thank ye, Bunger," said the English Captain, "he's welcome to
+the arm he has, since I can't help it, and didn't know him then; but
+not to another one. No more White Whales for me; I've lowered for
+him once, and that has satisfied me. There would be great glory in
+killing him, I know that; and there is a ship-load of precious sperm
+in him, but, hark ye, he's best let alone; don't you think so,
+Captain?"--glancing at the ivory leg.
+
+"He is. But he will still be hunted, for all that. What is best let
+alone, that accursed thing is not always what least allures. He's
+all a magnet! How long since thou saw'st him last? Which way
+heading?"
+
+"Bless my soul, and curse the foul fiend's," cried Bunger, stoopingly
+walking round Ahab, and like a dog, strangely snuffing; "this man's
+blood--bring the thermometer!--it's at the boiling point!--his pulse
+makes these planks beat!--sir!"--taking a lancet from his pocket, and
+drawing near to Ahab's arm.
+
+"Avast!" roared Ahab, dashing him against the bulwarks--"Man the
+boat! Which way heading?"
+
+"Good God!" cried the English Captain, to whom the question was put.
+"What's the matter? He was heading east, I think.--Is your Captain
+crazy?" whispering Fedallah.
+
+But Fedallah, putting a finger on his lip, slid over the bulwarks to
+take the boat's steering oar, and Ahab, swinging the cutting-tackle
+towards him, commanded the ship's sailors to stand by to lower.
+
+In a moment he was standing in the boat's stern, and the Manilla men
+were springing to their oars. In vain the English Captain hailed
+him. With back to the stranger ship, and face set like a flint to
+his own, Ahab stood upright till alongside of the Pequod.
+
+
+
+CHAPTER 101
+
+The Decanter.
+
+
+Ere the English ship fades from sight, be it set down here, that she
+hailed from London, and was named after the late Samuel Enderby,
+merchant of that city, the original of the famous whaling house of
+Enderby & Sons; a house which in my poor whaleman's opinion, comes
+not far behind the united royal houses of the Tudors and Bourbons, in
+point of real historical interest. How long, prior to the year of
+our Lord 1775, this great whaling house was in existence, my numerous
+fish-documents do not make plain; but in that year (1775) it fitted
+out the first English ships that ever regularly hunted the Sperm
+Whale; though for some score of years previous (ever since 1726) our
+valiant Coffins and Maceys of Nantucket and the Vineyard had in large
+fleets pursued that Leviathan, but only in the North and South
+Atlantic: not elsewhere. Be it distinctly recorded here, that the
+Nantucketers were the first among mankind to harpoon with civilized
+steel the great Sperm Whale; and that for half a century they were
+the only people of the whole globe who so harpooned him.
+
+In 1778, a fine ship, the Amelia, fitted out for the express purpose,
+and at the sole charge of the vigorous Enderbys, boldly rounded Cape
+Horn, and was the first among the nations to lower a whale-boat of
+any sort in the great South Sea. The voyage was a skilful and lucky
+one; and returning to her berth with her hold full of the precious
+sperm, the Amelia's example was soon followed by other ships, English
+and American, and thus the vast Sperm Whale grounds of the Pacific
+were thrown open. But not content with this good deed, the
+indefatigable house again bestirred itself: Samuel and all his
+Sons--how many, their mother only knows--and under their immediate
+auspices, and partly, I think, at their expense, the British
+government was induced to send the sloop-of-war Rattler on a whaling
+voyage of discovery into the South Sea. Commanded by a naval
+Post-Captain, the Rattler made a rattling voyage of it, and did some
+service; how much does not appear. But this is not all. In 1819,
+the same house fitted out a discovery whale ship of their own, to go
+on a tasting cruise to the remote waters of Japan. That ship--well
+called the "Syren"--made a noble experimental cruise; and it was thus
+that the great Japanese Whaling Ground first became generally known.
+The Syren in this famous voyage was commanded by a Captain Coffin, a
+Nantucketer.
+
+All honour to the Enderbies, therefore, whose house, I think, exists
+to the present day; though doubtless the original Samuel must long
+ago have slipped his cable for the great South Sea of the other
+world.
+
+The ship named after him was worthy of the honour, being a very fast
+sailer and a noble craft every way. I boarded her once at midnight
+somewhere off the Patagonian coast, and drank good flip down in the
+forecastle. It was a fine gam we had, and they were all
+trumps--every soul on board. A short life to them, and a jolly
+death. And that fine gam I had--long, very long after old Ahab
+touched her planks with his ivory heel--it minds me of the noble,
+solid, Saxon hospitality of that ship; and may my parson forget me,
+and the devil remember me, if I ever lose sight of it. Flip? Did I
+say we had flip? Yes, and we flipped it at the rate of ten gallons
+the hour; and when the squall came (for it's squally off there by
+Patagonia), and all hands--visitors and all--were called to reef
+topsails, we were so top-heavy that we had to swing each other aloft
+in bowlines; and we ignorantly furled the skirts of our jackets into
+the sails, so that we hung there, reefed fast in the howling gale, a
+warning example to all drunken tars. However, the masts did not go
+overboard; and by and by we scrambled down, so sober, that we had to
+pass the flip again, though the savage salt spray bursting down the
+forecastle scuttle, rather too much diluted and pickled it to my
+taste.
+
+The beef was fine--tough, but with body in it. They said it was
+bull-beef; others, that it was dromedary beef; but I do not know, for
+certain, how that was. They had dumplings too; small, but
+substantial, symmetrically globular, and indestructible dumplings. I
+fancied that you could feel them, and roll them about in you after
+they were swallowed. If you stooped over too far forward, you risked
+their pitching out of you like billiard-balls. The bread--but that
+couldn't be helped; besides, it was an anti-scorbutic; in short, the
+bread contained the only fresh fare they had. But the forecastle was
+not very light, and it was very easy to step over into a dark corner
+when you ate it. But all in all, taking her from truck to helm,
+considering the dimensions of the cook's boilers, including his own
+live parchment boilers; fore and aft, I say, the Samuel Enderby was a
+jolly ship; of good fare and plenty; fine flip and strong; crack
+fellows all, and capital from boot heels to hat-band.
+
+But why was it, think ye, that the Samuel Enderby, and some other
+English whalers I know of--not all though--were such famous,
+hospitable ships; that passed round the beef, and the bread, and the
+can, and the joke; and were not soon weary of eating, and drinking,
+and laughing? I will tell you. The abounding good cheer of these
+English whalers is matter for historical research. Nor have I been
+at all sparing of historical whale research, when it has seemed
+needed.
+
+The English were preceded in the whale fishery by the Hollanders,
+Zealanders, and Danes; from whom they derived many terms still extant
+in the fishery; and what is yet more, their fat old fashions,
+touching plenty to eat and drink. For, as a general thing, the
+English merchant-ship scrimps her crew; but not so the English
+whaler. Hence, in the English, this thing of whaling good cheer is
+not normal and natural, but incidental and particular; and,
+therefore, must have some special origin, which is here pointed out,
+and will be still further elucidated.
+
+During my researches in the Leviathanic histories, I stumbled upon an
+ancient Dutch volume, which, by the musty whaling smell of it, I knew
+must be about whalers. The title was, "Dan Coopman," wherefore I
+concluded that this must be the invaluable memoirs of some Amsterdam
+cooper in the fishery, as every whale ship must carry its cooper. I
+was reinforced in this opinion by seeing that it was the production
+of one "Fitz Swackhammer." But my friend Dr. Snodhead, a very
+learned man, professor of Low Dutch and High German in the college of
+Santa Claus and St. Pott's, to whom I handed the work for
+translation, giving him a box of sperm candles for his trouble--this
+same Dr. Snodhead, so soon as he spied the book, assured me that "Dan
+Coopman" did not mean "The Cooper," but "The Merchant." In short,
+this ancient and learned Low Dutch book treated of the commerce of
+Holland; and, among other subjects, contained a very interesting
+account of its whale fishery. And in this chapter it was, headed,
+"Smeer," or "Fat," that I found a long detailed list of the outfits
+for the larders and cellars of 180 sail of Dutch whalemen; from which
+list, as translated by Dr. Snodhead, I transcribe the following:
+
+400,000 lbs. of beef.
+60,000 lbs. Friesland pork.
+150,000 lbs. of stock fish.
+550,000 lbs. of biscuit.
+72,000 lbs. of soft bread.
+2,800 firkins of butter.
+20,000 lbs. Texel & Leyden cheese.
+144,000 lbs. cheese (probably an inferior article).
+550 ankers of Geneva.
+10,800 barrels of beer.
+
+Most statistical tables are parchingly dry in the reading; not so in
+the present case, however, where the reader is flooded with whole
+pipes, barrels, quarts, and gills of good gin and good cheer.
+
+At the time, I devoted three days to the studious digesting of all
+this beer, beef, and bread, during which many profound thoughts were
+incidentally suggested to me, capable of a transcendental and
+Platonic application; and, furthermore, I compiled supplementary
+tables of my own, touching the probable quantity of stock-fish, etc.,
+consumed by every Low Dutch harpooneer in that ancient Greenland and
+Spitzbergen whale fishery. In the first place, the amount of butter,
+and Texel and Leyden cheese consumed, seems amazing. I impute it,
+though, to their naturally unctuous natures, being rendered still
+more unctuous by the nature of their vocation, and especially by
+their pursuing their game in those frigid Polar Seas, on the very
+coasts of that Esquimaux country where the convivial natives pledge
+each other in bumpers of train oil.
+
+The quantity of beer, too, is very large, 10,800 barrels. Now,
+as those polar fisheries could only be prosecuted in the short summer
+of that climate, so that the whole cruise of one of these Dutch
+whalemen, including the short voyage to and from the Spitzbergen sea,
+did not much exceed three months, say, and reckoning 30 men to each
+of their fleet of 180 sail, we have 5,400 Low Dutch seamen in all;
+therefore, I say, we have precisely two barrels of beer per man, for
+a twelve weeks' allowance, exclusive of his fair proportion of that
+550 ankers of gin. Now, whether these gin and beer harpooneers, so
+fuddled as one might fancy them to have been, were the right sort of
+men to stand up in a boat's head, and take good aim at flying whales;
+this would seem somewhat improbable. Yet they did aim at them, and
+hit them too. But this was very far North, be it remembered, where
+beer agrees well with the constitution; upon the Equator, in our
+southern fishery, beer would be apt to make the harpooneer sleepy at
+the mast-head and boozy in his boat; and grievous loss might ensue to
+Nantucket and New Bedford.
+
+But no more; enough has been said to show that the old Dutch whalers
+of two or three centuries ago were high livers; and that the English
+whalers have not neglected so excellent an example. For, say they,
+when cruising in an empty ship, if you can get nothing better out of
+the world, get a good dinner out of it, at least. And this empties
+the decanter.
+
+
+
+CHAPTER 102
+
+A Bower in the Arsacides.
+
+
+Hitherto, in descriptively treating of the Sperm Whale, I have
+chiefly dwelt upon the marvels of his outer aspect; or separately and
+in detail upon some few interior structural features. But to a large
+and thorough sweeping comprehension of him, it behooves me now to
+unbutton him still further, and untagging the points of his hose,
+unbuckling his garters, and casting loose the hooks and the eyes of
+the joints of his innermost bones, set him before you in his
+ultimatum; that is to say, in his unconditional skeleton.
+
+But how now, Ishmael? How is it, that you, a mere oarsman in the
+fishery, pretend to know aught about the subterranean parts of the
+whale? Did erudite Stubb, mounted upon your capstan, deliver
+lectures on the anatomy of the Cetacea; and by help of the windlass,
+hold up a specimen rib for exhibition? Explain thyself, Ishmael.
+Can you land a full-grown whale on your deck for examination, as a
+cook dishes a roast-pig? Surely not. A veritable witness have you
+hitherto been, Ishmael; but have a care how you seize the privilege
+of Jonah alone; the privilege of discoursing upon the joists and
+beams; the rafters, ridge-pole, sleepers, and under-pinnings, making
+up the frame-work of leviathan; and belike of the tallow-vats,
+dairy-rooms, butteries, and cheeseries in his bowels.
+
+I confess, that since Jonah, few whalemen have penetrated very far
+beneath the skin of the adult whale; nevertheless, I have been
+blessed with an opportunity to dissect him in miniature. In a ship I
+belonged to, a small cub Sperm Whale was once bodily hoisted to the
+deck for his poke or bag, to make sheaths for the barbs of the
+harpoons, and for the heads of the lances. Think you I let that
+chance go, without using my boat-hatchet and jack-knife, and breaking
+the seal and reading all the contents of that young cub?
+
+And as for my exact knowledge of the bones of the leviathan in their
+gigantic, full grown development, for that rare knowledge I am
+indebted to my late royal friend Tranquo, king of Tranque, one of
+the Arsacides. For being at Tranque, years ago, when attached to the
+trading-ship Dey of Algiers, I was invited to spend part of the
+Arsacidean holidays with the lord of Tranque, at his retired palm
+villa at Pupella; a sea-side glen not very far distant from what our
+sailors called Bamboo-Town, his capital.
+
+Among many other fine qualities, my royal friend Tranquo, being
+gifted with a devout love for all matters of barbaric vertu, had
+brought together in Pupella whatever rare things the more ingenious
+of his people could invent; chiefly carved woods of wonderful
+devices, chiselled shells, inlaid spears, costly paddles, aromatic
+canoes; and all these distributed among whatever natural wonders, the
+wonder-freighted, tribute-rendering waves had cast upon his shores.
+
+Chief among these latter was a great Sperm Whale, which, after an
+unusually long raging gale, had been found dead and stranded, with
+his head against a cocoa-nut tree, whose plumage-like, tufted
+droopings seemed his verdant jet. When the vast body had at last
+been stripped of its fathom-deep enfoldings, and the bones become
+dust dry in the sun, then the skeleton was carefully transported up
+the Pupella glen, where a grand temple of lordly palms now sheltered
+it.
+
+The ribs were hung with trophies; the vertebrae were carved with
+Arsacidean annals, in strange hieroglyphics; in the skull, the
+priests kept up an unextinguished aromatic flame, so that the mystic
+head again sent forth its vapoury spout; while, suspended from a
+bough, the terrific lower jaw vibrated over all the devotees, like
+the hair-hung sword that so affrighted Damocles.
+
+It was a wondrous sight. The wood was green as mosses of the Icy
+Glen; the trees stood high and haughty, feeling their living sap; the
+industrious earth beneath was as a weaver's loom, with a gorgeous
+carpet on it, whereof the ground-vine tendrils formed the warp and
+woof, and the living flowers the figures. All the trees, with all
+their laden branches; all the shrubs, and ferns, and grasses; the
+message-carrying air; all these unceasingly were active. Through the
+lacings of the leaves, the great sun seemed a flying shuttle weaving
+the unwearied verdure. Oh, busy weaver! unseen weaver!--pause!--one
+word!--whither flows the fabric? what palace may it deck? wherefore
+all these ceaseless toilings? Speak, weaver!--stay thy hand!--but
+one single word with thee! Nay--the shuttle flies--the figures float
+from forth the loom; the freshet-rushing carpet for ever slides
+away. The weaver-god, he weaves; and by that weaving is he deafened,
+that he hears no mortal voice; and by that humming, we, too, who look
+on the loom are deafened; and only when we escape it shall we hear
+the thousand voices that speak through it. For even so it is in all
+material factories. The spoken words that are inaudible among the
+flying spindles; those same words are plainly heard without the
+walls, bursting from the opened casements. Thereby have villainies
+been detected. Ah, mortal! then, be heedful; for so, in all this din
+of the great world's loom, thy subtlest thinkings may be overheard
+afar.
+
+Now, amid the green, life-restless loom of that Arsacidean wood, the
+great, white, worshipped skeleton lay lounging--a gigantic idler!
+Yet, as the ever-woven verdant warp and woof intermixed and hummed
+around him, the mighty idler seemed the cunning weaver; himself all
+woven over with the vines; every month assuming greener, fresher
+verdure; but himself a skeleton. Life folded Death; Death trellised
+Life; the grim god wived with youthful Life, and begat him
+curly-headed glories.
+
+Now, when with royal Tranquo I visited this wondrous whale, and saw
+the skull an altar, and the artificial smoke ascending from where the
+real jet had issued, I marvelled that the king should regard a chapel
+as an object of vertu. He laughed. But more I marvelled that the
+priests should swear that smoky jet of his was genuine. To and fro I
+paced before this skeleton--brushed the vines aside--broke through
+the ribs--and with a ball of Arsacidean twine, wandered, eddied long
+amid its many winding, shaded colonnades and arbours. But soon my
+line was out; and following it back, I emerged from the opening where I
+entered. I saw no living thing within; naught was there but bones.
+
+Cutting me a green measuring-rod, I once more dived within the
+skeleton. From their arrow-slit in the skull, the priests perceived
+me taking the altitude of the final rib, "How now!" they shouted;
+"Dar'st thou measure this our god! That's for us." "Aye,
+priests--well, how long do ye make him, then?" But hereupon a fierce
+contest rose among them, concerning feet and inches; they cracked
+each other's sconces with their yard-sticks--the great skull
+echoed--and seizing that lucky chance, I quickly concluded my own
+admeasurements.
+
+These admeasurements I now propose to set before you. But first, be
+it recorded, that, in this matter, I am not free to utter any fancied
+measurement I please. Because there are skeleton authorities you
+can refer to, to test my accuracy. There is a Leviathanic Museum,
+they tell me, in Hull, England, one of the whaling ports of that
+country, where they have some fine specimens of fin-backs and other
+whales. Likewise, I have heard that in the museum of Manchester, in
+New Hampshire, they have what the proprietors call "the only perfect
+specimen of a Greenland or River Whale in the United States."
+Moreover, at a place in Yorkshire, England, Burton Constable by name,
+a certain Sir Clifford Constable has in his possession the skeleton
+of a Sperm Whale, but of moderate size, by no means of the full-grown
+magnitude of my friend King Tranquo's.
+
+In both cases, the stranded whales to which these two skeletons
+belonged, were originally claimed by their proprietors upon similar
+grounds. King Tranquo seizing his because he wanted it; and Sir
+Clifford, because he was lord of the seignories of those parts. Sir
+Clifford's whale has been articulated throughout; so that, like a
+great chest of drawers, you can open and shut him, in all his bony
+cavities--spread out his ribs like a gigantic fan--and swing all day
+upon his lower jaw. Locks are to be put upon some of his trap-doors
+and shutters; and a footman will show round future visitors with a
+bunch of keys at his side. Sir Clifford thinks of charging twopence
+for a peep at the whispering gallery in the spinal column; threepence
+to hear the echo in the hollow of his cerebellum; and sixpence for
+the unrivalled view from his forehead.
+
+The skeleton dimensions I shall now proceed to set down are copied
+verbatim from my right arm, where I had them tattooed; as in my wild
+wanderings at that period, there was no other secure way of
+preserving such valuable statistics. But as I was crowded for space,
+and wished the other parts of my body to remain a blank page for a
+poem I was then composing--at least, what untattooed parts might
+remain--I did not trouble myself with the odd inches; nor, indeed,
+should inches at all enter into a congenial admeasurement of the
+whale.
+
+
+
+CHAPTER 103
+
+Measurement of The Whale's Skeleton.
+
+
+In the first place, I wish to lay before you a particular, plain
+statement, touching the living bulk of this leviathan, whose skeleton
+we are briefly to exhibit. Such a statement may prove useful here.
+
+According to a careful calculation I have made, and which I partly
+base upon Captain Scoresby's estimate, of seventy tons for the
+largest sized Greenland whale of sixty feet in length; according to
+my careful calculation, I say, a Sperm Whale of the largest
+magnitude, between eighty-five and ninety feet in length, and
+something less than forty feet in its fullest circumference, such a
+whale will weigh at least ninety tons; so that, reckoning thirteen
+men to a ton, he would considerably outweigh the combined population
+of a whole village of one thousand one hundred inhabitants.
+
+Think you not then that brains, like yoked cattle, should be put to
+this leviathan, to make him at all budge to any landsman's
+imagination?
+
+Having already in various ways put before you his skull, spout-hole,
+jaw, teeth, tail, forehead, fins, and divers other parts, I shall now
+simply point out what is most interesting in the general bulk of his
+unobstructed bones. But as the colossal skull embraces so very large
+a proportion of the entire extent of the skeleton; as it is by far
+the most complicated part; and as nothing is to be repeated
+concerning it in this chapter, you must not fail to carry it in your
+mind, or under your arm, as we proceed, otherwise you will not gain a
+complete notion of the general structure we are about to view.
+
+In length, the Sperm Whale's skeleton at Tranque measured seventy-two
+Feet; so that when fully invested and extended in life, he must have
+been ninety feet long; for in the whale, the skeleton loses about one
+fifth in length compared with the living body. Of this seventy-two
+feet, his skull and jaw comprised some twenty feet, leaving some
+fifty feet of plain back-bone. Attached to this back-bone, for
+something less than a third of its length, was the mighty circular
+basket of ribs which once enclosed his vitals.
+
+To me this vast ivory-ribbed chest, with the long, unrelieved spine,
+extending far away from it in a straight line, not a little resembled
+the hull of a great ship new-laid upon the stocks, when only some
+twenty of her naked bow-ribs are inserted, and the keel is otherwise,
+for the time, but a long, disconnected timber.
+
+The ribs were ten on a side. The first, to begin from the neck, was
+nearly six feet long; the second, third, and fourth were each
+successively longer, till you came to the climax of the fifth, or one
+of the middle ribs, which measured eight feet and some inches. From
+that part, the remaining ribs diminished, till the tenth and last
+only spanned five feet and some inches. In general thickness, they
+all bore a seemly correspondence to their length. The middle ribs
+were the most arched. In some of the Arsacides they are used for
+beams whereon to lay footpath bridges over small streams.
+
+In considering these ribs, I could not but be struck anew with the
+circumstance, so variously repeated in this book, that the skeleton
+of the whale is by no means the mould of his invested form. The
+largest of the Tranque ribs, one of the middle ones, occupied that
+part of the fish which, in life, is greatest in depth. Now, the
+greatest depth of the invested body of this particular whale must
+have been at least sixteen feet; whereas, the corresponding rib
+measured but little more than eight feet. So that this rib only
+conveyed half of the true notion of the living magnitude of that
+part. Besides, for some way, where I now saw but a naked spine, all
+that had been once wrapped round with tons of added bulk in flesh,
+muscle, blood, and bowels. Still more, for the ample fins, I here
+saw but a few disordered joints; and in place of the weighty and
+majestic, but boneless flukes, an utter blank!
+
+How vain and foolish, then, thought I, for timid untravelled man to
+try to comprehend aright this wondrous whale, by merely poring over
+his dead attenuated skeleton, stretched in this peaceful wood. No.
+Only in the heart of quickest perils; only when within the eddyings
+of his angry flukes; only on the profound unbounded sea, can the
+fully invested whale be truly and livingly found out.
+
+But the spine. For that, the best way we can consider it is, with a
+crane, to pile its bones high up on end. No speedy enterprise. But
+now it's done, it looks much like Pompey's Pillar.
+
+There are forty and odd vertebrae in all, which in the skeleton are
+not locked together. They mostly lie like the great knobbed blocks
+on a Gothic spire, forming solid courses of heavy masonry. The
+largest, a middle one, is in width something less than three feet,
+and in depth more than four. The smallest, where the spine tapers
+away into the tail, is only two inches in width, and looks something
+like a white billiard-ball. I was told that there were still smaller
+ones, but they had been lost by some little cannibal urchins, the
+priest's children, who had stolen them to play marbles with. Thus we
+see how that the spine of even the hugest of living things tapers off
+at last into simple child's play.
+
+
+
+CHAPTER 104
+
+The Fossil Whale.
+
+
+From his mighty bulk the whale affords a most congenial theme whereon
+to enlarge, amplify, and generally expatiate. Would you, you could
+not compress him. By good rights he should only be treated of in
+imperial folio. Not to tell over again his furlongs from spiracle to
+tail, and the yards he measures about the waist; only think of the
+gigantic involutions of his intestines, where they lie in him like
+great cables and hawsers coiled away in the subterranean orlop-deck
+of a line-of-battle-ship.
+
+Since I have undertaken to manhandle this Leviathan, it behooves me
+to approve myself omnisciently exhaustive in the enterprise; not
+overlooking the minutest seminal germs of his blood, and spinning him
+out to the uttermost coil of his bowels. Having already described
+him in most of his present habitatory and anatomical peculiarities,
+it now remains to magnify him in an archaeological, fossiliferous,
+and antediluvian point of view. Applied to any other creature than
+the Leviathan--to an ant or a flea--such portly terms might justly be
+deemed unwarrantably grandiloquent. But when Leviathan is the text,
+the case is altered. Fain am I to stagger to this emprise under
+the weightiest words of the dictionary. And here be it said, that
+whenever it has been convenient to consult one in the course of these
+dissertations, I have invariably used a huge quarto edition of
+Johnson, expressly purchased for that purpose; because that famous
+lexicographer's uncommon personal bulk more fitted him to compile a
+lexicon to be used by a whale author like me.
+
+One often hears of writers that rise and swell with their subject,
+though it may seem but an ordinary one. How, then, with me, writing
+of this Leviathan? Unconsciously my chirography expands into placard
+capitals. Give me a condor's quill! Give me Vesuvius' crater for an
+inkstand! Friends, hold my arms! For in the mere act of penning my
+thoughts of this Leviathan, they weary me, and make me faint with
+their outreaching comprehensiveness of sweep, as if to include the
+whole circle of the sciences, and all the generations of whales, and
+men, and mastodons, past, present, and to come, with all the
+revolving panoramas of empire on earth, and throughout the whole
+universe, not excluding its suburbs. Such, and so magnifying, is the
+virtue of a large and liberal theme! We expand to its bulk. To
+produce a mighty book, you must choose a mighty theme. No great and
+enduring volume can ever be written on the flea, though many there be
+who have tried it.
+
+Ere entering upon the subject of Fossil Whales, I present my
+credentials as a geologist, by stating that in my miscellaneous time
+I have been a stone-mason, and also a great digger of ditches,
+canals and wells, wine-vaults, cellars, and cisterns of all sorts.
+Likewise, by way of preliminary, I desire to remind the reader, that
+while in the earlier geological strata there are found the fossils of
+monsters now almost completely extinct; the subsequent relics
+discovered in what are called the Tertiary formations seem the
+connecting, or at any rate intercepted links, between the
+antichronical creatures, and those whose remote posterity are said to
+have entered the Ark; all the Fossil Whales hitherto discovered
+belong to the Tertiary period, which is the last preceding the
+superficial formations. And though none of them precisely answer to
+any known species of the present time, they are yet sufficiently akin
+to them in general respects, to justify their taking rank as
+Cetacean fossils.
+
+Detached broken fossils of pre-adamite whales, fragments of their
+bones and skeletons, have within thirty years past, at various
+intervals, been found at the base of the Alps, in Lombardy, in
+France, in England, in Scotland, and in the States of Louisiana,
+Mississippi, and Alabama. Among the more curious of such remains is
+part of a skull, which in the year 1779 was disinterred in the Rue
+Dauphine in Paris, a short street opening almost directly upon the
+palace of the Tuileries; and bones disinterred in excavating the
+great docks of Antwerp, in Napoleon's time. Cuvier pronounced these
+fragments to have belonged to some utterly unknown Leviathanic
+species.
+
+But by far the most wonderful of all Cetacean relics was the almost
+complete vast skeleton of an extinct monster, found in the year 1842,
+on the plantation of Judge Creagh, in Alabama. The awe-stricken
+credulous slaves in the vicinity took it for the bones of one of the
+fallen angels. The Alabama doctors declared it a huge reptile, and
+bestowed upon it the name of Basilosaurus. But some specimen bones
+of it being taken across the sea to Owen, the English Anatomist, it
+turned out that this alleged reptile was a whale, though of a
+departed species. A significant illustration of the fact, again and
+again repeated in this book, that the skeleton of the whale furnishes
+but little clue to the shape of his fully invested body. So Owen
+rechristened the monster Zeuglodon; and in his paper read before the
+London Geological Society, pronounced it, in substance, one of the
+most extraordinary creatures which the mutations of the globe have
+blotted out of existence.
+
+When I stand among these mighty Leviathan skeletons, skulls, tusks,
+jaws, ribs, and vertebrae, all characterized by partial resemblances
+to the existing breeds of sea-monsters; but at the same time bearing
+on the other hand similar affinities to the annihilated antichronical
+Leviathans, their incalculable seniors; I am, by a flood, borne back
+to that wondrous period, ere time itself can be said to have begun;
+for time began with man. Here Saturn's grey chaos rolls over me, and
+I obtain dim, shuddering glimpses into those Polar eternities; when
+wedged bastions of ice pressed hard upon what are now the Tropics;
+and in all the 25,000 miles of this world's circumference, not an
+inhabitable hand's breadth of land was visible. Then the whole world
+was the whale's; and, king of creation, he left his wake along the
+present lines of the Andes and the Himmalehs. Who can show a
+pedigree like Leviathan? Ahab's harpoon had shed older blood than
+the Pharaoh's. Methuselah seems a school-boy. I look round to shake
+hands with Shem. I am horror-struck at this antemosaic, unsourced
+existence of the unspeakable terrors of the whale, which, having been
+before all time, must needs exist after all humane ages are over.
+
+But not alone has this Leviathan left his pre-adamite traces in the
+stereotype plates of nature, and in limestone and marl bequeathed his
+ancient bust; but upon Egyptian tablets, whose antiquity seems to
+claim for them an almost fossiliferous character, we find the
+unmistakable print of his fin. In an apartment of the great temple
+of Denderah, some fifty years ago, there was discovered upon the
+granite ceiling a sculptured and painted planisphere, abounding in
+centaurs, griffins, and dolphins, similar to the grotesque figures
+on the celestial globe of the moderns. Gliding among them, old
+Leviathan swam as of yore; was there swimming in that planisphere,
+centuries before Solomon was cradled.
+
+Nor must there be omitted another strange attestation of the
+antiquity of the whale, in his own osseous post-diluvian reality, as
+set down by the venerable John Leo, the old Barbary traveller.
+
+"Not far from the Sea-side, they have a Temple, the Rafters and Beams
+of which are made of Whale-Bones; for Whales of a monstrous size are
+oftentimes cast up dead upon that shore. The Common People imagine,
+that by a secret Power bestowed by God upon the temple, no Whale can
+pass it without immediate death. But the truth of the Matter is,
+that on either side of the Temple, there are Rocks that shoot two
+Miles into the Sea, and wound the Whales when they light upon 'em.
+They keep a Whale's Rib of an incredible length for a Miracle, which
+lying upon the Ground with its convex part uppermost, makes an Arch,
+the Head of which cannot be reached by a Man upon a Camel's Back.
+This Rib (says John Leo) is said to have layn there a hundred Years
+before I saw it. Their Historians affirm, that a Prophet who
+prophesy'd of Mahomet, came from this Temple, and some do not stand
+to assert, that the Prophet Jonas was cast forth by the Whale at the
+Base of the Temple."
+
+In this Afric Temple of the Whale I leave you, reader, and if you be
+a Nantucketer, and a whaleman, you will silently worship there.
+
+
+
+CHAPTER 105
+
+Does the Whale's Magnitude Diminish?--Will He Perish?
+
+
+Inasmuch, then, as this Leviathan comes floundering down upon us from
+the head-waters of the Eternities, it may be fitly inquired, whether,
+in the long course of his generations, he has not degenerated from
+the original bulk of his sires.
+
+But upon investigation we find, that not only are the whales of the
+present day superior in magnitude to those whose fossil remains are
+found in the Tertiary system (embracing a distinct geological period
+prior to man), but of the whales found in that Tertiary system, those
+belonging to its latter formations exceed in size those of its
+earlier ones.
+
+Of all the pre-adamite whales yet exhumed, by far the largest is the
+Alabama one mentioned in the last chapter, and that was less than
+seventy feet in length in the skeleton. Whereas, we have already
+seen, that the tape-measure gives seventy-two feet for the skeleton
+of a large sized modern whale. And I have heard, on whalemen's
+authority, that Sperm Whales have been captured near a hundred feet
+long at the time of capture.
+
+But may it not be, that while the whales of the present hour are an
+advance in magnitude upon those of all previous geological periods;
+may it not be, that since Adam's time they have degenerated?
+
+Assuredly, we must conclude so, if we are to credit the accounts of
+such gentlemen as Pliny, and the ancient naturalists generally. For
+Pliny tells us of Whales that embraced acres of living bulk, and
+Aldrovandus of others which measured eight hundred feet in
+length--Rope Walks and Thames Tunnels of Whales! And even in the
+days of Banks and Solander, Cooke's naturalists, we find a Danish
+member of the Academy of Sciences setting down certain Iceland Whales
+(reydan-siskur, or Wrinkled Bellies) at one hundred and twenty yards;
+that is, three hundred and sixty feet. And Lacepede, the French
+naturalist, in his elaborate history of whales, in the very beginning
+of his work (page 3), sets down the Right Whale at one hundred
+metres, three hundred and twenty-eight feet. And this work was
+published so late as A.D. 1825.
+
+But will any whaleman believe these stories? No. The whale of
+to-day is as big as his ancestors in Pliny's time. And if ever I go
+where Pliny is, I, a whaleman (more than he was), will make bold to
+tell him so. Because I cannot understand how it is, that while the
+Egyptian mummies that were buried thousands of years before even
+Pliny was born, do not measure so much in their coffins as a modern
+Kentuckian in his socks; and while the cattle and other animals
+sculptured on the oldest Egyptian and Nineveh tablets, by the
+relative proportions in which they are drawn, just as plainly prove
+that the high-bred, stall-fed, prize cattle of Smithfield, not only
+equal, but far exceed in magnitude the fattest of Pharaoh's fat kine;
+in the face of all this, I will not admit that of all animals the
+whale alone should have degenerated.
+
+But still another inquiry remains; one often agitated by the more
+recondite Nantucketers. Whether owing to the almost omniscient
+look-outs at the mast-heads of the whaleships, now penetrating even
+through Behring's straits, and into the remotest secret drawers and
+lockers of the world; and the thousand harpoons and lances darted
+along all continental coasts; the moot point is, whether Leviathan
+can long endure so wide a chase, and so remorseless a havoc; whether
+he must not at last be exterminated from the waters, and the last
+whale, like the last man, smoke his last pipe, and then himself
+evaporate in the final puff.
+
+Comparing the humped herds of whales with the humped herds of
+buffalo, which, not forty years ago, overspread by tens of thousands
+the prairies of Illinois and Missouri, and shook their iron manes and
+scowled with their thunder-clotted brows upon the sites of populous
+river-capitals, where now the polite broker sells you land at a
+dollar an inch; in such a comparison an irresistible argument would
+seem furnished, to show that the hunted whale cannot now escape
+speedy extinction.
+
+But you must look at this matter in every light. Though so short a
+period ago--not a good lifetime--the census of the buffalo in
+Illinois exceeded the census of men now in London, and though at the
+present day not one horn or hoof of them remains in all that region;
+and though the cause of this wondrous extermination was the spear of
+man; yet the far different nature of the whale-hunt peremptorily
+forbids so inglorious an end to the Leviathan. Forty men in one ship
+hunting the Sperm Whales for forty-eight months think they have done
+extremely well, and thank God, if at last they carry home the oil of
+forty fish. Whereas, in the days of the old Canadian and Indian
+hunters and trappers of the West, when the far west (in whose sunset
+suns still rise) was a wilderness and a virgin, the same number of
+moccasined men, for the same number of months, mounted on horse
+instead of sailing in ships, would have slain not forty, but forty
+thousand and more buffaloes; a fact that, if need were, could be
+statistically stated.
+
+Nor, considered aright, does it seem any argument in favour of the
+gradual extinction of the Sperm Whale, for example, that in former
+years (the latter part of the last century, say) these Leviathans, in
+small pods, were encountered much oftener than at present, and, in
+consequence, the voyages were not so prolonged, and were also much
+more remunerative. Because, as has been elsewhere noticed, those
+whales, influenced by some views to safety, now swim the seas in
+immense caravans, so that to a large degree the scattered solitaries,
+yokes, and pods, and schools of other days are now aggregated into
+vast but widely separated, unfrequent armies. That is all. And
+equally fallacious seems the conceit, that because the so-called
+whale-bone whales no longer haunt many grounds in former years
+abounding with them, hence that species also is declining. For they
+are only being driven from promontory to cape; and if one coast is no
+longer enlivened with their jets, then, be sure, some other and
+remoter strand has been very recently startled by the unfamiliar
+spectacle.
+
+Furthermore: concerning these last mentioned Leviathans, they have
+two firm fortresses, which, in all human probability, will for ever
+remain impregnable. And as upon the invasion of their valleys, the
+frosty Swiss have retreated to their mountains; so, hunted from the
+savannas and glades of the middle seas, the whale-bone whales can at
+last resort to their Polar citadels, and diving under the ultimate
+glassy barriers and walls there, come up among icy fields and floes;
+and in a charmed circle of everlasting December, bid defiance to all
+pursuit from man.
+
+But as perhaps fifty of these whale-bone whales are harpooned for one
+cachalot, some philosophers of the forecastle have concluded that
+this positive havoc has already very seriously diminished their
+battalions. But though for some time past a number of these whales,
+not less than 13,000, have been annually slain on the nor'-west
+coast by the Americans alone; yet there are considerations which
+render even this circumstance of little or no account as an opposing
+argument in this matter.
+
+Natural as it is to be somewhat incredulous concerning the
+populousness of the more enormous creatures of the globe, yet what
+shall we say to Harto, the historian of Goa, when he tells us that at
+one hunting the King of Siam took 4,000 elephants; that in those
+regions elephants are numerous as droves of cattle in the temperate
+climes. And there seems no reason to doubt that if these elephants,
+which have now been hunted for thousands of years, by Semiramis, by
+Porus, by Hannibal, and by all the successive monarchs of the
+East--if they still survive there in great numbers, much more may the
+great whale outlast all hunting, since he has a pasture to expatiate
+in, which is precisely twice as large as all Asia, both Americas,
+Europe and Africa, New Holland, and all the Isles of the sea
+combined.
+
+Moreover: we are to consider, that from the presumed great longevity
+of whales, their probably attaining the age of a century and more,
+therefore at any one period of time, several distinct adult
+generations must be contemporary. And what that is, we may soon
+gain some idea of, by imagining all the grave-yards, cemeteries, and
+family vaults of creation yielding up the live bodies of all the men,
+women, and children who were alive seventy-five years ago; and adding
+this countless host to the present human population of the globe.
+
+Wherefore, for all these things, we account the whale immortal in his
+species, however perishable in his individuality. He swam the seas
+before the continents broke water; he once swam over the site of the
+Tuileries, and Windsor Castle, and the Kremlin. In Noah's flood he
+despised Noah's Ark; and if ever the world is to be again flooded,
+like the Netherlands, to kill off its rats, then the eternal whale
+will still survive, and rearing upon the topmost crest of the
+equatorial flood, spout his frothed defiance to the skies.
+
+
+
+CHAPTER 106
+
+Ahab's Leg.
+
+
+The precipitating manner in which Captain Ahab had quitted the Samuel
+Enderby of London, had not been unattended with some small violence
+to his own person. He had lighted with such energy upon a thwart of
+his boat that his ivory leg had received a half-splintering shock.
+And when after gaining his own deck, and his own pivot-hole there, he
+so vehemently wheeled round with an urgent command to the steersman
+(it was, as ever, something about his not steering inflexibly
+enough); then, the already shaken ivory received such an additional
+twist and wrench, that though it still remained entire, and to all
+appearances lusty, yet Ahab did not deem it entirely trustworthy.
+
+And, indeed, it seemed small matter for wonder, that for all his
+pervading, mad recklessness, Ahab did at times give careful heed to
+the condition of that dead bone upon which he partly stood. For it
+had not been very long prior to the Pequod's sailing from Nantucket,
+that he had been found one night lying prone upon the ground, and
+insensible; by some unknown, and seemingly inexplicable, unimaginable
+casualty, his ivory limb having been so violently displaced, that it
+had stake-wise smitten, and all but pierced his groin; nor was it
+without extreme difficulty that the agonizing wound was entirely
+cured.
+
+Nor, at the time, had it failed to enter his monomaniac mind, that
+all the anguish of that then present suffering was but the direct
+issue of a former woe; and he too plainly seemed to see, that as the
+most poisonous reptile of the marsh perpetuates his kind as
+inevitably as the sweetest songster of the grove; so, equally with
+every felicity, all miserable events do naturally beget their like.
+Yea, more than equally, thought Ahab; since both the ancestry and
+posterity of Grief go further than the ancestry and posterity of Joy.
+For, not to hint of this: that it is an inference from certain
+canonic teachings, that while some natural enjoyments here shall have
+no children born to them for the other world, but, on the contrary,
+shall be followed by the joy-childlessness of all hell's despair;
+whereas, some guilty mortal miseries shall still fertilely beget to
+themselves an eternally progressive progeny of griefs beyond the
+grave; not at all to hint of this, there still seems an inequality in
+the deeper analysis of the thing. For, thought Ahab, while even the
+highest earthly felicities ever have a certain unsignifying pettiness
+lurking in them, but, at bottom, all heartwoes, a mystic
+significance, and, in some men, an archangelic grandeur; so do their
+diligent tracings-out not belie the obvious deduction. To trail the
+genealogies of these high mortal miseries, carries us at last among
+the sourceless primogenitures of the gods; so that, in the face of
+all the glad, hay-making suns, and soft cymballing, round
+harvest-moons, we must needs give in to this: that the gods
+themselves are not for ever glad. The ineffaceable, sad birth-mark
+in the brow of man, is but the stamp of sorrow in the signers.
+
+Unwittingly here a secret has been divulged, which perhaps might more
+properly, in set way, have been disclosed before. With many other
+particulars concerning Ahab, always had it remained a mystery to
+some, why it was, that for a certain period, both before and after
+the sailing of the Pequod, he had hidden himself away with such
+Grand-Lama-like exclusiveness; and, for that one interval, sought
+speechless refuge, as it were, among the marble senate of the dead.
+Captain Peleg's bruited reason for this thing appeared by no means
+adequate; though, indeed, as touching all Ahab's deeper part, every
+revelation partook more of significant darkness than of explanatory
+light. But, in the end, it all came out; this one matter did, at
+least. That direful mishap was at the bottom of his temporary
+recluseness. And not only this, but to that ever-contracting,
+dropping circle ashore, who, for any reason, possessed the privilege
+of a less banned approach to him; to that timid circle the above
+hinted casualty--remaining, as it did, moodily unaccounted for by
+Ahab--invested itself with terrors, not entirely underived from the
+land of spirits and of wails. So that, through their zeal for him,
+they had all conspired, so far as in them lay, to muffle up the
+knowledge of this thing from others; and hence it was, that not till
+a considerable interval had elapsed, did it transpire upon the
+Pequod's decks.
+
+But be all this as it may; let the unseen, ambiguous synod in the
+air, or the vindictive princes and potentates of fire, have to do or
+not with earthly Ahab, yet, in this present matter of his leg, he
+took plain practical procedures;--he called the carpenter.
+
+And when that functionary appeared before him, he bade him without
+delay set about making a new leg, and directed the mates to see him
+supplied with all the studs and joists of jaw-ivory (Sperm Whale)
+which had thus far been accumulated on the voyage, in order that a
+careful selection of the stoutest, clearest-grained stuff might be
+secured. This done, the carpenter received orders to have the leg
+completed that night; and to provide all the fittings for it,
+independent of those pertaining to the distrusted one in use.
+Moreover, the ship's forge was ordered to be hoisted out of its
+temporary idleness in the hold; and, to accelerate the affair, the
+blacksmith was commanded to proceed at once to the forging of
+whatever iron contrivances might be needed.
+
+
+
+CHAPTER 107
+
+The Carpenter.
+
+
+Seat thyself sultanically among the moons of Saturn, and take high
+abstracted man alone; and he seems a wonder, a grandeur, and a woe.
+But from the same point, take mankind in mass, and for the most part,
+they seem a mob of unnecessary duplicates, both contemporary and
+hereditary. But most humble though he was, and far from furnishing
+an example of the high, humane abstraction; the Pequod's carpenter
+was no duplicate; hence, he now comes in person on this stage.
+
+Like all sea-going ship carpenters, and more especially those
+belonging to whaling vessels, he was, to a certain off-handed,
+practical extent, alike experienced in numerous trades and callings
+collateral to his own; the carpenter's pursuit being the ancient and
+outbranching trunk of all those numerous handicrafts which more or
+less have to do with wood as an auxiliary material. But, besides the
+application to him of the generic remark above, this carpenter of the
+Pequod was singularly efficient in those thousand nameless mechanical
+emergencies continually recurring in a large ship, upon a three or
+four years' voyage, in uncivilized and far-distant seas. For not to
+speak of his readiness in ordinary duties:--repairing stove boats,
+sprung spars, reforming the shape of clumsy-bladed oars, inserting
+bull's eyes in the deck, or new tree-nails in the side planks, and
+other miscellaneous matters more directly pertaining to his special
+business; he was moreover unhesitatingly expert in all manner of
+conflicting aptitudes, both useful and capricious.
+
+The one grand stage where he enacted all his various parts so
+manifold, was his vice-bench; a long rude ponderous table furnished
+with several vices, of different sizes, and both of iron and of wood.
+At all times except when whales were alongside, this bench was
+securely lashed athwartships against the rear of the Try-works.
+
+A belaying pin is found too large to be easily inserted into its
+hole: the carpenter claps it into one of his ever-ready vices, and
+straightway files it smaller. A lost land-bird of strange plumage
+strays on board, and is made a captive: out of clean shaved rods of
+right-whale bone, and cross-beams of sperm whale ivory, the carpenter
+makes a pagoda-looking cage for it. An oarsman sprains his wrist:
+the carpenter concocts a soothing lotion. Stubb longed for
+vermillion stars to be painted upon the blade of his every oar;
+screwing each oar in his big vice of wood, the carpenter
+symmetrically supplies the constellation. A sailor takes a fancy to
+wear shark-bone ear-rings: the carpenter drills his ears. Another
+has the toothache: the carpenter out pincers, and clapping one hand
+upon his bench bids him be seated there; but the poor fellow
+unmanageably winces under the unconcluded operation; whirling round
+the handle of his wooden vice, the carpenter signs him to clap his
+jaw in that, if he would have him draw the tooth.
+
+Thus, this carpenter was prepared at all points, and alike
+indifferent and without respect in all. Teeth he accounted bits of
+ivory; heads he deemed but top-blocks; men themselves he lightly held
+for capstans. But while now upon so wide a field thus variously
+accomplished and with such liveliness of expertness in him, too; all
+this would seem to argue some uncommon vivacity of intelligence. But
+not precisely so. For nothing was this man more remarkable, than for
+a certain impersonal stolidity as it were; impersonal, I say; for it
+so shaded off into the surrounding infinite of things, that it seemed
+one with the general stolidity discernible in the whole visible
+world; which while pauselessly active in uncounted modes, still
+eternally holds its peace, and ignores you, though you dig
+foundations for cathedrals. Yet was this half-horrible stolidity in
+him, involving, too, as it appeared, an all-ramifying
+heartlessness;--yet was it oddly dashed at times, with an old,
+crutch-like, antediluvian, wheezing humorousness, not unstreaked now
+and then with a certain grizzled wittiness; such as might have served
+to pass the time during the midnight watch on the bearded forecastle
+of Noah's ark. Was it that this old carpenter had been a life-long
+wanderer, whose much rolling, to and fro, not only had gathered no
+moss; but what is more, had rubbed off whatever small outward
+clingings might have originally pertained to him? He was a stript
+abstract; an unfractioned integral; uncompromised as a new-born babe;
+living without premeditated reference to this world or the next. You
+might almost say, that this strange uncompromisedness in him involved
+a sort of unintelligence; for in his numerous trades, he did not seem
+to work so much by reason or by instinct, or simply because he had
+been tutored to it, or by any intermixture of all these, even or
+uneven; but merely by a kind of deaf and dumb, spontaneous literal
+process. He was a pure manipulator; his brain, if he had ever had
+one, must have early oozed along into the muscles of his fingers. He
+was like one of those unreasoning but still highly useful, MULTUM IN
+PARVO, Sheffield contrivances, assuming the exterior--though a little
+swelled--of a common pocket knife; but containing, not only blades of
+various sizes, but also screw-drivers, cork-screws, tweezers, awls,
+pens, rulers, nail-filers, countersinkers. So, if his superiors
+wanted to use the carpenter for a screw-driver, all they had to do
+was to open that part of him, and the screw was fast: or if for
+tweezers, take him up by the legs, and there they were.
+
+Yet, as previously hinted, this omnitooled, open-and-shut carpenter,
+was, after all, no mere machine of an automaton. If he did not have
+a common soul in him, he had a subtle something that somehow
+anomalously did its duty. What that was, whether essence of
+quicksilver, or a few drops of hartshorn, there is no telling. But
+there it was; and there it had abided for now some sixty years or
+more. And this it was, this same unaccountable, cunning
+life-principle in him; this it was, that kept him a great part of the
+time soliloquizing; but only like an unreasoning wheel, which also
+hummingly soliloquizes; or rather, his body was a sentry-box and this
+soliloquizer on guard there, and talking all the time to keep himself
+awake.
+
+
+
+CHAPTER 108
+
+Ahab and the Carpenter.
+
+The Deck--First Night Watch.
+
+
+(CARPENTER STANDING BEFORE HIS VICE-BENCH, AND BY THE LIGHT OF TWO
+LANTERNS BUSILY FILING THE IVORY JOIST FOR THE LEG, WHICH JOIST IS
+FIRMLY FIXED IN THE VICE. SLABS OF IVORY, LEATHER STRAPS, PADS,
+SCREWS, AND VARIOUS TOOLS OF ALL SORTS LYING ABOUT THE BENCH.
+FORWARD, THE RED FLAME OF THE FORGE IS SEEN, WHERE THE BLACKSMITH IS
+AT WORK.)
+
+
+Drat the file, and drat the bone! That is hard which should be soft,
+and that is soft which should be hard. So we go, who file old jaws
+and shinbones. Let's try another. Aye, now, this works better
+(SNEEZES). Halloa, this bone dust is (SNEEZES)--why it's
+(SNEEZES)--yes it's (SNEEZES)--bless my soul, it won't let me speak!
+This is what an old fellow gets now for working in dead lumber. Saw
+a live tree, and you don't get this dust; amputate a live bone, and
+you don't get it (SNEEZES). Come, come, you old Smut, there, bear a
+hand, and let's have that ferule and buckle-screw; I'll be ready
+for them presently. Lucky now (SNEEZES) there's no knee-joint to
+make; that might puzzle a little; but a mere shinbone--why it's
+easy as making hop-poles; only I should like to put a good finish on.
+Time, time; if I but only had the time, I could turn him out as neat
+a leg now as ever (SNEEZES) scraped to a lady in a parlor. Those
+buckskin legs and calves of legs I've seen in shop windows wouldn't
+compare at all. They soak water, they do; and of course get
+rheumatic, and have to be doctored (SNEEZES) with washes and lotions,
+just like live legs. There; before I saw it off, now, I must call his
+old Mogulship, and see whether the length will be all right; too
+short, if anything, I guess. Ha! that's the heel; we are in luck;
+here he comes, or it's somebody else, that's certain.
+
+AHAB (ADVANCING)
+
+(DURING THE ENSUING SCENE, THE CARPENTER CONTINUES SNEEZING AT TIMES)
+
+
+Well, manmaker!
+
+Just in time, sir. If the captain pleases, I will now mark the
+length. Let me measure, sir.
+
+Measured for a leg! good. Well, it's not the first time. About it!
+There; keep thy finger on it. This is a cogent vice thou hast here,
+carpenter; let me feel its grip once. So, so; it does pinch some.
+
+Oh, sir, it will break bones--beware, beware!
+
+No fear; I like a good grip; I like to feel something in this
+slippery world that can hold, man. What's Prometheus about
+there?--the blacksmith, I mean--what's he about?
+
+He must be forging the buckle-screw, sir, now.
+
+Right. It's a partnership; he supplies the muscle part. He makes a
+fierce red flame there!
+
+Aye, sir; he must have the white heat for this kind of fine work.
+
+Um-m. So he must. I do deem it now a most meaning thing, that that
+old Greek, Prometheus, who made men, they say, should have been a
+blacksmith, and animated them with fire; for what's made in fire must
+properly belong to fire; and so hell's probable. How the soot flies!
+This must be the remainder the Greek made the Africans of.
+Carpenter, when he's through with that buckle, tell him to forge a
+pair of steel shoulder-blades; there's a pedlar aboard with a
+crushing pack.
+
+Sir?
+
+Hold; while Prometheus is about it, I'll order a complete man after a
+desirable pattern. Imprimis, fifty feet high in his socks; then,
+chest modelled after the Thames Tunnel; then, legs with roots to 'em,
+to stay in one place; then, arms three feet through the wrist; no
+heart at all, brass forehead, and about a quarter of an acre of fine
+brains; and let me see--shall I order eyes to see outwards? No, but
+put a sky-light on top of his head to illuminate inwards. There,
+take the order, and away.
+
+Now, what's he speaking about, and who's he speaking to, I should
+like to know? Shall I keep standing here? (ASIDE).
+
+'Tis but indifferent architecture to make a blind dome; here's one.
+No, no, no; I must have a lantern.
+
+Ho, ho! That's it, hey? Here are two, sir; one will serve my turn.
+
+What art thou thrusting that thief-catcher into my face for, man?
+Thrusted light is worse than presented pistols.
+
+I thought, sir, that you spoke to carpenter.
+
+
+Carpenter? why that's--but no;--a very tidy, and, I may say, an
+extremely gentlemanlike sort of business thou art in here,
+carpenter;--or would'st thou rather work in clay?
+
+Sir?--Clay? clay, sir? That's mud; we leave clay to ditchers, sir.
+
+The fellow's impious! What art thou sneezing about?
+
+Bone is rather dusty, sir.
+
+Take the hint, then; and when thou art dead, never bury thyself under
+living people's noses.
+
+Sir?--oh! ah!--I guess so;--yes--dear!
+
+Look ye, carpenter, I dare say thou callest thyself a right good
+workmanlike workman, eh? Well, then, will it speak thoroughly well
+for thy work, if, when I come to mount this leg thou makest, I shall
+nevertheless feel another leg in the same identical place with it;
+that is, carpenter, my old lost leg; the flesh and blood one, I mean.
+Canst thou not drive that old Adam away?
+
+Truly, sir, I begin to understand somewhat now. Yes, I have heard
+something curious on that score, sir; how that a dismasted man never
+entirely loses the feeling of his old spar, but it will be still
+pricking him at times. May I humbly ask if it be really so, sir?
+
+It is, man. Look, put thy live leg here in the place where mine once
+was; so, now, here is only one distinct leg to the eye, yet two to
+the soul. Where thou feelest tingling life; there, exactly there,
+there to a hair, do I. Is't a riddle?
+
+I should humbly call it a poser, sir.
+
+Hist, then. How dost thou know that some entire, living, thinking
+thing may not be invisibly and uninterpenetratingly standing
+precisely where thou now standest; aye, and standing there in thy
+spite? In thy most solitary hours, then, dost thou not fear
+eavesdroppers? Hold, don't speak! And if I still feel the smart of
+my crushed leg, though it be now so long dissolved; then, why mayst
+not thou, carpenter, feel the fiery pains of hell for ever, and
+without a body? Hah!
+
+Good Lord! Truly, sir, if it comes to that, I must calculate over
+again; I think I didn't carry a small figure, sir.
+
+Look ye, pudding-heads should never grant premises.--How long before
+the leg is done?
+
+Perhaps an hour, sir.
+
+Bungle away at it then, and bring it to me (TURNS TO GO). Oh, Life!
+Here I am, proud as Greek god, and yet standing debtor to this
+blockhead for a bone to stand on! Cursed be that mortal
+inter-indebtedness which will not do away with ledgers. I would be
+free as air; and I'm down in the whole world's books. I am so rich,
+I could have given bid for bid with the wealthiest Praetorians at the
+auction of the Roman empire (which was the world's); and yet I owe
+for the flesh in the tongue I brag with. By heavens! I'll get a
+crucible, and into it, and dissolve myself down to one small,
+compendious vertebra. So.
+
+CARPENTER (RESUMING HIS WORK).
+
+
+Well, well, well! Stubb knows him best of all, and Stubb always says
+he's queer; says nothing but that one sufficient little word queer;
+he's queer, says Stubb; he's queer--queer, queer; and keeps dinning
+it into Mr. Starbuck all the time--queer--sir--queer, queer, very
+queer. And here's his leg! Yes, now that I think of it, here's his
+bedfellow! has a stick of whale's jaw-bone for a wife! And this is
+his leg; he'll stand on this. What was that now about one leg
+standing in three places, and all three places standing in one
+hell--how was that? Oh! I don't wonder he looked so scornful at me!
+I'm a sort of strange-thoughted sometimes, they say; but that's only
+haphazard-like. Then, a short, little old body like me, should never
+undertake to wade out into deep waters with tall, heron-built
+captains; the water chucks you under the chin pretty quick, and
+there's a great cry for life-boats. And here's the heron's leg! long
+and slim, sure enough! Now, for most folks one pair of legs lasts a
+lifetime, and that must be because they use them mercifully, as a
+tender-hearted old lady uses her roly-poly old coach-horses. But
+Ahab; oh he's a hard driver. Look, driven one leg to death, and
+spavined the other for life, and now wears out bone legs by the cord.
+Halloa, there, you Smut! bear a hand there with those screws, and
+let's finish it before the resurrection fellow comes a-calling with
+his horn for all legs, true or false, as brewery-men go round
+collecting old beer barrels, to fill 'em up again. What a leg this
+is! It looks like a real live leg, filed down to nothing but the
+core; he'll be standing on this to-morrow; he'll be taking altitudes
+on it. Halloa! I almost forgot the little oval slate, smoothed
+ivory, where he figures up the latitude. So, so; chisel, file, and
+sand-paper, now!
+
+
+
+CHAPTER 109
+
+Ahab and Starbuck in the Cabin.
+
+
+According to usage they were pumping the ship next morning; and lo!
+no inconsiderable oil came up with the water; the casks below must
+have sprung a bad leak. Much concern was shown; and Starbuck went
+down into the cabin to report this unfavourable affair.*
+
+
+*In Sperm-whalemen with any considerable quantity of oil on board, it
+is a regular semiweekly duty to conduct a hose into the hold, and
+drench the casks with sea-water; which afterwards, at varying
+intervals, is removed by the ship's pumps. Hereby the casks are
+sought to be kept damply tight; while by the changed character of the
+withdrawn water, the mariners readily detect any serious leakage in
+the precious cargo.
+
+
+Now, from the South and West the Pequod was drawing nigh to Formosa
+and the Bashee Isles, between which lies one of the tropical outlets
+from the China waters into the Pacific. And so Starbuck found Ahab
+with a general chart of the oriental archipelagoes spread before him;
+and another separate one representing the long eastern coasts of the
+Japanese islands--Niphon, Matsmai, and Sikoke. With his snow-white
+new ivory leg braced against the screwed leg of his table, and with a
+long pruning-hook of a jack-knife in his hand, the wondrous old man,
+with his back to the gangway door, was wrinkling his brow, and
+tracing his old courses again.
+
+"Who's there?" hearing the footstep at the door, but not turning
+round to it. "On deck! Begone!"
+
+"Captain Ahab mistakes; it is I. The oil in the hold is leaking,
+sir. We must up Burtons and break out."
+
+"Up Burtons and break out? Now that we are nearing Japan; heave-to
+here for a week to tinker a parcel of old hoops?"
+
+"Either do that, sir, or waste in one day more oil than we may make
+good in a year. What we come twenty thousand miles to get is worth
+saving, sir."
+
+"So it is, so it is; if we get it."
+
+"I was speaking of the oil in the hold, sir."
+
+"And I was not speaking or thinking of that at all. Begone! Let it
+leak! I'm all aleak myself. Aye! leaks in leaks! not only full of
+leaky casks, but those leaky casks are in a leaky ship; and that's a
+far worse plight than the Pequod's, man. Yet I don't stop to plug my
+leak; for who can find it in the deep-loaded hull; or how hope to
+plug it, even if found, in this life's howling gale? Starbuck!
+I'll not have the Burtons hoisted."
+
+"What will the owners say, sir?"
+
+"Let the owners stand on Nantucket beach and outyell the Typhoons.
+What cares Ahab? Owners, owners? Thou art always prating to me,
+Starbuck, about those miserly owners, as if the owners were my
+conscience. But look ye, the only real owner of anything is its
+commander; and hark ye, my conscience is in this ship's keel.--On
+deck!"
+
+"Captain Ahab," said the reddening mate, moving further into the
+cabin, with a daring so strangely respectful and cautious that it
+almost seemed not only every way seeking to avoid the slightest
+outward manifestation of itself, but within also seemed more than
+half distrustful of itself; "A better man than I might well pass over
+in thee what he would quickly enough resent in a younger man; aye,
+and in a happier, Captain Ahab."
+
+"Devils! Dost thou then so much as dare to critically think of
+me?--On deck!"
+
+"Nay, sir, not yet; I do entreat. And I do dare, sir--to be
+forbearing! Shall we not understand each other better than hitherto,
+Captain Ahab?"
+
+Ahab seized a loaded musket from the rack (forming part of most
+South-Sea-men's cabin furniture), and pointing it towards Starbuck,
+exclaimed: "There is one God that is Lord over the earth, and one
+Captain that is lord over the Pequod.--On deck!"
+
+For an instant in the flashing eyes of the mate, and his fiery
+cheeks, you would have almost thought that he had really received the
+blaze of the levelled tube. But, mastering his emotion, he half
+calmly rose, and as he quitted the cabin, paused for an instant and
+said: "Thou hast outraged, not insulted me, sir; but for that I ask
+thee not to beware of Starbuck; thou wouldst but laugh; but let Ahab
+beware of Ahab; beware of thyself, old man."
+
+"He waxes brave, but nevertheless obeys; most careful bravery that!"
+murmured Ahab, as Starbuck disappeared. "What's that he said--Ahab
+beware of Ahab--there's something there!" Then unconsciously using
+the musket for a staff, with an iron brow he paced to and fro in the
+little cabin; but presently the thick plaits of his forehead relaxed,
+and returning the gun to the rack, he went to the deck.
+
+"Thou art but too good a fellow, Starbuck," he said lowly to the
+mate; then raising his voice to the crew: "Furl the t'gallant-sails,
+and close-reef the top-sails, fore and aft; back the main-yard; up
+Burton, and break out in the main-hold."
+
+It were perhaps vain to surmise exactly why it was, that as
+respecting Starbuck, Ahab thus acted. It may have been a flash of
+honesty in him; or mere prudential policy which, under the
+circumstance, imperiously forbade the slightest symptom of open
+disaffection, however transient, in the important chief officer of
+his ship. However it was, his orders were executed; and the Burtons
+were hoisted.
+
+
+
+CHAPTER 110
+
+Queequeg in His Coffin.
+
+
+Upon searching, it was found that the casks last struck into the hold
+were perfectly sound, and that the leak must be further off. So, it
+being calm weather, they broke out deeper and deeper, disturbing the
+slumbers of the huge ground-tier butts; and from that black midnight
+sending those gigantic moles into the daylight above. So deep did
+they go; and so ancient, and corroded, and weedy the aspect of the
+lowermost puncheons, that you almost looked next for some mouldy
+corner-stone cask containing coins of Captain Noah, with copies of
+the posted placards, vainly warning the infatuated old world from the
+flood. Tierce after tierce, too, of water, and bread, and beef, and
+shooks of staves, and iron bundles of hoops, were hoisted out, till
+at last the piled decks were hard to get about; and the hollow hull
+echoed under foot, as if you were treading over empty catacombs, and
+reeled and rolled in the sea like an air-freighted demijohn.
+Top-heavy was the ship as a dinnerless student with all Aristotle in
+his head. Well was it that the Typhoons did not visit them then.
+
+Now, at this time it was that my poor pagan companion, and fast
+bosom-friend, Queequeg, was seized with a fever, which brought him
+nigh to his endless end.
+
+Be it said, that in this vocation of whaling, sinecures are unknown;
+dignity and danger go hand in hand; till you get to be Captain, the
+higher you rise the harder you toil. So with poor Queequeg, who, as
+harpooneer, must not only face all the rage of the living whale,
+but--as we have elsewhere seen--mount his dead back in a rolling sea;
+and finally descend into the gloom of the hold, and bitterly sweating
+all day in that subterraneous confinement, resolutely manhandle the
+clumsiest casks and see to their stowage. To be short, among
+whalemen, the harpooneers are the holders, so called.
+
+Poor Queequeg! when the ship was about half disembowelled, you should
+have stooped over the hatchway, and peered down upon him there;
+where, stripped to his woollen drawers, the tattooed savage was
+crawling about amid that dampness and slime, like a green spotted
+lizard at the bottom of a well. And a well, or an ice-house, it
+somehow proved to him, poor pagan; where, strange to say, for all the
+heat of his sweatings, he caught a terrible chill which lapsed into a
+fever; and at last, after some days' suffering, laid him in his
+hammock, close to the very sill of the door of death. How he wasted
+and wasted away in those few long-lingering days, till there seemed
+but little left of him but his frame and tattooing. But as all else
+in him thinned, and his cheek-bones grew sharper, his eyes,
+nevertheless, seemed growing fuller and fuller; they became of a
+strange softness of lustre; and mildly but deeply looked out at you
+there from his sickness, a wondrous testimony to that immortal health
+in him which could not die, or be weakened. And like circles on the
+water, which, as they grow fainter, expand; so his eyes seemed
+rounding and rounding, like the rings of Eternity. An awe that
+cannot be named would steal over you as you sat by the side of this
+waning savage, and saw as strange things in his face, as any beheld
+who were bystanders when Zoroaster died. For whatever is truly
+wondrous and fearful in man, never yet was put into words or books.
+And the drawing near of Death, which alike levels all, alike
+impresses all with a last revelation, which only an author from the
+dead could adequately tell. So that--let us say it again--no dying
+Chaldee or Greek had higher and holier thoughts than those, whose
+mysterious shades you saw creeping over the face of poor Queequeg, as
+he quietly lay in his swaying hammock, and the rolling sea seemed
+gently rocking him to his final rest, and the ocean's invisible
+flood-tide lifted him higher and higher towards his destined heaven.
+
+Not a man of the crew but gave him up; and, as for Queequeg himself,
+what he thought of his case was forcibly shown by a curious favour he
+asked. He called one to him in the grey morning watch, when the day
+was just breaking, and taking his hand, said that while in Nantucket
+he had chanced to see certain little canoes of dark wood, like the
+rich war-wood of his native isle; and upon inquiry, he had learned
+that all whalemen who died in Nantucket, were laid in those same dark
+canoes, and that the fancy of being so laid had much pleased him; for
+it was not unlike the custom of his own race, who, after embalming a
+dead warrior, stretched him out in his canoe, and so left him to be
+floated away to the starry archipelagoes; for not only do they
+believe that the stars are isles, but that far beyond all visible
+horizons, their own mild, uncontinented seas, interflow with the blue
+heavens; and so form the white breakers of the milky way. He added,
+that he shuddered at the thought of being buried in his hammock,
+according to the usual sea-custom, tossed like something vile to the
+death-devouring sharks. No: he desired a canoe like those of
+Nantucket, all the more congenial to him, being a whaleman, that like
+a whale-boat these coffin-canoes were without a keel; though that
+involved but uncertain steering, and much lee-way adown the dim ages.
+
+Now, when this strange circumstance was made known aft, the carpenter
+was at once commanded to do Queequeg's bidding, whatever it might
+include. There was some heathenish, coffin-coloured old lumber
+aboard, which, upon a long previous voyage, had been cut from the
+aboriginal groves of the Lackaday islands, and from these dark planks
+the coffin was recommended to be made. No sooner was the carpenter
+apprised of the order, than taking his rule, he forthwith with all
+the indifferent promptitude of his character, proceeded into the
+forecastle and took Queequeg's measure with great accuracy, regularly
+chalking Queequeg's person as he shifted the rule.
+
+"Ah! poor fellow! he'll have to die now," ejaculated the Long Island
+sailor.
+
+Going to his vice-bench, the carpenter for convenience sake and
+general reference, now transferringly measured on it the exact length
+the coffin was to be, and then made the transfer permanent by cutting
+two notches at its extremities. This done, he marshalled the planks
+and his tools, and to work.
+
+When the last nail was driven, and the lid duly planed and fitted, he
+lightly shouldered the coffin and went forward with it, inquiring
+whether they were ready for it yet in that direction.
+
+Overhearing the indignant but half-humorous cries with which the
+people on deck began to drive the coffin away, Queequeg, to every
+one's consternation, commanded that the thing should be instantly
+brought to him, nor was there any denying him; seeing that, of all
+mortals, some dying men are the most tyrannical; and certainly, since
+they will shortly trouble us so little for evermore, the poor fellows
+ought to be indulged.
+
+Leaning over in his hammock, Queequeg long regarded the coffin with
+an attentive eye. He then called for his harpoon, had the wooden
+stock drawn from it, and then had the iron part placed in the coffin
+along with one of the paddles of his boat. All by his own request,
+also, biscuits were then ranged round the sides within: a flask of
+fresh water was placed at the head, and a small bag of woody earth
+scraped up in the hold at the foot; and a piece of sail-cloth being
+rolled up for a pillow, Queequeg now entreated to be lifted into his
+final bed, that he might make trial of its comforts, if any it had.
+He lay without moving a few minutes, then told one to go to his bag
+and bring out his little god, Yojo. Then crossing his arms on his
+breast with Yojo between, he called for the coffin lid (hatch he
+called it) to be placed over him. The head part turned over with a
+leather hinge, and there lay Queequeg in his coffin with little but
+his composed countenance in view. "Rarmai" (it will do; it is easy),
+he murmured at last, and signed to be replaced in his hammock.
+
+But ere this was done, Pip, who had been slily hovering near by all
+this while, drew nigh to him where he lay, and with soft sobbings,
+took him by the hand; in the other, holding his tambourine.
+
+"Poor rover! will ye never have done with all this weary roving?
+where go ye now? But if the currents carry ye to those sweet
+Antilles where the beaches are only beat with water-lilies, will ye
+do one little errand for me? Seek out one Pip, who's now been
+missing long: I think he's in those far Antilles. If ye find him,
+then comfort him; for he must be very sad; for look! he's left his
+tambourine behind;--I found it. Rig-a-dig, dig, dig! Now, Queequeg,
+die; and I'll beat ye your dying march."
+
+"I have heard," murmured Starbuck, gazing down the scuttle, "that in
+violent fevers, men, all ignorance, have talked in ancient tongues;
+and that when the mystery is probed, it turns out always that in
+their wholly forgotten childhood those ancient tongues had been
+really spoken in their hearing by some lofty scholars. So, to my
+fond faith, poor Pip, in this strange sweetness of his lunacy, brings
+heavenly vouchers of all our heavenly homes. Where learned he that,
+but there?--Hark! he speaks again: but more wildly now."
+
+"Form two and two! Let's make a General of him! Ho, where's his
+harpoon? Lay it across here.--Rig-a-dig, dig, dig! huzza! Oh for a
+game cock now to sit upon his head and crow! Queequeg dies
+game!--mind ye that; Queequeg dies game!--take ye good heed of that;
+Queequeg dies game! I say; game, game, game! but base little Pip, he
+died a coward; died all a'shiver;--out upon Pip! Hark ye; if ye find
+Pip, tell all the Antilles he's a runaway; a coward, a coward, a
+coward! Tell them he jumped from a whale-boat! I'd never beat my
+tambourine over base Pip, and hail him General, if he were once more
+dying here. No, no! shame upon all cowards--shame upon them! Let 'em
+go drown like Pip, that jumped from a whale-boat. Shame! shame!"
+
+During all this, Queequeg lay with closed eyes, as if in a dream.
+Pip was led away, and the sick man was replaced in his hammock.
+
+But now that he had apparently made every preparation for death; now
+that his coffin was proved a good fit, Queequeg suddenly rallied;
+soon there seemed no need of the carpenter's box: and thereupon,
+when some expressed their delighted surprise, he, in substance, said,
+that the cause of his sudden convalescence was this;--at a critical
+moment, he had just recalled a little duty ashore, which he was
+leaving undone; and therefore had changed his mind about dying: he
+could not die yet, he averred. They asked him, then, whether to live
+or die was a matter of his own sovereign will and pleasure. He
+answered, certainly. In a word, it was Queequeg's conceit, that if a
+man made up his mind to live, mere sickness could not kill him:
+nothing but a whale, or a gale, or some violent, ungovernable,
+unintelligent destroyer of that sort.
+
+Now, there is this noteworthy difference between savage and
+civilized; that while a sick, civilized man may be six months
+convalescing, generally speaking, a sick savage is almost half-well
+again in a day. So, in good time my Queequeg gained strength; and at
+length after sitting on the windlass for a few indolent days (but
+eating with a vigorous appetite) he suddenly leaped to his feet,
+threw out his arms and legs, gave himself a good stretching, yawned
+a little bit, and then springing into the head of his hoisted boat,
+and poising a harpoon, pronounced himself fit for a fight.
+
+With a wild whimsiness, he now used his coffin for a sea-chest; and
+emptying into it his canvas bag of clothes, set them in order there.
+Many spare hours he spent, in carving the lid with all manner of
+grotesque figures and drawings; and it seemed that hereby he was
+striving, in his rude way, to copy parts of the twisted tattooing on
+his body. And this tattooing had been the work of a departed
+prophet and seer of his island, who, by those hieroglyphic marks, had
+written out on his body a complete theory of the heavens and the
+earth, and a mystical treatise on the art of attaining truth; so that
+Queequeg in his own proper person was a riddle to unfold; a wondrous
+work in one volume; but whose mysteries not even himself could read,
+though his own live heart beat against them; and these mysteries were
+therefore destined in the end to moulder away with the living
+parchment whereon they were inscribed, and so be unsolved to the
+last. And this thought it must have been which suggested to Ahab
+that wild exclamation of his, when one morning turning away from
+surveying poor Queequeg--"Oh, devilish tantalization of the gods!"
+
+
+
+CHAPTER 111
+
+The Pacific.
+
+
+When gliding by the Bashee isles we emerged at last upon the great
+South Sea; were it not for other things, I could have greeted my dear
+Pacific with uncounted thanks, for now the long supplication of my
+youth was answered; that serene ocean rolled eastwards from me a
+thousand leagues of blue.
+
+There is, one knows not what sweet mystery about this sea, whose
+gently awful stirrings seem to speak of some hidden soul beneath;
+like those fabled undulations of the Ephesian sod over the buried
+Evangelist St. John. And meet it is, that over these sea-pastures,
+wide-rolling watery prairies and Potters' Fields of all four
+continents, the waves should rise and fall, and ebb and flow
+unceasingly; for here, millions of mixed shades and shadows, drowned
+dreams, somnambulisms, reveries; all that we call lives and souls,
+lie dreaming, dreaming, still; tossing like slumberers in their beds;
+the ever-rolling waves but made so by their restlessness.
+
+To any meditative Magian rover, this serene Pacific, once beheld,
+must ever after be the sea of his adoption. It rolls the midmost
+waters of the world, the Indian ocean and Atlantic being but its
+arms. The same waves wash the moles of the new-built Californian
+towns, but yesterday planted by the recentest race of men, and lave
+the faded but still gorgeous skirts of Asiatic lands, older than
+Abraham; while all between float milky-ways of coral isles, and
+low-lying, endless, unknown Archipelagoes, and impenetrable Japans.
+Thus this mysterious, divine Pacific zones the world's whole bulk
+about; makes all coasts one bay to it; seems the tide-beating heart
+of earth. Lifted by those eternal swells, you needs must own the
+seductive god, bowing your head to Pan.
+
+But few thoughts of Pan stirred Ahab's brain, as standing like an
+iron statue at his accustomed place beside the mizen rigging, with
+one nostril he unthinkingly snuffed the sugary musk from the Bashee
+isles (in whose sweet woods mild lovers must be walking), and with
+the other consciously inhaled the salt breath of the new found sea;
+that sea in which the hated White Whale must even then be swimming.
+Launched at length upon these almost final waters, and gliding
+towards the Japanese cruising-ground, the old man's purpose
+intensified itself. His firm lips met like the lips of a vice; the
+Delta of his forehead's veins swelled like overladen brooks; in his
+very sleep, his ringing cry ran through the vaulted hull, "Stern all!
+the White Whale spouts thick blood!"
+
+
+
+CHAPTER 112
+
+The Blacksmith.
+
+
+Availing himself of the mild, summer-cool weather that now reigned
+in these latitudes, and in preparation for the peculiarly active
+pursuits shortly to be anticipated, Perth, the begrimed, blistered
+old blacksmith, had not removed his portable forge to the hold again,
+after concluding his contributory work for Ahab's leg, but still
+retained it on deck, fast lashed to ringbolts by the foremast; being
+now almost incessantly invoked by the headsmen, and harpooneers, and
+bowsmen to do some little job for them; altering, or repairing, or
+new shaping their various weapons and boat furniture. Often he would
+be surrounded by an eager circle, all waiting to be served; holding
+boat-spades, pike-heads, harpoons, and lances, and jealously watching
+his every sooty movement, as he toiled. Nevertheless, this old man's
+was a patient hammer wielded by a patient arm. No murmur, no
+impatience, no petulance did come from him. Silent, slow, and
+solemn; bowing over still further his chronically broken back, he
+toiled away, as if toil were life itself, and the heavy beating of
+his hammer the heavy beating of his heart. And so it was.--Most
+miserable!
+
+A peculiar walk in this old man, a certain slight but painful
+appearing yawing in his gait, had at an early period of the voyage
+excited the curiosity of the mariners. And to the importunity of
+their persisted questionings he had finally given in; and so it came
+to pass that every one now knew the shameful story of his wretched
+fate.
+
+Belated, and not innocently, one bitter winter's midnight, on the
+road running between two country towns, the blacksmith half-stupidly
+felt the deadly numbness stealing over him, and sought refuge in a
+leaning, dilapidated barn. The issue was, the loss of the
+extremities of both feet. Out of this revelation, part by part, at
+last came out the four acts of the gladness, and the one long, and as
+yet uncatastrophied fifth act of the grief of his life's drama.
+
+He was an old man, who, at the age of nearly sixty, had postponedly
+encountered that thing in sorrow's technicals called ruin. He had
+been an artisan of famed excellence, and with plenty to do; owned a
+house and garden; embraced a youthful, daughter-like, loving wife,
+and three blithe, ruddy children; every Sunday went to a
+cheerful-looking church, planted in a grove. But one night, under
+cover of darkness, and further concealed in a most cunning
+disguisement, a desperate burglar slid into his happy home, and
+robbed them all of everything. And darker yet to tell, the
+blacksmith himself did ignorantly conduct this burglar into his
+family's heart. It was the Bottle Conjuror! Upon the opening of
+that fatal cork, forth flew the fiend, and shrivelled up his home.
+Now, for prudent, most wise, and economic reasons, the blacksmith's
+shop was in the basement of his dwelling, but with a separate
+entrance to it; so that always had the young and loving healthy wife
+listened with no unhappy nervousness, but with vigorous pleasure, to
+the stout ringing of her young-armed old husband's hammer; whose
+reverberations, muffled by passing through the floors and walls, came
+up to her, not unsweetly, in her nursery; and so, to stout Labor's
+iron lullaby, the blacksmith's infants were rocked to slumber.
+
+Oh, woe on woe! Oh, Death, why canst thou not sometimes be timely?
+Hadst thou taken this old blacksmith to thyself ere his full ruin
+came upon him, then had the young widow had a delicious grief, and
+her orphans a truly venerable, legendary sire to dream of in their
+after years; and all of them a care-killing competency. But Death
+plucked down some virtuous elder brother, on whose whistling daily
+toil solely hung the responsibilities of some other family, and left
+the worse than useless old man standing, till the hideous rot of life
+should make him easier to harvest.
+
+Why tell the whole? The blows of the basement hammer every day grew
+more and more between; and each blow every day grew fainter than the
+last; the wife sat frozen at the window, with tearless eyes,
+glitteringly gazing into the weeping faces of her children; the
+bellows fell; the forge choked up with cinders; the house was sold;
+the mother dived down into the long church-yard grass; her children
+twice followed her thither; and the houseless, familyless old man
+staggered off a vagabond in crape; his every woe unreverenced; his
+grey head a scorn to flaxen curls!
+
+Death seems the only desirable sequel for a career like this; but
+Death is only a launching into the region of the strange Untried; it
+is but the first salutation to the possibilities of the immense
+Remote, the Wild, the Watery, the Unshored; therefore, to the
+death-longing eyes of such men, who still have left in them some
+interior compunctions against suicide, does the all-contributed and
+all-receptive ocean alluringly spread forth his whole plain of
+unimaginable, taking terrors, and wonderful, new-life adventures; and
+from the hearts of infinite Pacifics, the thousand mermaids sing to
+them--"Come hither, broken-hearted; here is another life without the
+guilt of intermediate death; here are wonders supernatural, without
+dying for them. Come hither! bury thyself in a life which, to your
+now equally abhorred and abhorring, landed world, is more oblivious
+than death. Come hither! put up THY gravestone, too, within the
+churchyard, and come hither, till we marry thee!"
+
+Hearkening to these voices, East and West, by early sunrise, and by
+fall of eve, the blacksmith's soul responded, Aye, I come! And so
+Perth went a-whaling.
+
+
+
+CHAPTER 113
+
+The Forge.
+
+
+With matted beard, and swathed in a bristling shark-skin apron, about
+mid-day, Perth was standing between his forge and anvil, the latter
+placed upon an iron-wood log, with one hand holding a pike-head in
+the coals, and with the other at his forge's lungs, when Captain Ahab
+came along, carrying in his hand a small rusty-looking leathern bag.
+While yet a little distance from the forge, moody Ahab paused; till
+at last, Perth, withdrawing his iron from the fire, began hammering
+it upon the anvil--the red mass sending off the sparks in thick
+hovering flights, some of which flew close to Ahab.
+
+"Are these thy Mother Carey's chickens, Perth? they are always flying
+in thy wake; birds of good omen, too, but not to all;--look here,
+they burn; but thou--thou liv'st among them without a scorch."
+
+"Because I am scorched all over, Captain Ahab," answered Perth,
+resting for a moment on his hammer; "I am past scorching; not easily
+can'st thou scorch a scar."
+
+"Well, well; no more. Thy shrunk voice sounds too calmly, sanely
+woeful to me. In no Paradise myself, I am impatient of all misery in
+others that is not mad. Thou should'st go mad, blacksmith; say, why
+dost thou not go mad? How can'st thou endure without being mad? Do
+the heavens yet hate thee, that thou can'st not go mad?--What wert
+thou making there?"
+
+"Welding an old pike-head, sir; there were seams and dents in it."
+
+"And can'st thou make it all smooth again, blacksmith, after such
+hard usage as it had?"
+
+"I think so, sir."
+
+"And I suppose thou can'st smoothe almost any seams and dents; never
+mind how hard the metal, blacksmith?"
+
+"Aye, sir, I think I can; all seams and dents but one."
+
+"Look ye here, then," cried Ahab, passionately advancing, and leaning
+with both hands on Perth's shoulders; "look ye here--HERE--can ye
+smoothe out a seam like this, blacksmith," sweeping one hand across
+his ribbed brow; "if thou could'st, blacksmith, glad enough would I
+lay my head upon thy anvil, and feel thy heaviest hammer between my
+eyes. Answer! Can'st thou smoothe this seam?"
+
+"Oh! that is the one, sir! Said I not all seams and dents but one?"
+
+"Aye, blacksmith, it is the one; aye, man, it is unsmoothable; for
+though thou only see'st it here in my flesh, it has worked down into
+the bone of my skull--THAT is all wrinkles! But, away with child's
+play; no more gaffs and pikes to-day. Look ye here!" jingling the
+leathern bag, as if it were full of gold coins. "I, too, want a
+harpoon made; one that a thousand yoke of fiends could not part,
+Perth; something that will stick in a whale like his own fin-bone.
+There's the stuff," flinging the pouch upon the anvil. "Look ye,
+blacksmith, these are the gathered nail-stubbs of the steel shoes of
+racing horses."
+
+"Horse-shoe stubbs, sir? Why, Captain Ahab, thou hast here, then,
+the best and stubbornest stuff we blacksmiths ever work."
+
+"I know it, old man; these stubbs will weld together like glue from
+the melted bones of murderers. Quick! forge me the harpoon. And
+forge me first, twelve rods for its shank; then wind, and twist, and
+hammer these twelve together like the yarns and strands of a
+tow-line. Quick! I'll blow the fire."
+
+When at last the twelve rods were made, Ahab tried them, one by one,
+by spiralling them, with his own hand, round a long, heavy iron bolt.
+"A flaw!" rejecting the last one. "Work that over again, Perth."
+
+This done, Perth was about to begin welding the twelve into one, when
+Ahab stayed his hand, and said he would weld his own iron. As, then,
+with regular, gasping hems, he hammered on the anvil, Perth passing
+to him the glowing rods, one after the other, and the hard pressed
+forge shooting up its intense straight flame, the Parsee passed
+silently, and bowing over his head towards the fire, seemed invoking
+some curse or some blessing on the toil. But, as Ahab looked up, he
+slid aside.
+
+"What's that bunch of lucifers dodging about there for?" muttered
+Stubb, looking on from the forecastle. "That Parsee smells fire like
+a fusee; and smells of it himself, like a hot musket's powder-pan."
+
+At last the shank, in one complete rod, received its final heat; and
+as Perth, to temper it, plunged it all hissing into the cask of water
+near by, the scalding steam shot up into Ahab's bent face.
+
+"Would'st thou brand me, Perth?" wincing for a moment with the pain;
+"have I been but forging my own branding-iron, then?"
+
+"Pray God, not that; yet I fear something, Captain Ahab. Is not this
+harpoon for the White Whale?"
+
+"For the white fiend! But now for the barbs; thou must make them
+thyself, man. Here are my razors--the best of steel; here, and make
+the barbs sharp as the needle-sleet of the Icy Sea."
+
+For a moment, the old blacksmith eyed the razors as though he would
+fain not use them.
+
+"Take them, man, I have no need for them; for I now neither shave,
+sup, nor pray till--but here--to work!"
+
+Fashioned at last into an arrowy shape, and welded by Perth to the
+shank, the steel soon pointed the end of the iron; and as the
+blacksmith was about giving the barbs their final heat, prior to
+tempering them, he cried to Ahab to place the water-cask near.
+
+"No, no--no water for that; I want it of the true death-temper.
+Ahoy, there! Tashtego, Queequeg, Daggoo! What say ye, pagans! Will
+ye give me as much blood as will cover this barb?" holding it high
+up. A cluster of dark nods replied, Yes. Three punctures were made
+in the heathen flesh, and the White Whale's barbs were then tempered.
+
+"Ego non baptizo te in nomine patris, sed in nomine diaboli!"
+deliriously howled Ahab, as the malignant iron scorchingly devoured
+the baptismal blood.
+
+Now, mustering the spare poles from below, and selecting one of
+hickory, with the bark still investing it, Ahab fitted the end to the
+socket of the iron. A coil of new tow-line was then unwound, and
+some fathoms of it taken to the windlass, and stretched to a great
+tension. Pressing his foot upon it, till the rope hummed like a
+harp-string, then eagerly bending over it, and seeing no strandings,
+Ahab exclaimed, "Good! and now for the seizings."
+
+At one extremity the rope was unstranded, and the separate spread
+yarns were all braided and woven round the socket of the harpoon; the
+pole was then driven hard up into the socket; from the lower end the
+rope was traced half-way along the pole's length, and firmly secured
+so, with intertwistings of twine. This done, pole, iron, and
+rope--like the Three Fates--remained inseparable, and Ahab moodily
+stalked away with the weapon; the sound of his ivory leg, and the
+sound of the hickory pole, both hollowly ringing along every plank.
+But ere he entered his cabin, light, unnatural, half-bantering, yet
+most piteous sound was heard. Oh, Pip! thy wretched laugh, thy
+idle but unresting eye; all thy strange mummeries not unmeaningly
+blended with the black tragedy of the melancholy ship, and mocked it!
+
+
+
+CHAPTER 114
+
+The Gilder.
+
+
+Penetrating further and further into the heart of the Japanese
+cruising ground, the Pequod was soon all astir in the fishery.
+Often, in mild, pleasant weather, for twelve, fifteen, eighteen, and
+twenty hours on the stretch, they were engaged in the boats, steadily
+pulling, or sailing, or paddling after the whales, or for an
+interlude of sixty or seventy minutes calmly awaiting their uprising;
+though with but small success for their pains.
+
+At such times, under an abated sun; afloat all day upon smooth, slow
+heaving swells; seated in his boat, light as a birch canoe; and so
+sociably mixing with the soft waves themselves, that like
+hearth-stone cats they purr against the gunwale; these are the times
+of dreamy quietude, when beholding the tranquil beauty and brilliancy
+of the ocean's skin, one forgets the tiger heart that pants beneath
+it; and would not willingly remember, that this velvet paw but
+conceals a remorseless fang.
+
+These are the times, when in his whale-boat the rover softly feels a
+certain filial, confident, land-like feeling towards the sea; that he
+regards it as so much flowery earth; and the distant ship revealing
+only the tops of her masts, seems struggling forward, not through
+high rolling waves, but through the tall grass of a rolling prairie:
+as when the western emigrants' horses only show their erected ears,
+while their hidden bodies widely wade through the amazing verdure.
+
+The long-drawn virgin vales; the mild blue hill-sides; as over these
+there steals the hush, the hum; you almost swear that play-wearied
+children lie sleeping in these solitudes, in some glad May-time, when
+the flowers of the woods are plucked. And all this mixes with your
+most mystic mood; so that fact and fancy, half-way meeting,
+interpenetrate, and form one seamless whole.
+
+Nor did such soothing scenes, however temporary, fail of at least as
+temporary an effect on Ahab. But if these secret golden keys did
+seem to open in him his own secret golden treasuries, yet did his
+breath upon them prove but tarnishing.
+
+Oh, grassy glades! oh, ever vernal endless landscapes in the soul; in
+ye,--though long parched by the dead drought of the earthy
+life,--in ye, men yet may roll, like young horses in new morning
+clover; and for some few fleeting moments, feel the cool dew of the
+life immortal on them. Would to God these blessed calms would last.
+But the mingled, mingling threads of life are woven by warp and woof:
+calms crossed by storms, a storm for every calm. There is no steady
+unretracing progress in this life; we do not advance through fixed
+gradations, and at the last one pause:--through infancy's unconscious
+spell, boyhood's thoughtless faith, adolescence' doubt (the common
+doom), then scepticism, then disbelief, resting at last in manhood's
+pondering repose of If. But once gone through, we trace the round
+again; and are infants, boys, and men, and Ifs eternally. Where lies
+the final harbor, whence we unmoor no more? In what rapt ether sails
+the world, of which the weariest will never weary? Where is the
+foundling's father hidden? Our souls are like those orphans whose
+unwedded mothers die in bearing them: the secret of our paternity
+lies in their grave, and we must there to learn it.
+
+And that same day, too, gazing far down from his boat's side into
+that same golden sea, Starbuck lowly murmured:--
+
+"Loveliness unfathomable, as ever lover saw in his young bride's
+eye!--Tell me not of thy teeth-tiered sharks, and thy kidnapping
+cannibal ways. Let faith oust fact; let fancy oust memory; I look
+deep down and do believe."
+
+And Stubb, fish-like, with sparkling scales, leaped up in that same
+golden light:--
+
+"I am Stubb, and Stubb has his history; but here Stubb takes oaths
+that he has always been jolly!"
+
+
+
+CHAPTER 115
+
+The Pequod Meets The Bachelor.
+
+
+And jolly enough were the sights and the sounds that came bearing
+down before the wind, some few weeks after Ahab's harpoon had been
+welded.
+
+It was a Nantucket ship, the Bachelor, which had just wedged in her
+last cask of oil, and bolted down her bursting hatches; and now, in
+glad holiday apparel, was joyously, though somewhat vain-gloriously,
+sailing round among the widely-separated ships on the ground,
+previous to pointing her prow for home.
+
+The three men at her mast-head wore long streamers of narrow red
+bunting at their hats; from the stern, a whale-boat was suspended,
+bottom down; and hanging captive from the bowsprit was seen the long
+lower jaw of the last whale they had slain. Signals, ensigns, and
+jacks of all colours were flying from her rigging, on every side.
+Sideways lashed in each of her three basketed tops were two barrels
+of sperm; above which, in her top-mast cross-trees, you saw slender
+breakers of the same precious fluid; and nailed to her main truck was
+a brazen lamp.
+
+As was afterwards learned, the Bachelor had met with the most
+surprising success; all the more wonderful, for that while cruising
+in the same seas numerous other vessels had gone entire months
+without securing a single fish. Not only had barrels of beef and
+bread been given away to make room for the far more valuable sperm,
+but additional supplemental casks had been bartered for, from the
+ships she had met; and these were stowed along the deck, and in the
+captain's and officers' state-rooms. Even the cabin table itself
+had been knocked into kindling-wood; and the cabin mess dined off the
+broad head of an oil-butt, lashed down to the floor for a
+centrepiece. In the forecastle, the sailors had actually caulked
+and pitched their chests, and filled them; it was humorously added,
+that the cook had clapped a head on his largest boiler, and filled
+it; that the steward had plugged his spare coffee-pot and filled it;
+that the harpooneers had headed the sockets of their irons and filled
+them; that indeed everything was filled with sperm, except the
+captain's pantaloons pockets, and those he reserved to thrust his
+hands into, in self-complacent testimony of his entire satisfaction.
+
+As this glad ship of good luck bore down upon the moody Pequod, the
+barbarian sound of enormous drums came from her forecastle; and
+drawing still nearer, a crowd of her men were seen standing round her
+huge try-pots, which, covered with the parchment-like POKE or stomach
+skin of the black fish, gave forth a loud roar to every stroke of the
+clenched hands of the crew. On the quarter-deck, the mates and
+harpooneers were dancing with the olive-hued girls who had eloped
+with them from the Polynesian Isles; while suspended in an
+ornamented boat, firmly secured aloft between the foremast and
+mainmast, three Long Island negroes, with glittering fiddle-bows of
+whale ivory, were presiding over the hilarious jig. Meanwhile,
+others of the ship's company were tumultuously busy at the masonry of
+the try-works, from which the huge pots had been removed. You would
+have almost thought they were pulling down the cursed Bastille, such
+wild cries they raised, as the now useless brick and mortar were
+being hurled into the sea.
+
+Lord and master over all this scene, the captain stood erect on the
+ship's elevated quarter-deck, so that the whole rejoicing drama was
+full before him, and seemed merely contrived for his own individual
+diversion.
+
+And Ahab, he too was standing on his quarter-deck, shaggy and black,
+with a stubborn gloom; and as the two ships crossed each other's
+wakes--one all jubilations for things passed, the other all
+forebodings as to things to come--their two captains in themselves
+impersonated the whole striking contrast of the scene.
+
+"Come aboard, come aboard!" cried the gay Bachelor's commander,
+lifting a glass and a bottle in the air.
+
+"Hast seen the White Whale?" gritted Ahab in reply.
+
+"No; only heard of him; but don't believe in him at all," said the
+other good-humoredly. "Come aboard!"
+
+"Thou art too damned jolly. Sail on. Hast lost any men?"
+
+"Not enough to speak of--two islanders, that's all;--but come aboard,
+old hearty, come along. I'll soon take that black from your brow.
+Come along, will ye (merry's the play); a full ship and
+homeward-bound."
+
+"How wondrous familiar is a fool!" muttered Ahab; then aloud, "Thou
+art a full ship and homeward bound, thou sayst; well, then, call me
+an empty ship, and outward-bound. So go thy ways, and I will mine.
+Forward there! Set all sail, and keep her to the wind!"
+
+And thus, while the one ship went cheerily before the breeze, the
+other stubbornly fought against it; and so the two vessels parted;
+the crew of the Pequod looking with grave, lingering glances towards
+the receding Bachelor; but the Bachelor's men never heeding their
+gaze for the lively revelry they were in. And as Ahab, leaning over
+the taffrail, eyed the homewardbound craft, he took from his pocket a
+small vial of sand, and then looking from the ship to the vial,
+seemed thereby bringing two remote associations together, for that
+vial was filled with Nantucket soundings.
+
+
+
+CHAPTER 116
+
+The Dying Whale.
+
+
+Not seldom in this life, when, on the right side, fortune's favourites
+sail close by us, we, though all adroop before, catch somewhat of the
+rushing breeze, and joyfully feel our bagging sails fill out. So
+seemed it with the Pequod. For next day after encountering the gay
+Bachelor, whales were seen and four were slain; and one of them by
+Ahab.
+
+It was far down the afternoon; and when all the spearings of the
+crimson fight were done: and floating in the lovely sunset sea and
+sky, sun and whale both stilly died together; then, such a sweetness
+and such plaintiveness, such inwreathing orisons curled up in that
+rosy air, that it almost seemed as if far over from the deep green
+convent valleys of the Manilla isles, the Spanish land-breeze,
+wantonly turned sailor, had gone to sea, freighted with these vesper
+hymns.
+
+Soothed again, but only soothed to deeper gloom, Ahab, who had
+sterned off from the whale, sat intently watching his final wanings
+from the now tranquil boat. For that strange spectacle observable in
+all sperm whales dying--the turning sunwards of the head, and so
+expiring--that strange spectacle, beheld of such a placid evening,
+somehow to Ahab conveyed a wondrousness unknown before.
+
+"He turns and turns him to it,--how slowly, but how steadfastly, his
+homage-rendering and invoking brow, with his last dying motions. He
+too worships fire; most faithful, broad, baronial vassal of the
+sun!--Oh that these too-favouring eyes should see these too-favouring
+sights. Look! here, far water-locked; beyond all hum of human weal
+or woe; in these most candid and impartial seas; where to traditions
+no rocks furnish tablets; where for long Chinese ages, the billows
+have still rolled on speechless and unspoken to, as stars that shine
+upon the Niger's unknown source; here, too, life dies sunwards full
+of faith; but see! no sooner dead, than death whirls round the
+corpse, and it heads some other way.
+
+"Oh, thou dark Hindoo half of nature, who of drowned bones hast
+builded thy separate throne somewhere in the heart of these
+unverdured seas; thou art an infidel, thou queen, and too truly
+speakest to me in the wide-slaughtering Typhoon, and the hushed
+burial of its after calm. Nor has this thy whale sunwards turned his
+dying head, and then gone round again, without a lesson to me.
+
+"Oh, trebly hooped and welded hip of power! Oh, high aspiring,
+rainbowed jet!--that one strivest, this one jettest all in vain! In
+vain, oh whale, dost thou seek intercedings with yon all-quickening
+sun, that only calls forth life, but gives it not again. Yet dost
+thou, darker half, rock me with a prouder, if a darker faith. All
+thy unnamable imminglings float beneath me here; I am buoyed by
+breaths of once living things, exhaled as air, but water now.
+
+"Then hail, for ever hail, O sea, in whose eternal tossings the wild
+fowl finds his only rest. Born of earth, yet suckled by the sea;
+though hill and valley mothered me, ye billows are my
+foster-brothers!"
+
+
+
+CHAPTER 117
+
+The Whale Watch.
+
+
+The four whales slain that evening had died wide apart; one, far to
+windward; one, less distant, to leeward; one ahead; one astern.
+These last three were brought alongside ere nightfall; but the
+windward one could not be reached till morning; and the boat that had
+killed it lay by its side all night; and that boat was Ahab's.
+
+The waif-pole was thrust upright into the dead whale's spout-hole;
+and the lantern hanging from its top, cast a troubled flickering
+glare upon the black, glossy back, and far out upon the midnight
+waves, which gently chafed the whale's broad flank, like soft surf
+upon a beach.
+
+Ahab and all his boat's crew seemed asleep but the Parsee; who
+crouching in the bow, sat watching the sharks, that spectrally played
+round the whale, and tapped the light cedar planks with their tails.
+A sound like the moaning in squadrons over Asphaltites of unforgiven
+ghosts of Gomorrah, ran shuddering through the air.
+
+Started from his slumbers, Ahab, face to face, saw the Parsee; and
+hooped round by the gloom of the night they seemed the last men in a
+flooded world. "I have dreamed it again," said he.
+
+"Of the hearses? Have I not said, old man, that neither hearse nor
+coffin can be thine?"
+
+"And who are hearsed that die on the sea?"
+
+"But I said, old man, that ere thou couldst die on this voyage, two
+hearses must verily be seen by thee on the sea; the first not made by
+mortal hands; and the visible wood of the last one must be grown in
+America."
+
+"Aye, aye! a strange sight that, Parsee:--a hearse and its plumes
+floating over the ocean with the waves for the pall-bearers. Ha!
+Such a sight we shall not soon see."
+
+"Believe it or not, thou canst not die till it be seen, old man."
+
+"And what was that saying about thyself?"
+
+"Though it come to the last, I shall still go before thee thy pilot."
+
+"And when thou art so gone before--if that ever befall--then ere I
+can follow, thou must still appear to me, to pilot me still?--Was it
+not so? Well, then, did I believe all ye say, oh my pilot! I have
+here two pledges that I shall yet slay Moby Dick and survive it."
+
+"Take another pledge, old man," said the Parsee, as his eyes lighted
+up like fire-flies in the gloom--"Hemp only can kill thee."
+
+"The gallows, ye mean.--I am immortal then, on land and on sea,"
+cried Ahab, with a laugh of derision;--"Immortal on land and on sea!"
+
+Both were silent again, as one man. The grey dawn came on, and the
+slumbering crew arose from the boat's bottom, and ere noon the dead
+whale was brought to the ship.
+
+
+
+CHAPTER 118
+
+The Quadrant.
+
+
+The season for the Line at length drew near; and every day when Ahab,
+coming from his cabin, cast his eyes aloft, the vigilant helmsman
+would ostentatiously handle his spokes, and the eager mariners
+quickly run to the braces, and would stand there with all their eyes
+centrally fixed on the nailed doubloon; impatient for the order to
+point the ship's prow for the equator. In good time the order came.
+It was hard upon high noon; and Ahab, seated in the bows of his
+high-hoisted boat, was about taking his wonted daily observation of
+the sun to determine his latitude.
+
+Now, in that Japanese sea, the days in summer are as freshets of
+effulgences. That unblinkingly vivid Japanese sun seems the blazing
+focus of the glassy ocean's immeasurable burning-glass. The sky
+looks lacquered; clouds there are none; the horizon floats; and this
+nakedness of unrelieved radiance is as the insufferable splendors of
+God's throne. Well that Ahab's quadrant was furnished with coloured
+glasses, through which to take sight of that solar fire. So,
+swinging his seated form to the roll of the ship, and with his
+astrological-looking instrument placed to his eye, he remained in
+that posture for some moments to catch the precise instant when the
+sun should gain its precise meridian. Meantime while his whole
+attention was absorbed, the Parsee was kneeling beneath him on the
+ship's deck, and with face thrown up like Ahab's, was eyeing the same
+sun with him; only the lids of his eyes half hooded their orbs, and
+his wild face was subdued to an earthly passionlessness. At length
+the desired observation was taken; and with his pencil upon his ivory
+leg, Ahab soon calculated what his latitude must be at that precise
+instant. Then falling into a moment's revery, he again looked up
+towards the sun and murmured to himself: "Thou sea-mark! thou high
+and mighty Pilot! thou tellest me truly where I AM--but canst thou
+cast the least hint where I SHALL be? Or canst thou tell where some
+other thing besides me is this moment living? Where is Moby Dick?
+This instant thou must be eyeing him. These eyes of mine look into
+the very eye that is even now beholding him; aye, and into the eye
+that is even now equally beholding the objects on the unknown,
+thither side of thee, thou sun!"
+
+Then gazing at his quadrant, and handling, one after the other, its
+numerous cabalistical contrivances, he pondered again, and muttered:
+"Foolish toy! babies' plaything of haughty Admirals, and Commodores,
+and Captains; the world brags of thee, of thy cunning and might; but
+what after all canst thou do, but tell the poor, pitiful point, where
+thou thyself happenest to be on this wide planet, and the hand that
+holds thee: no! not one jot more! Thou canst not tell where one drop
+of water or one grain of sand will be to-morrow noon; and yet with
+thy impotence thou insultest the sun! Science! Curse thee, thou
+vain toy; and cursed be all the things that cast man's eyes aloft to
+that heaven, whose live vividness but scorches him, as these old eyes
+are even now scorched with thy light, O sun! Level by nature to this
+earth's horizon are the glances of man's eyes; not shot from the
+crown of his head, as if God had meant him to gaze on his firmament.
+Curse thee, thou quadrant!" dashing it to the deck, "no longer will I
+guide my earthly way by thee; the level ship's compass, and the level
+deadreckoning, by log and by line; THESE shall conduct me, and show
+me my place on the sea. Aye," lighting from the boat to the deck,
+"thus I trample on thee, thou paltry thing that feebly pointest on
+high; thus I split and destroy thee!"
+
+As the frantic old man thus spoke and thus trampled with his live and
+dead feet, a sneering triumph that seemed meant for Ahab, and a
+fatalistic despair that seemed meant for himself--these passed over
+the mute, motionless Parsee's face. Unobserved he rose and glided
+away; while, awestruck by the aspect of their commander, the seamen
+clustered together on the forecastle, till Ahab, troubledly pacing
+the deck, shouted out--"To the braces! Up helm!--square in!"
+
+In an instant the yards swung round; and as the ship half-wheeled
+upon her heel, her three firm-seated graceful masts erectly poised
+upon her long, ribbed hull, seemed as the three Horatii pirouetting
+on one sufficient steed.
+
+Standing between the knight-heads, Starbuck watched the Pequod's
+tumultuous way, and Ahab's also, as he went lurching along the deck.
+
+"I have sat before the dense coal fire and watched it all aglow, full
+of its tormented flaming life; and I have seen it wane at last, down,
+down, to dumbest dust. Old man of oceans! of all this fiery life of
+thine, what will at length remain but one little heap of ashes!"
+
+"Aye," cried Stubb, "but sea-coal ashes--mind ye that, Mr.
+Starbuck--sea-coal, not your common charcoal. Well, well; I heard
+Ahab mutter, 'Here some one thrusts these cards into these old hands
+of mine; swears that I must play them, and no others.' And damn me,
+Ahab, but thou actest right; live in the game, and die in it!"
+
+
+
+CHAPTER 119
+
+The Candles.
+
+
+Warmest climes but nurse the cruellest fangs: the tiger of Bengal
+crouches in spiced groves of ceaseless verdure. Skies the most
+effulgent but basket the deadliest thunders: gorgeous Cuba knows
+tornadoes that never swept tame northern lands. So, too, it is, that
+in these resplendent Japanese seas the mariner encounters the direst
+of all storms, the Typhoon. It will sometimes burst from out that
+cloudless sky, like an exploding bomb upon a dazed and sleepy town.
+
+Towards evening of that day, the Pequod was torn of her canvas, and
+bare-poled was left to fight a Typhoon which had struck her directly
+ahead. When darkness came on, sky and sea roared and split with the
+thunder, and blazed with the lightning, that showed the disabled
+masts fluttering here and there with the rags which the first fury of
+the tempest had left for its after sport.
+
+Holding by a shroud, Starbuck was standing on the quarter-deck; at
+every flash of the lightning glancing aloft, to see what additional
+disaster might have befallen the intricate hamper there; while Stubb
+and Flask were directing the men in the higher hoisting and firmer
+lashing of the boats. But all their pains seemed naught. Though
+lifted to the very top of the cranes, the windward quarter boat
+(Ahab's) did not escape. A great rolling sea, dashing high up
+against the reeling ship's high teetering side, stove in the boat's
+bottom at the stern, and left it again, all dripping through like a
+sieve.
+
+"Bad work, bad work! Mr. Starbuck," said Stubb, regarding the wreck,
+"but the sea will have its way. Stubb, for one, can't fight it. You
+see, Mr. Starbuck, a wave has such a great long start before it
+leaps, all round the world it runs, and then comes the spring! But
+as for me, all the start I have to meet it, is just across the deck
+here. But never mind; it's all in fun: so the old song
+says;"--(SINGS.)
+
+Oh! jolly is the gale,
+And a joker is the whale,
+A' flourishin' his tail,--
+Such a funny, sporty, gamy, jesty, joky, hoky-poky lad, is the Ocean, oh!
+
+The scud all a flyin',
+That's his flip only foamin';
+When he stirs in the spicin',--
+Such a funny, sporty, gamy, jesty, joky, hoky-poky lad, is the Ocean, oh!
+
+Thunder splits the ships,
+But he only smacks his lips,
+A tastin' of this flip,--
+Such a funny, sporty, gamy, jesty, joky, hoky-poky lad, is the Ocean, oh!
+
+
+"Avast Stubb," cried Starbuck, "let the Typhoon sing, and strike his
+harp here in our rigging; but if thou art a brave man thou wilt hold
+thy peace."
+
+"But I am not a brave man; never said I was a brave man; I am a
+coward; and I sing to keep up my spirits. And I tell you what it is,
+Mr. Starbuck, there's no way to stop my singing in this world but to
+cut my throat. And when that's done, ten to one I sing ye the
+doxology for a wind-up."
+
+"Madman! look through my eyes if thou hast none of thine own."
+
+"What! how can you see better of a dark night than anybody else,
+never mind how foolish?"
+
+"Here!" cried Starbuck, seizing Stubb by the shoulder, and pointing
+his hand towards the weather bow, "markest thou not that the gale
+comes from the eastward, the very course Ahab is to run for Moby
+Dick? the very course he swung to this day noon? now mark his boat
+there; where is that stove? In the stern-sheets, man; where he is
+wont to stand--his stand-point is stove, man! Now jump overboard,
+and sing away, if thou must!
+
+"I don't half understand ye: what's in the wind?"
+
+"Yes, yes, round the Cape of Good Hope is the shortest way to
+Nantucket," soliloquized Starbuck suddenly, heedless of Stubb's
+question. "The gale that now hammers at us to stave us, we can turn
+it into a fair wind that will drive us towards home. Yonder, to
+windward, all is blackness of doom; but to leeward, homeward--I see
+it lightens up there; but not with the lightning."
+
+At that moment in one of the intervals of profound darkness,
+following the flashes, a voice was heard at his side; and almost at
+the same instant a volley of thunder peals rolled overhead.
+
+"Who's there?"
+
+"Old Thunder!" said Ahab, groping his way along the bulwarks to his
+pivot-hole; but suddenly finding his path made plain to him by
+elbowed lances of fire.
+
+Now, as the lightning rod to a spire on shore is intended to carry
+off the perilous fluid into the soil; so the kindred rod which at sea
+some ships carry to each mast, is intended to conduct it into the
+water. But as this conductor must descend to considerable depth,
+that its end may avoid all contact with the hull; and as moreover, if
+kept constantly towing there, it would be liable to many mishaps,
+besides interfering not a little with some of the rigging, and more
+or less impeding the vessel's way in the water; because of all this,
+the lower parts of a ship's lightning-rods are not always overboard;
+but are generally made in long slender links, so as to be the more
+readily hauled up into the chains outside, or thrown down into the
+sea, as occasion may require.
+
+"The rods! the rods!" cried Starbuck to the crew, suddenly admonished
+to vigilance by the vivid lightning that had just been darting
+flambeaux, to light Ahab to his post. "Are they overboard? drop them
+over, fore and aft. Quick!"
+
+"Avast!" cried Ahab; "let's have fair play here, though we be the
+weaker side. Yet I'll contribute to raise rods on the Himmalehs and
+Andes, that all the world may be secured; but out on privileges! Let
+them be, sir."
+
+"Look aloft!" cried Starbuck. "The corpusants! the corpusants!
+
+All the yard-arms were tipped with a pallid fire; and touched at each
+tri-pointed lightning-rod-end with three tapering white flames, each
+of the three tall masts was silently burning in that sulphurous air,
+like three gigantic wax tapers before an altar.
+
+"Blast the boat! let it go!" cried Stubb at this instant, as a
+swashing sea heaved up under his own little craft, so that its
+gunwale violently jammed his hand, as he was passing a lashing.
+"Blast it!"--but slipping backward on the deck, his uplifted eyes
+caught the flames; and immediately shifting his tone he cried--"The
+corpusants have mercy on us all!"
+
+To sailors, oaths are household words; they will swear in the trance
+of the calm, and in the teeth of the tempest; they will imprecate
+curses from the topsail-yard-arms, when most they teeter over to a
+seething sea; but in all my voyagings, seldom have I heard a common
+oath when God's burning finger has been laid on the ship; when His
+"Mene, Mene, Tekel Upharsin" has been woven into the shrouds and the
+cordage.
+
+While this pallidness was burning aloft, few words were heard from
+the enchanted crew; who in one thick cluster stood on the forecastle,
+all their eyes gleaming in that pale phosphorescence, like a far away
+constellation of stars. Relieved against the ghostly light, the
+gigantic jet negro, Daggoo, loomed up to thrice his real stature, and
+seemed the black cloud from which the thunder had come. The parted
+mouth of Tashtego revealed his shark-white teeth, which strangely
+gleamed as if they too had been tipped by corpusants; while lit up by
+the preternatural light, Queequeg's tattooing burned like Satanic
+blue flames on his body.
+
+The tableau all waned at last with the pallidness aloft; and once
+more the Pequod and every soul on her decks were wrapped in a pall.
+A moment or two passed, when Starbuck, going forward, pushed against
+some one. It was Stubb. "What thinkest thou now, man; I heard thy
+cry; it was not the same in the song."
+
+"No, no, it wasn't; I said the corpusants have mercy on us all; and I
+hope they will, still. But do they only have mercy on long
+faces?--have they no bowels for a laugh? And look ye, Mr.
+Starbuck--but it's too dark to look. Hear me, then: I take that
+mast-head flame we saw for a sign of good luck; for those masts are
+rooted in a hold that is going to be chock a' block with sperm-oil,
+d'ye see; and so, all that sperm will work up into the masts, like
+sap in a tree. Yes, our three masts will yet be as three spermaceti
+candles--that's the good promise we saw."
+
+At that moment Starbuck caught sight of Stubb's face slowly beginning
+to glimmer into sight. Glancing upwards, he cried: "See! see!" and
+once more the high tapering flames were beheld with what seemed
+redoubled supernaturalness in their pallor.
+
+"The corpusants have mercy on us all," cried Stubb, again.
+
+At the base of the mainmast, full beneath the doubloon and the
+flame, the Parsee was kneeling in Ahab's front, but with his head
+bowed away from him; while near by, from the arched and overhanging
+rigging, where they had just been engaged securing a spar, a number
+of the seamen, arrested by the glare, now cohered together, and hung
+pendulous, like a knot of numbed wasps from a drooping, orchard twig.
+In various enchanted attitudes, like the standing, or stepping, or
+running skeletons in Herculaneum, others remained rooted to the deck;
+but all their eyes upcast.
+
+"Aye, aye, men!" cried Ahab. "Look up at it; mark it well; the white
+flame but lights the way to the White Whale! Hand me those mainmast
+links there; I would fain feel this pulse, and let mine beat against
+it; blood against fire! So."
+
+Then turning--the last link held fast in his left hand, he put his
+foot upon the Parsee; and with fixed upward eye, and high-flung right
+arm, he stood erect before the lofty tri-pointed trinity of flames.
+
+"Oh! thou clear spirit of clear fire, whom on these seas I as Persian
+once did worship, till in the sacramental act so burned by thee, that
+to this hour I bear the scar; I now know thee, thou clear spirit, and
+I now know that thy right worship is defiance. To neither love nor
+reverence wilt thou be kind; and e'en for hate thou canst but kill;
+and all are killed. No fearless fool now fronts thee. I own thy
+speechless, placeless power; but to the last gasp of my earthquake
+life will dispute its unconditional, unintegral mastery in me. In the
+midst of the personified impersonal, a personality stands here.
+Though but a point at best; whencesoe'er I came; wheresoe'er I go;
+yet while I earthly live, the queenly personality lives in me, and
+feels her royal rights. But war is pain, and hate is woe. Come in
+thy lowest form of love, and I will kneel and kiss thee; but at thy
+highest, come as mere supernal power; and though thou launchest
+navies of full-freighted worlds, there's that in here that still
+remains indifferent. Oh, thou clear spirit, of thy fire thou madest
+me, and like a true child of fire, I breathe it back to thee."
+
+[SUDDEN, REPEATED FLASHES OF LIGHTNING; THE NINE FLAMES LEAP
+LENGTHWISE TO THRICE THEIR PREVIOUS HEIGHT; AHAB, WITH THE REST,
+CLOSES HIS EYES, HIS RIGHT HAND PRESSED HARD UPON THEM.]
+
+"I own thy speechless, placeless power; said I not so? Nor was it
+wrung from me; nor do I now drop these links. Thou canst blind; but
+I can then grope. Thou canst consume; but I can then be ashes. Take
+the homage of these poor eyes, and shutter-hands. I would not take
+it. The lightning flashes through my skull; mine eye-balls ache and
+ache; my whole beaten brain seems as beheaded, and rolling on some
+stunning ground. Oh, oh! Yet blindfold, yet will I talk to thee.
+Light though thou be, thou leapest out of darkness; but I am darkness
+leaping out of light, leaping out of thee! The javelins cease; open
+eyes; see, or not? There burn the flames! Oh, thou magnanimous! now
+I do glory in my genealogy. But thou art but my fiery father; my
+sweet mother, I know not. Oh, cruel! what hast thou done with her?
+There lies my puzzle; but thine is greater. Thou knowest not how
+came ye, hence callest thyself unbegotten; certainly knowest not thy
+beginning, hence callest thyself unbegun. I know that of me, which
+thou knowest not of thyself, oh, thou omnipotent. There is some
+unsuffusing thing beyond thee, thou clear spirit, to whom all thy
+eternity is but time, all thy creativeness mechanical. Through thee,
+thy flaming self, my scorched eyes do dimly see it. Oh, thou
+foundling fire, thou hermit immemorial, thou too hast thy
+incommunicable riddle, thy unparticipated grief. Here again with
+haughty agony, I read my sire. Leap! leap up, and lick the sky! I
+leap with thee; I burn with thee; would fain be welded with thee;
+defyingly I worship thee!"
+
+"The boat! the boat!" cried Starbuck, "look at thy boat, old man!"
+
+Ahab's harpoon, the one forged at Perth's fire, remained firmly
+lashed in its conspicuous crotch, so that it projected beyond his
+whale-boat's bow; but the sea that had stove its bottom had caused
+the loose leather sheath to drop off; and from the keen steel barb
+there now came a levelled flame of pale, forked fire. As the silent
+harpoon burned there like a serpent's tongue, Starbuck grasped Ahab
+by the arm--"God, God is against thee, old man; forbear! 'tis an
+ill voyage! ill begun, ill continued; let me square the yards, while
+we may, old man, and make a fair wind of it homewards, to go on a
+better voyage than this."
+
+Overhearing Starbuck, the panic-stricken crew instantly ran to the
+braces--though not a sail was left aloft. For the moment all the
+aghast mate's thoughts seemed theirs; they raised a half mutinous
+cry. But dashing the rattling lightning links to the deck, and
+snatching the burning harpoon, Ahab waved it like a torch among them;
+swearing to transfix with it the first sailor that but cast loose a
+rope's end. Petrified by his aspect, and still more shrinking from
+the fiery dart that he held, the men fell back in dismay, and Ahab
+again spoke:--
+
+"All your oaths to hunt the White Whale are as binding as mine; and
+heart, soul, and body, lungs and life, old Ahab is bound. And that
+ye may know to what tune this heart beats; look ye here; thus I blow
+out the last fear!" And with one blast of his breath he extinguished
+the flame.
+
+As in the hurricane that sweeps the plain, men fly the neighborhood
+of some lone, gigantic elm, whose very height and strength but render
+it so much the more unsafe, because so much the more a mark for
+thunderbolts; so at those last words of Ahab's many of the mariners
+did run from him in a terror of dismay.
+
+
+
+CHAPTER 120
+
+The Deck Towards the End of the First Night Watch.
+
+AHAB STANDING BY THE HELM. STARBUCK APPROACHING HIM.
+
+
+We must send down the main-top-sail yard, sir. The band is working
+loose and the lee lift is half-stranded. Shall I strike it, sir?"
+
+"Strike nothing; lash it. If I had sky-sail poles, I'd sway them up
+now."
+
+"Sir!--in God's name!--sir?"
+
+"Well."
+
+"The anchors are working, sir. Shall I get them inboard?"
+
+"Strike nothing, and stir nothing, but lash everything. The wind
+rises, but it has not got up to my table-lands yet. Quick, and see
+to it.--By masts and keels! he takes me for the hunch-backed skipper
+of some coasting smack. Send down my main-top-sail yard! Ho,
+gluepots! Loftiest trucks were made for wildest winds, and this
+brain-truck of mine now sails amid the cloud-scud. Shall I strike
+that? Oh, none but cowards send down their brain-trucks in tempest
+time. What a hooroosh aloft there! I would e'en take it for
+sublime, did I not know that the colic is a noisy malady. Oh, take
+medicine, take medicine!"
+
+
+
+CHAPTER 121
+
+Midnight.--The Forecastle Bulwarks.
+
+
+STUBB AND FLASK MOUNTED ON THEM, AND PASSING ADDITIONAL LASHINGS OVER
+THE ANCHORS THERE HANGING.
+
+
+No, Stubb; you may pound that knot there as much as you please, but
+you will never pound into me what you were just now saying. And how
+long ago is it since you said the very contrary? Didn't you once say
+that whatever ship Ahab sails in, that ship should pay something
+extra on its insurance policy, just as though it were loaded with
+powder barrels aft and boxes of lucifers forward? Stop, now; didn't
+you say so?"
+
+"Well, suppose I did? What then? I've part changed my flesh since
+that time, why not my mind? Besides, supposing we ARE loaded with
+powder barrels aft and lucifers forward; how the devil could the
+lucifers get afire in this drenching spray here? Why, my little man,
+you have pretty red hair, but you couldn't get afire now. Shake
+yourself; you're Aquarius, or the water-bearer, Flask; might fill
+pitchers at your coat collar. Don't you see, then, that for these
+extra risks the Marine Insurance companies have extra guarantees?
+Here are hydrants, Flask. But hark, again, and I'll answer ye the
+other thing. First take your leg off from the crown of the anchor
+here, though, so I can pass the rope; now listen. What's the mighty
+difference between holding a mast's lightning-rod in the storm, and
+standing close by a mast that hasn't got any lightning-rod at all in
+a storm? Don't you see, you timber-head, that no harm can come to
+the holder of the rod, unless the mast is first struck? What are you
+talking about, then? Not one ship in a hundred carries rods, and
+Ahab,--aye, man, and all of us,--were in no more danger then, in my
+poor opinion, than all the crews in ten thousand ships now sailing
+the seas. Why, you King-Post, you, I suppose you would have every
+man in the world go about with a small lightning-rod running up the
+corner of his hat, like a militia officer's skewered feather, and
+trailing behind like his sash. Why don't ye be sensible, Flask? it's
+easy to be sensible; why don't ye, then? any man with half an eye can
+be sensible."
+
+"I don't know that, Stubb. You sometimes find it rather hard."
+
+"Yes, when a fellow's soaked through, it's hard to be sensible,
+that's a fact. And I am about drenched with this spray. Never mind;
+catch the turn there, and pass it. Seems to me we are lashing down
+these anchors now as if they were never going to be used again.
+Tying these two anchors here, Flask, seems like tying a man's hands
+behind him. And what big generous hands they are, to be sure. These
+are your iron fists, hey? What a hold they have, too! I wonder,
+Flask, whether the world is anchored anywhere; if she is, she swings
+with an uncommon long cable, though. There, hammer that knot down,
+and we've done. So; next to touching land, lighting on deck is the
+most satisfactory. I say, just wring out my jacket skirts, will ye?
+Thank ye. They laugh at long-togs so, Flask; but seems to me, a
+Long tailed coat ought always to be worn in all storms afloat. The
+tails tapering down that way, serve to carry off the water, d'ye see.
+Same with cocked hats; the cocks form gable-end eave-troughs, Flask.
+No more monkey-jackets and tarpaulins for me; I must mount a
+swallow-tail, and drive down a beaver; so. Halloa! whew! there goes
+my tarpaulin overboard; Lord, Lord, that the winds that come from
+heaven should be so unmannerly! This is a nasty night, lad."
+
+
+
+CHAPTER 122
+
+Midnight Aloft.--Thunder and Lightning.
+
+
+THE MAIN-TOP-SAIL YARD.--TASHTEGO PASSING NEW LASHINGS AROUND IT.
+
+
+"Um, um, um. Stop that thunder! Plenty too much thunder up here.
+What's the use of thunder? Um, um, um. We don't want thunder; we
+want rum; give us a glass of rum. Um, um, um!"
+
+
+
+CHAPTER 123
+
+The Musket.
+
+
+During the most violent shocks of the Typhoon, the man at the
+Pequod's jaw-bone tiller had several times been reelingly hurled to
+the deck by its spasmodic motions, even though preventer tackles had
+been attached to it--for they were slack--because some play to the
+tiller was indispensable.
+
+In a severe gale like this, while the ship is but a tossed
+shuttlecock to the blast, it is by no means uncommon to see the
+needles in the compasses, at intervals, go round and round. It was
+thus with the Pequod's; at almost every shock the helmsman had not
+failed to notice the whirling velocity with which they revolved upon
+the cards; it is a sight that hardly anyone can behold without some
+sort of unwonted emotion.
+
+Some hours after midnight, the Typhoon abated so much, that through
+the strenuous exertions of Starbuck and Stubb--one engaged forward
+and the other aft--the shivered remnants of the jib and fore and
+main-top-sails were cut adrift from the spars, and went eddying away
+to leeward, like the feathers of an albatross, which sometimes are
+cast to the winds when that storm-tossed bird is on the wing.
+
+The three corresponding new sails were now bent and reefed, and a
+storm-trysail was set further aft; so that the ship soon went through
+the water with some precision again; and the course--for the present,
+East-south-east--which he was to steer, if practicable, was once more
+given to the helmsman. For during the violence of the gale, he had
+only steered according to its vicissitudes. But as he was now
+bringing the ship as near her course as possible, watching the
+compass meanwhile, lo! a good sign! the wind seemed coming round
+astern; aye, the foul breeze became fair!
+
+Instantly the yards were squared, to the lively song of "HO! THE FAIR
+WIND! OH-YE-HO, CHEERLY MEN!" the crew singing for joy, that so
+promising an event should so soon have falsified the evil portents
+preceding it.
+
+In compliance with the standing order of his commander--to report
+immediately, and at any one of the twenty-four hours, any decided
+change in the affairs of the deck,--Starbuck had no sooner trimmed
+the yards to the breeze--however reluctantly and gloomily,--than he
+mechanically went below to apprise Captain Ahab of the circumstance.
+
+Ere knocking at his state-room, he involuntarily paused before it a
+moment. The cabin lamp--taking long swings this way and that--was
+burning fitfully, and casting fitful shadows upon the old man's
+bolted door,--a thin one, with fixed blinds inserted, in place of
+upper panels. The isolated subterraneousness of the cabin made a
+certain humming silence to reign there, though it was hooped round by
+all the roar of the elements. The loaded muskets in the rack were
+shiningly revealed, as they stood upright against the forward
+bulkhead. Starbuck was an honest, upright man; but out of Starbuck's
+heart, at that instant when he saw the muskets, there strangely
+evolved an evil thought; but so blent with its neutral or good
+accompaniments that for the instant he hardly knew it for itself.
+
+"He would have shot me once," he murmured, "yes, there's the very
+musket that he pointed at me;--that one with the studded stock; let
+me touch it--lift it. Strange, that I, who have handled so many
+deadly lances, strange, that I should shake so now. Loaded? I must
+see. Aye, aye; and powder in the pan;--that's not good. Best spill
+it?--wait. I'll cure myself of this. I'll hold the musket boldly
+while I think.--I come to report a fair wind to him. But how fair?
+Fair for death and doom,--THAT'S fair for Moby Dick. It's a fair
+wind that's only fair for that accursed fish.--The very tube he
+pointed at me!--the very one; THIS one--I hold it here; he would have
+killed me with the very thing I handle now.--Aye and he would fain
+kill all his crew. Does he not say he will not strike his spars to
+any gale? Has he not dashed his heavenly quadrant? and in these same
+perilous seas, gropes he not his way by mere dead reckoning of the
+error-abounding log? and in this very Typhoon, did he not swear that
+he would have no lightning-rods? But shall this crazed old man be
+tamely suffered to drag a whole ship's company down to doom with
+him?--Yes, it would make him the wilful murderer of thirty men and
+more, if this ship come to any deadly harm; and come to deadly harm,
+my soul swears this ship will, if Ahab have his way. If, then, he
+were this instant--put aside, that crime would not be his. Ha! is he
+muttering in his sleep? Yes, just there,--in there, he's sleeping.
+Sleeping? aye, but still alive, and soon awake again. I can't
+withstand thee, then, old man. Not reasoning; not remonstrance; not
+entreaty wilt thou hearken to; all this thou scornest. Flat
+obedience to thy own flat commands, this is all thou breathest. Aye,
+and say'st the men have vow'd thy vow; say'st all of us are Ahabs.
+Great God forbid!--But is there no other way? no lawful way?--Make
+him a prisoner to be taken home? What! hope to wrest this old man's
+living power from his own living hands? Only a fool would try it.
+Say he were pinioned even; knotted all over with ropes and hawsers;
+chained down to ring-bolts on this cabin floor; he would be more
+hideous than a caged tiger, then. I could not endure the sight;
+could not possibly fly his howlings; all comfort, sleep itself,
+inestimable reason would leave me on the long intolerable voyage.
+What, then, remains? The land is hundreds of leagues away, and
+locked Japan the nearest. I stand alone here upon an open sea, with
+two oceans and a whole continent between me and law.--Aye, aye, 'tis
+so.--Is heaven a murderer when its lightning strikes a would-be
+murderer in his bed, tindering sheets and skin together?--And would I
+be a murderer, then, if"--and slowly, stealthily, and half sideways
+looking, he placed the loaded musket's end against the door.
+
+"On this level, Ahab's hammock swings within; his head this way. A
+touch, and Starbuck may survive to hug his wife and child again.--Oh
+Mary! Mary!--boy! boy! boy!--But if I wake thee not to death, old
+man, who can tell to what unsounded deeps Starbuck's body this day
+week may sink, with all the crew! Great God, where art Thou? Shall
+I? shall I?--The wind has gone down and shifted, sir; the fore and
+main topsails are reefed and set; she heads her course."
+
+"Stern all! Oh Moby Dick, I clutch thy heart at last!"
+
+Such were the sounds that now came hurtling from out the old man's
+tormented sleep, as if Starbuck's voice had caused the long dumb
+dream to speak.
+
+The yet levelled musket shook like a drunkard's arm against the
+panel; Starbuck seemed wrestling with an angel; but turning from the
+door, he placed the death-tube in its rack, and left the place.
+
+"He's too sound asleep, Mr. Stubb; go thou down, and wake him, and
+tell him. I must see to the deck here. Thou know'st what to say."
+
+
+
+CHAPTER 124
+
+The Needle.
+
+
+Next morning the not-yet-subsided sea rolled in long slow billows of
+mighty bulk, and striving in the Pequod's gurgling track, pushed her
+on like giants' palms outspread. The strong, unstaggering breeze
+abounded so, that sky and air seemed vast outbellying sails; the
+whole world boomed before the wind. Muffled in the full morning
+light, the invisible sun was only known by the spread intensity of
+his place; where his bayonet rays moved on in stacks. Emblazonings,
+as of crowned Babylonian kings and queens, reigned over everything.
+The sea was as a crucible of molten gold, that bubblingly leaps with
+light and heat.
+
+Long maintaining an enchanted silence, Ahab stood apart; and every
+time the tetering ship loweringly pitched down her bowsprit, he
+turned to eye the bright sun's rays produced ahead; and when she
+profoundly settled by the stern, he turned behind, and saw the sun's
+rearward place, and how the same yellow rays were blending with his
+undeviating wake.
+
+"Ha, ha, my ship! thou mightest well be taken now for the sea-chariot
+of the sun. Ho, ho! all ye nations before my prow, I bring the sun
+to ye! Yoke on the further billows; hallo! a tandem, I drive the
+sea!"
+
+But suddenly reined back by some counter thought, he hurried towards
+the helm, huskily demanding how the ship was heading.
+
+"East-sou-east, sir," said the frightened steersman.
+
+"Thou liest!" smiting him with his clenched fist. "Heading East at
+this hour in the morning, and the sun astern?"
+
+Upon this every soul was confounded; for the phenomenon just then
+observed by Ahab had unaccountably escaped every one else; but its
+very blinding palpableness must have been the cause.
+
+Thrusting his head half way into the binnacle, Ahab caught one
+glimpse of the compasses; his uplifted arm slowly fell; for a moment
+he almost seemed to stagger. Standing behind him Starbuck looked,
+and lo! the two compasses pointed East, and the Pequod was as
+infallibly going West.
+
+But ere the first wild alarm could get out abroad among the crew, the
+old man with a rigid laugh exclaimed, "I have it! It has happened
+before. Mr. Starbuck, last night's thunder turned our
+compasses--that's all. Thou hast before now heard of such a thing, I
+take it."
+
+"Aye; but never before has it happened to me, sir," said the pale
+mate, gloomily.
+
+Here, it must needs be said, that accidents like this have in more
+than one case occurred to ships in violent storms. The magnetic
+energy, as developed in the mariner's needle, is, as all know,
+essentially one with the electricity beheld in heaven; hence it is
+not to be much marvelled at, that such things should be. Instances
+where the lightning has actually struck the vessel, so as to smite
+down some of the spars and rigging, the effect upon the needle has at
+times been still more fatal; all its loadstone virtue being
+annihilated, so that the before magnetic steel was of no more use
+than an old wife's knitting needle. But in either case, the needle
+never again, of itself, recovers the original virtue thus marred or
+lost; and if the binnacle compasses be affected, the same fate
+reaches all the others that may be in the ship; even were the
+lowermost one inserted into the kelson.
+
+Deliberately standing before the binnacle, and eyeing the
+transpointed compasses, the old man, with the sharp of his extended
+hand, now took the precise bearing of the sun, and satisfied that the
+needles were exactly inverted, shouted out his orders for the ship's
+course to be changed accordingly. The yards were hard up; and once
+more the Pequod thrust her undaunted bows into the opposing wind, for
+the supposed fair one had only been juggling her.
+
+Meanwhile, whatever were his own secret thoughts, Starbuck said
+nothing, but quietly he issued all requisite orders; while Stubb and
+Flask--who in some small degree seemed then to be sharing his
+feelings--likewise unmurmuringly acquiesced. As for the men, though
+some of them lowly rumbled, their fear of Ahab was greater than their
+fear of Fate. But as ever before, the pagan harpooneers remained
+almost wholly unimpressed; or if impressed, it was only with a
+certain magnetism shot into their congenial hearts from inflexible
+Ahab's.
+
+For a space the old man walked the deck in rolling reveries. But
+chancing to slip with his ivory heel, he saw the crushed copper
+sight-tubes of the quadrant he had the day before dashed to the deck.
+
+"Thou poor, proud heaven-gazer and sun's pilot! yesterday I wrecked
+thee, and to-day the compasses would fain have wrecked me. So, so.
+But Ahab is lord over the level loadstone yet. Mr. Starbuck--a lance
+without a pole; a top-maul, and the smallest of the sail-maker's
+needles. Quick!"
+
+Accessory, perhaps, to the impulse dictating the thing he was now
+about to do, were certain prudential motives, whose object might have
+been to revive the spirits of his crew by a stroke of his subtile
+skill, in a matter so wondrous as that of the inverted compasses.
+Besides, the old man well knew that to steer by transpointed needles,
+though clumsily practicable, was not a thing to be passed over by
+superstitious sailors, without some shudderings and evil portents.
+
+"Men," said he, steadily turning upon the crew, as the mate handed
+him the things he had demanded, "my men, the thunder turned old
+Ahab's needles; but out of this bit of steel Ahab can make one of his
+own, that will point as true as any."
+
+Abashed glances of servile wonder were exchanged by the sailors, as
+this was said; and with fascinated eyes they awaited whatever magic
+might follow. But Starbuck looked away.
+
+With a blow from the top-maul Ahab knocked off the steel head of the
+lance, and then handing to the mate the long iron rod remaining, bade
+him hold it upright, without its touching the deck. Then, with the
+maul, after repeatedly smiting the upper end of this iron rod, he
+placed the blunted needle endwise on the top of it, and less strongly
+hammered that, several times, the mate still holding the rod as
+before. Then going through some small strange motions with
+it--whether indispensable to the magnetizing of the steel, or merely
+intended to augment the awe of the crew, is uncertain--he called for
+linen thread; and moving to the binnacle, slipped out the two
+reversed needles there, and horizontally suspended the sail-needle by
+its middle, over one of the compass-cards. At first, the steel went
+round and round, quivering and vibrating at either end; but at last
+it settled to its place, when Ahab, who had been intently watching
+for this result, stepped frankly back from the binnacle, and pointing
+his stretched arm towards it, exclaimed,--"Look ye, for yourselves,
+if Ahab be not lord of the level loadstone! The sun is East, and
+that compass swears it!"
+
+One after another they peered in, for nothing but their own eyes
+could persuade such ignorance as theirs, and one after another they
+slunk away.
+
+In his fiery eyes of scorn and triumph, you then saw Ahab in all his
+fatal pride.
+
+
+
+CHAPTER 125
+
+The Log and Line.
+
+
+While now the fated Pequod had been so long afloat this voyage, the
+log and line had but very seldom been in use. Owing to a confident
+reliance upon other means of determining the vessel's place, some
+merchantmen, and many whalemen, especially when cruising, wholly
+neglect to heave the log; though at the same time, and frequently
+more for form's sake than anything else, regularly putting down upon
+the customary slate the course steered by the ship, as well as the
+presumed average rate of progression every hour. It had been thus
+with the Pequod. The wooden reel and angular log attached hung, long
+untouched, just beneath the railing of the after bulwarks. Rains and
+spray had damped it; sun and wind had warped it; all the elements
+had combined to rot a thing that hung so idly. But heedless of all
+this, his mood seized Ahab, as he happened to glance upon the reel,
+not many hours after the magnet scene, and he remembered how his
+quadrant was no more, and recalled his frantic oath about the level
+log and line. The ship was sailing plungingly; astern the billows
+rolled in riots.
+
+"Forward, there! Heave the log!"
+
+Two seamen came. The golden-hued Tahitian and the grizzly Manxman.
+"Take the reel, one of ye, I'll heave."
+
+They went towards the extreme stern, on the ship's lee side, where
+the deck, with the oblique energy of the wind, was now almost dipping
+into the creamy, sidelong-rushing sea.
+
+The Manxman took the reel, and holding it high up, by the projecting
+handle-ends of the spindle, round which the spool of line revolved,
+so stood with the angular log hanging downwards, till Ahab advanced
+to him.
+
+Ahab stood before him, and was lightly unwinding some thirty or forty
+turns to form a preliminary hand-coil to toss overboard, when the old
+Manxman, who was intently eyeing both him and the line, made bold to
+speak.
+
+"Sir, I mistrust it; this line looks far gone, long heat and wet have
+spoiled it."
+
+"'Twill hold, old gentleman. Long heat and wet, have they spoiled
+thee? Thou seem'st to hold. Or, truer perhaps, life holds thee;
+not thou it."
+
+"I hold the spool, sir. But just as my captain says. With these
+grey hairs of mine 'tis not worth while disputing, 'specially with a
+superior, who'll ne'er confess."
+
+"What's that? There now's a patched professor in Queen Nature's
+granite-founded College; but methinks he's too subservient. Where
+wert thou born?"
+
+"In the little rocky Isle of Man, sir."
+
+"Excellent! Thou'st hit the world by that."
+
+"I know not, sir, but I was born there."
+
+"In the Isle of Man, hey? Well, the other way, it's good. Here's a
+man from Man; a man born in once independent Man, and now unmanned of
+Man; which is sucked in--by what? Up with the reel! The dead, blind
+wall butts all inquiring heads at last. Up with it! So."
+
+The log was heaved. The loose coils rapidly straightened out in a
+long dragging line astern, and then, instantly, the reel began to
+whirl. In turn, jerkingly raised and lowered by the rolling billows,
+the towing resistance of the log caused the old reelman to stagger
+strangely.
+
+"Hold hard!"
+
+Snap! the overstrained line sagged down in one long festoon; the
+tugging log was gone.
+
+"I crush the quadrant, the thunder turns the needles, and now the mad
+sea parts the log-line. But Ahab can mend all. Haul in here,
+Tahitian; reel up, Manxman. And look ye, let the carpenter make
+another log, and mend thou the line. See to it."
+
+"There he goes now; to him nothing's happened; but to me, the skewer
+seems loosening out of the middle of the world. Haul in, haul in,
+Tahitian! These lines run whole, and whirling out: come in broken,
+and dragging slow. Ha, Pip? come to help; eh, Pip?"
+
+"Pip? whom call ye Pip? Pip jumped from the whale-boat. Pip's
+missing. Let's see now if ye haven't fished him up here, fisherman.
+It drags hard; I guess he's holding on. Jerk him, Tahiti! Jerk him
+off; we haul in no cowards here. Ho! there's his arm just breaking
+water. A hatchet! a hatchet! cut it off--we haul in no cowards here.
+Captain Ahab! sir, sir! here's Pip, trying to get on board again."
+
+"Peace, thou crazy loon," cried the Manxman, seizing him by the arm.
+"Away from the quarter-deck!"
+
+"The greater idiot ever scolds the lesser," muttered Ahab, advancing.
+"Hands off from that holiness! Where sayest thou Pip was, boy?
+
+"Astern there, sir, astern! Lo! lo!"
+
+"And who art thou, boy? I see not my reflection in the vacant pupils
+of thy eyes. Oh God! that man should be a thing for immortal souls
+to sieve through! Who art thou, boy?"
+
+"Bell-boy, sir; ship's-crier; ding, dong, ding! Pip! Pip! Pip! One
+hundred pounds of clay reward for Pip; five feet high--looks
+cowardly--quickest known by that! Ding, dong, ding! Who's seen Pip
+the coward?"
+
+"There can be no hearts above the snow-line. Oh, ye frozen heavens!
+look down here. Ye did beget this luckless child, and have abandoned
+him, ye creative libertines. Here, boy; Ahab's cabin shall be Pip's
+home henceforth, while Ahab lives. Thou touchest my inmost centre,
+boy; thou art tied to me by cords woven of my heart-strings. Come,
+let's down."
+
+"What's this? here's velvet shark-skin," intently gazing at Ahab's
+hand, and feeling it. "Ah, now, had poor Pip but felt so kind a
+thing as this, perhaps he had ne'er been lost! This seems to me,
+sir, as a man-rope; something that weak souls may hold by. Oh, sir,
+let old Perth now come and rivet these two hands together; the black
+one with the white, for I will not let this go."
+
+"Oh, boy, nor will I thee, unless I should thereby drag thee to worse
+horrors than are here. Come, then, to my cabin. Lo! ye believers in
+gods all goodness, and in man all ill, lo you! see the omniscient
+gods oblivious of suffering man; and man, though idiotic, and knowing
+not what he does, yet full of the sweet things of love and gratitude.
+Come! I feel prouder leading thee by thy black hand, than though I
+grasped an Emperor's!"
+
+"There go two daft ones now," muttered the old Manxman. "One daft
+with strength, the other daft with weakness. But here's the end of
+the rotten line--all dripping, too. Mend it, eh? I think we had
+best have a new line altogether. I'll see Mr. Stubb about it."
+
+
+
+CHAPTER 126
+
+The Life-Buoy.
+
+
+Steering now south-eastward by Ahab's levelled steel, and her
+progress solely determined by Ahab's level log and line; the Pequod
+held on her path towards the Equator. Making so long a passage
+through such unfrequented waters, descrying no ships, and ere long,
+sideways impelled by unvarying trade winds, over waves monotonously
+mild; all these seemed the strange calm things preluding some riotous
+and desperate scene.
+
+At last, when the ship drew near to the outskirts, as it were, of the
+Equatorial fishing-ground, and in the deep darkness that goes before
+the dawn, was sailing by a cluster of rocky islets; the watch--then
+headed by Flask--was startled by a cry so plaintively wild and
+unearthly--like half-articulated wailings of the ghosts of all
+Herod's murdered Innocents--that one and all, they started from their
+reveries, and for the space of some moments stood, or sat, or leaned
+all transfixedly listening, like the carved Roman slave, while that
+wild cry remained within hearing. The Christian or civilized part of
+the crew said it was mermaids, and shuddered; but the pagan
+harpooneers remained unappalled. Yet the grey Manxman--the oldest
+mariner of all--declared that the wild thrilling sounds that were
+heard, were the voices of newly drowned men in the sea.
+
+Below in his hammock, Ahab did not hear of this till grey dawn, when
+he came to the deck; it was then recounted to him by Flask, not
+unaccompanied with hinted dark meanings. He hollowly laughed, and
+thus explained the wonder.
+
+Those rocky islands the ship had passed were the resort of great
+numbers of seals, and some young seals that had lost their dams, or
+some dams that had lost their cubs, must have risen nigh the ship and
+kept company with her, crying and sobbing with their human sort of
+wail. But this only the more affected some of them, because most
+mariners cherish a very superstitious feeling about seals, arising
+not only from their peculiar tones when in distress, but also from
+the human look of their round heads and semi-intelligent faces, seen
+peeringly uprising from the water alongside. In the sea, under
+certain circumstances, seals have more than once been mistaken for
+men.
+
+But the bodings of the crew were destined to receive a most plausible
+confirmation in the fate of one of their number that morning. At
+sun-rise this man went from his hammock to his mast-head at the fore;
+and whether it was that he was not yet half waked from his sleep (for
+sailors sometimes go aloft in a transition state), whether it was
+thus with the man, there is now no telling; but, be that as it may,
+he had not been long at his perch, when a cry was heard--a cry and a
+rushing--and looking up, they saw a falling phantom in the air; and
+looking down, a little tossed heap of white bubbles in the blue of
+the sea.
+
+The life-buoy--a long slender cask--was dropped from the stern, where
+it always hung obedient to a cunning spring; but no hand rose to
+seize it, and the sun having long beat upon this cask it had
+shrunken, so that it slowly filled, and that parched wood also
+filled at its every pore; and the studded iron-bound cask followed
+the sailor to the bottom, as if to yield him his pillow, though in
+sooth but a hard one.
+
+And thus the first man of the Pequod that mounted the mast to look
+out for the White Whale, on the White Whale's own peculiar ground;
+that man was swallowed up in the deep. But few, perhaps, thought of
+that at the time. Indeed, in some sort, they were not grieved at
+this event, at least as a portent; for they regarded it, not as a
+foreshadowing of evil in the future, but as the fulfilment of an
+evil already presaged. They declared that now they knew the reason
+of those wild shrieks they had heard the night before. But again the
+old Manxman said nay.
+
+The lost life-buoy was now to be replaced; Starbuck was directed to
+see to it; but as no cask of sufficient lightness could be found, and
+as in the feverish eagerness of what seemed the approaching crisis of
+the voyage, all hands were impatient of any toil but what was
+directly connected with its final end, whatever that might prove to
+be; therefore, they were going to leave the ship's stern unprovided
+with a buoy, when by certain strange signs and inuendoes Queequeg
+hinted a hint concerning his coffin.
+
+"A life-buoy of a coffin!" cried Starbuck, starting.
+
+"Rather queer, that, I should say," said Stubb.
+
+"It will make a good enough one," said Flask, "the carpenter here can
+arrange it easily."
+
+"Bring it up; there's nothing else for it," said Starbuck, after a
+melancholy pause. "Rig it, carpenter; do not look at me so--the
+coffin, I mean. Dost thou hear me? Rig it."
+
+"And shall I nail down the lid, sir?" moving his hand as with a
+hammer.
+
+"Aye."
+
+"And shall I caulk the seams, sir?" moving his hand as with a
+caulking-iron.
+
+"Aye."
+
+"And shall I then pay over the same with pitch, sir?" moving his hand
+as with a pitch-pot.
+
+"Away! what possesses thee to this? Make a life-buoy of the coffin,
+and no more.--Mr. Stubb, Mr. Flask, come forward with me."
+
+"He goes off in a huff. The whole he can endure; at the parts he
+baulks. Now I don't like this. I make a leg for Captain Ahab, and
+he wears it like a gentleman; but I make a bandbox for Queequeg, and
+he won't put his head into it. Are all my pains to go for nothing
+with that coffin? And now I'm ordered to make a life-buoy of it.
+It's like turning an old coat; going to bring the flesh on the other
+side now. I don't like this cobbling sort of business--I don't like
+it at all; it's undignified; it's not my place. Let tinkers' brats
+do tinkerings; we are their betters. I like to take in hand none but
+clean, virgin, fair-and-square mathematical jobs, something that
+regularly begins at the beginning, and is at the middle when midway,
+and comes to an end at the conclusion; not a cobbler's job, that's at
+an end in the middle, and at the beginning at the end. It's the old
+woman's tricks to be giving cobbling jobs. Lord! what an affection
+all old women have for tinkers. I know an old woman of sixty-five
+who ran away with a bald-headed young tinker once. And that's the
+reason I never would work for lonely widow old women ashore, when I
+kept my job-shop in the Vineyard; they might have taken it into their
+lonely old heads to run off with me. But heigh-ho! there are no caps
+at sea but snow-caps. Let me see. Nail down the lid; caulk the
+seams; pay over the same with pitch; batten them down tight, and hang
+it with the snap-spring over the ship's stern. Were ever such things
+done before with a coffin? Some superstitious old carpenters, now,
+would be tied up in the rigging, ere they would do the job. But I'm
+made of knotty Aroostook hemlock; I don't budge. Cruppered with a
+coffin! Sailing about with a grave-yard tray! But never mind. We
+workers in woods make bridal-bedsteads and card-tables, as well as
+coffins and hearses. We work by the month, or by the job, or by the
+profit; not for us to ask the why and wherefore of our work, unless
+it be too confounded cobbling, and then we stash it if we can. Hem!
+I'll do the job, now, tenderly. I'll have me--let's see--how many in
+the ship's company, all told? But I've forgotten. Any way, I'll
+have me thirty separate, Turk's-headed life-lines, each three feet
+long hanging all round to the coffin. Then, if the hull go down,
+there'll be thirty lively fellows all fighting for one coffin, a
+sight not seen very often beneath the sun! Come hammer,
+caulking-iron, pitch-pot, and marling-spike! Let's to it."
+
+
+
+CHAPTER 127
+
+The Deck.
+
+
+THE COFFIN LAID UPON TWO LINE-TUBS, BETWEEN THE VICE-BENCH AND THE
+OPEN HATCHWAY; THE CARPENTER CAULKING ITS SEAMS; THE STRING OF
+TWISTED OAKUM SLOWLY UNWINDING FROM A LARGE ROLL OF IT PLACED IN THE
+BOSOM OF HIS FROCK.--AHAB COMES SLOWLY FROM THE CABIN-GANGWAY, AND
+HEARS PIP FOLLOWING HIM.
+
+
+Back, lad; I will be with ye again presently. He goes! Not this
+hand complies with my humor more genially than that boy.--Middle
+aisle of a church! What's here?"
+
+"Life-buoy, sir. Mr. Starbuck's orders. Oh, look, sir! Beware the
+hatchway!"
+
+"Thank ye, man. Thy coffin lies handy to the vault."
+
+"Sir? The hatchway? oh! So it does, sir, so it does."
+
+"Art not thou the leg-maker? Look, did not this stump come from thy
+shop?"
+
+"I believe it did, sir; does the ferrule stand, sir?"
+
+"Well enough. But art thou not also the undertaker?"
+
+"Aye, sir; I patched up this thing here as a coffin for Queequeg; but
+they've set me now to turning it into something else."
+
+"Then tell me; art thou not an arrant, all-grasping, intermeddling,
+monopolising, heathenish old scamp, to be one day making legs, and
+the next day coffins to clap them in, and yet again life-buoys out of
+those same coffins? Thou art as unprincipled as the gods, and as
+much of a jack-of-all-trades."
+
+"But I do not mean anything, sir. I do as I do."
+
+"The gods again. Hark ye, dost thou not ever sing working about a
+coffin? The Titans, they say, hummed snatches when chipping out the
+craters for volcanoes; and the grave-digger in the play sings, spade
+in hand. Dost thou never?"
+
+"Sing, sir? Do I sing? Oh, I'm indifferent enough, sir, for that;
+but the reason why the grave-digger made music must have been because
+there was none in his spade, sir. But the caulking mallet is full of
+it. Hark to it."
+
+"Aye, and that's because the lid there's a sounding-board; and what
+in all things makes the sounding-board is this--there's naught
+beneath. And yet, a coffin with a body in it rings pretty much the
+same, Carpenter. Hast thou ever helped carry a bier, and heard the
+coffin knock against the churchyard gate, going in?
+
+"Faith, sir, I've--"
+
+"Faith? What's that?"
+
+"Why, faith, sir, it's only a sort of exclamation-like--that's all,
+sir."
+
+"Um, um; go on."
+
+"I was about to say, sir, that--"
+
+"Art thou a silk-worm? Dost thou spin thy own shroud out of thyself?
+Look at thy bosom! Despatch! and get these traps out of sight."
+
+"He goes aft. That was sudden, now; but squalls come sudden in hot
+latitudes. I've heard that the Isle of Albemarle, one of the
+Gallipagos, is cut by the Equator right in the middle. Seems to me
+some sort of Equator cuts yon old man, too, right in his middle.
+He's always under the Line--fiery hot, I tell ye! He's looking this
+way--come, oakum; quick. Here we go again. This wooden mallet is
+the cork, and I'm the professor of musical glasses--tap, tap!"
+
+(AHAB TO HIMSELF.)
+
+"There's a sight! There's a sound! The grey-headed woodpecker
+tapping the hollow tree! Blind and dumb might well be envied now.
+See! that thing rests on two line-tubs, full of tow-lines. A most
+malicious wag, that fellow. Rat-tat! So man's seconds tick! Oh!
+how immaterial are all materials! What things real are there, but
+imponderable thoughts? Here now's the very dreaded symbol of grim
+death, by a mere hap, made the expressive sign of the help and hope
+of most endangered life. A life-buoy of a coffin! Does it go
+further? Can it be that in some spiritual sense the coffin is, after
+all, but an immortality-preserver! I'll think of that. But no. So
+far gone am I in the dark side of earth, that its other side, the
+theoretic bright one, seems but uncertain twilight to me. Will ye
+never have done, Carpenter, with that accursed sound? I go below;
+let me not see that thing here when I return again. Now, then, Pip,
+we'll talk this over; I do suck most wondrous philosophies from thee!
+Some unknown conduits from the unknown worlds must empty into thee!"
+
+
+
+CHAPTER 128
+
+The Pequod Meets The Rachel.
+
+
+Next day, a large ship, the Rachel, was descried, bearing directly
+down upon the Pequod, all her spars thickly clustering with men. At
+the time the Pequod was making good speed through the water; but as
+the broad-winged windward stranger shot nigh to her, the boastful
+sails all fell together as blank bladders that are burst, and all
+life fled from the smitten hull.
+
+"Bad news; she brings bad news," muttered the old Manxman. But ere
+her commander, who, with trumpet to mouth, stood up in his boat; ere
+he could hopefully hail, Ahab's voice was heard.
+
+"Hast seen the White Whale?"
+
+"Aye, yesterday. Have ye seen a whale-boat adrift?"
+
+Throttling his joy, Ahab negatively answered this unexpected
+question; and would then have fain boarded the stranger, when the
+stranger captain himself, having stopped his vessel's way, was seen
+descending her side. A few keen pulls, and his boat-hook soon
+clinched the Pequod's main-chains, and he sprang to the deck.
+Immediately he was recognised by Ahab for a Nantucketer he knew. But
+no formal salutation was exchanged.
+
+"Where was he?--not killed!--not killed!" cried Ahab, closely
+advancing. "How was it?"
+
+It seemed that somewhat late on the afternoon of the day previous,
+while three of the stranger's boats were engaged with a shoal of
+whales, which had led them some four or five miles from the ship; and
+while they were yet in swift chase to windward, the white hump and
+head of Moby Dick had suddenly loomed up out of the water, not very
+far to leeward; whereupon, the fourth rigged boat--a reserved
+one--had been instantly lowered in chase. After a keen sail before
+the wind, this fourth boat--the swiftest keeled of all--seemed to
+have succeeded in fastening--at least, as well as the man at the
+mast-head could tell anything about it. In the distance he saw the
+diminished dotted boat; and then a swift gleam of bubbling white
+water; and after that nothing more; whence it was concluded that the
+stricken whale must have indefinitely run away with his pursuers, as
+often happens. There was some apprehension, but no positive alarm,
+as yet. The recall signals were placed in the rigging; darkness came
+on; and forced to pick up her three far to windward boats--ere going
+in quest of the fourth one in the precisely opposite direction--the
+ship had not only been necessitated to leave that boat to its fate
+till near midnight, but, for the time, to increase her distance from
+it. But the rest of her crew being at last safe aboard, she crowded
+all sail--stunsail on stunsail--after the missing boat; kindling a
+fire in her try-pots for a beacon; and every other man aloft on the
+look-out. But though when she had thus sailed a sufficient distance
+to gain the presumed place of the absent ones when last seen; though
+she then paused to lower her spare boats to pull all around her; and
+not finding anything, had again dashed on; again paused, and lowered
+her boats; and though she had thus continued doing till daylight;
+yet not the least glimpse of the missing keel had been seen.
+
+The story told, the stranger Captain immediately went on to reveal
+his object in boarding the Pequod. He desired that ship to unite
+with his own in the search; by sailing over the sea some four or five
+miles apart, on parallel lines, and so sweeping a double horizon, as
+it were.
+
+"I will wager something now," whispered Stubb to Flask, "that some
+one in that missing boat wore off that Captain's best coat; mayhap,
+his watch--he's so cursed anxious to get it back. Who ever heard of
+two pious whale-ships cruising after one missing whale-boat in the
+height of the whaling season? See, Flask, only see how pale he
+looks--pale in the very buttons of his eyes--look--it wasn't the
+coat--it must have been the--"
+
+"My boy, my own boy is among them. For God's sake--I beg, I
+conjure"--here exclaimed the stranger Captain to Ahab, who thus far
+had but icily received his petition. "For eight-and-forty hours let
+me charter your ship--I will gladly pay for it, and roundly pay for
+it--if there be no other way--for eight-and-forty hours only--only
+that--you must, oh, you must, and you SHALL do this thing."
+
+"His son!" cried Stubb, "oh, it's his son he's lost! I take back the
+coat and watch--what says Ahab? We must save that boy."
+
+"He's drowned with the rest on 'em, last night," said the old Manx
+sailor standing behind them; "I heard; all of ye heard their
+spirits."
+
+Now, as it shortly turned out, what made this incident of the
+Rachel's the more melancholy, was the circumstance, that not only was
+one of the Captain's sons among the number of the missing boat's
+crew; but among the number of the other boat's crews, at the same
+time, but on the other hand, separated from the ship during the dark
+vicissitudes of the chase, there had been still another son; as that
+for a time, the wretched father was plunged to the bottom of the
+cruellest perplexity; which was only solved for him by his chief
+mate's instinctively adopting the ordinary procedure of a whale-ship
+in such emergencies, that is, when placed between jeopardized but
+divided boats, always to pick up the majority first. But the
+captain, for some unknown constitutional reason, had refrained from
+mentioning all this, and not till forced to it by Ahab's iciness did
+he allude to his one yet missing boy; a little lad, but twelve years
+old, whose father with the earnest but unmisgiving hardihood of a
+Nantucketer's paternal love, had thus early sought to initiate him in
+the perils and wonders of a vocation almost immemorially the destiny
+of all his race. Nor does it unfrequently occur, that Nantucket
+captains will send a son of such tender age away from them, for a
+protracted three or four years' voyage in some other ship than their
+own; so that their first knowledge of a whaleman's career shall be
+unenervated by any chance display of a father's natural but untimely
+partiality, or undue apprehensiveness and concern.
+
+Meantime, now the stranger was still beseeching his poor boon of
+Ahab; and Ahab still stood like an anvil, receiving every shock, but
+without the least quivering of his own.
+
+"I will not go," said the stranger, "till you say aye to me. Do to
+me as you would have me do to you in the like case. For YOU too have
+a boy, Captain Ahab--though but a child, and nestling safely at home
+now--a child of your old age too--Yes, yes, you relent; I see
+it--run, run, men, now, and stand by to square in the yards."
+
+"Avast," cried Ahab--"touch not a rope-yarn"; then in a voice that
+prolongingly moulded every word--"Captain Gardiner, I will not do it.
+Even now I lose time. Good-bye, good-bye. God bless ye, man, and
+may I forgive myself, but I must go. Mr. Starbuck, look at the
+binnacle watch, and in three minutes from this present instant warn
+off all strangers: then brace forward again, and let the ship sail
+as before."
+
+Hurriedly turning, with averted face, he descended into his cabin,
+leaving the strange captain transfixed at this unconditional and
+utter rejection of his so earnest suit. But starting from his
+enchantment, Gardiner silently hurried to the side; more fell than
+stepped into his boat, and returned to his ship.
+
+Soon the two ships diverged their wakes; and long as the strange
+vessel was in view, she was seen to yaw hither and thither at every
+dark spot, however small, on the sea. This way and that her yards
+were swung round; starboard and larboard, she continued to tack;
+now she beat against a head sea; and again it pushed her before it;
+while all the while, her masts and yards were thickly clustered with
+men, as three tall cherry trees, when the boys are cherrying among
+the boughs.
+
+But by her still halting course and winding, woeful way, you plainly
+saw that this ship that so wept with spray, still remained without
+comfort. She was Rachel, weeping for her children, because they were
+not.
+
+
+
+CHAPTER 129
+
+The Cabin.
+
+
+(AHAB MOVING TO GO ON DECK; PIP CATCHES HIM BY THE HAND TO FOLLOW.)
+
+Lad, lad, I tell thee thou must not follow Ahab now. The hour is
+coming when Ahab would not scare thee from him, yet would not have
+thee by him. There is that in thee, poor lad, which I feel too
+curing to my malady. Like cures like; and for this hunt, my malady
+becomes my most desired health. Do thou abide below here, where they
+shall serve thee, as if thou wert the captain. Aye, lad, thou shalt
+sit here in my own screwed chair; another screw to it, thou must be."
+
+"No, no, no! ye have not a whole body, sir; do ye but use poor me for
+your one lost leg; only tread upon me, sir; I ask no more, so I
+remain a part of ye."
+
+"Oh! spite of million villains, this makes me a bigot in the fadeless
+fidelity of man!--and a black! and crazy!--but methinks
+like-cures-like applies to him too; he grows so sane again."
+
+"They tell me, sir, that Stubb did once desert poor little Pip, whose
+drowned bones now show white, for all the blackness of his living
+skin. But I will never desert ye, sir, as Stubb did him. Sir, I
+must go with ye."
+
+"If thou speakest thus to me much more, Ahab's purpose keels up in
+him. I tell thee no; it cannot be."
+
+"Oh good master, master, master!
+
+"Weep so, and I will murder thee! have a care, for Ahab too is mad.
+Listen, and thou wilt often hear my ivory foot upon the deck, and
+still know that I am there. And now I quit thee. Thy hand!--Met!
+True art thou, lad, as the circumference to its centre. So: God for
+ever bless thee; and if it come to that,--God for ever save thee, let
+what will befall."
+
+(AHAB GOES; PIP STEPS ONE STEP FORWARD.)
+
+
+"Here he this instant stood; I stand in his air,--but I'm alone.
+Now were even poor Pip here I could endure it, but he's missing.
+Pip! Pip! Ding, dong, ding! Who's seen Pip? He must be up here;
+let's try the door. What? neither lock, nor bolt, nor bar; and yet
+there's no opening it. It must be the spell; he told me to stay
+here: Aye, and told me this screwed chair was mine. Here, then, I'll
+seat me, against the transom, in the ship's full middle, all her keel
+and her three masts before me. Here, our old sailors say, in their
+black seventy-fours great admirals sometimes sit at table, and lord
+it over rows of captains and lieutenants. Ha! what's this? epaulets!
+epaulets! the epaulets all come crowding! Pass round the decanters;
+glad to see ye; fill up, monsieurs! What an odd feeling, now, when a
+black boy's host to white men with gold lace upon their
+coats!--Monsieurs, have ye seen one Pip?--a little negro lad, five
+feet high, hang-dog look, and cowardly! Jumped from a whale-boat
+once;--seen him? No! Well then, fill up again, captains, and let's
+drink shame upon all cowards! I name no names. Shame upon them!
+Put one foot upon the table. Shame upon all cowards.--Hist! above
+there, I hear ivory--Oh, master! master! I am indeed down-hearted
+when you walk over me. But here I'll stay, though this stern
+strikes rocks; and they bulge through; and oysters come to join me."
+
+
+
+CHAPTER 130
+
+The Hat.
+
+
+And now that at the proper time and place, after so long and wide a
+preliminary cruise, Ahab,--all other whaling waters swept--seemed to
+have chased his foe into an ocean-fold, to slay him the more securely
+there; now, that he found himself hard by the very latitude and
+longitude where his tormenting wound had been inflicted; now that a
+vessel had been spoken which on the very day preceding had actually
+encountered Moby Dick;--and now that all his successive meetings with
+various ships contrastingly concurred to show the demoniac
+indifference with which the white whale tore his hunters, whether
+sinning or sinned against; now it was that there lurked a something
+in the old man's eyes, which it was hardly sufferable for feeble
+souls to see. As the unsetting polar star, which through the
+livelong, arctic, six months' night sustains its piercing, steady,
+central gaze; so Ahab's purpose now fixedly gleamed down upon the
+constant midnight of the gloomy crew. It domineered above them so,
+that all their bodings, doubts, misgivings, fears, were fain to hide
+beneath their souls, and not sprout forth a single spear or leaf.
+
+In this foreshadowing interval too, all humor, forced or natural,
+vanished. Stubb no more strove to raise a smile; Starbuck no more
+strove to check one. Alike, joy and sorrow, hope and fear, seemed
+ground to finest dust, and powdered, for the time, in the clamped
+mortar of Ahab's iron soul. Like machines, they dumbly moved about
+the deck, ever conscious that the old man's despot eye was on them.
+
+But did you deeply scan him in his more secret confidential hours;
+when he thought no glance but one was on him; then you would have
+seen that even as Ahab's eyes so awed the crew's, the inscrutable
+Parsee's glance awed his; or somehow, at least, in some wild way, at
+times affected it. Such an added, gliding strangeness began to
+invest the thin Fedallah now; such ceaseless shudderings shook him;
+that the men looked dubious at him; half uncertain, as it seemed,
+whether indeed he were a mortal substance, or else a tremulous shadow
+cast upon the deck by some unseen being's body. And that shadow was
+always hovering there. For not by night, even, had Fedallah ever
+certainly been known to slumber, or go below. He would stand still
+for hours: but never sat or leaned; his wan but wondrous eyes did
+plainly say--We two watchmen never rest.
+
+Nor, at any time, by night or day could the mariners now step upon
+the deck, unless Ahab was before them; either standing in his
+pivot-hole, or exactly pacing the planks between two undeviating
+limits,--the main-mast and the mizen; or else they saw him standing
+in the cabin-scuttle,--his living foot advanced upon the deck, as if
+to step; his hat slouched heavily over his eyes; so that however
+motionless he stood, however the days and nights were added on, that
+he had not swung in his hammock; yet hidden beneath that slouching
+hat, they could never tell unerringly whether, for all this, his eyes
+were really closed at times; or whether he was still intently
+scanning them; no matter, though he stood so in the scuttle for a
+whole hour on the stretch, and the unheeded night-damp gathered in
+beads of dew upon that stone-carved coat and hat. The clothes that
+the night had wet, the next day's sunshine dried upon him; and so,
+day after day, and night after night; he went no more beneath the
+planks; whatever he wanted from the cabin that thing he sent for.
+
+He ate in the same open air; that is, his two only meals,--breakfast
+and dinner: supper he never touched; nor reaped his beard; which
+darkly grew all gnarled, as unearthed roots of trees blown over,
+which still grow idly on at naked base, though perished in the upper
+verdure. But though his whole life was now become one watch on deck;
+and though the Parsee's mystic watch was without intermission as his
+own; yet these two never seemed to speak--one man to the
+other--unless at long intervals some passing unmomentous matter made
+it necessary. Though such a potent spell seemed secretly to join the
+twain; openly, and to the awe-struck crew, they seemed pole-like
+asunder. If by day they chanced to speak one word; by night, dumb
+men were both, so far as concerned the slightest verbal interchange.
+At times, for longest hours, without a single hail, they stood far
+parted in the starlight; Ahab in his scuttle, the Parsee by the
+mainmast; but still fixedly gazing upon each other; as if in the
+Parsee Ahab saw his forethrown shadow, in Ahab the Parsee his
+abandoned substance.
+
+And yet, somehow, did Ahab--in his own proper self, as daily, hourly,
+and every instant, commandingly revealed to his subordinates,--Ahab
+seemed an independent lord; the Parsee but his slave. Still again
+both seemed yoked together, and an unseen tyrant driving them; the
+lean shade siding the solid rib. For be this Parsee what he may, all
+rib and keel was solid Ahab.
+
+At the first faintest glimmering of the dawn, his iron voice was
+heard from aft,--"Man the mast-heads!"--and all through the day,
+till after sunset and after twilight, the same voice every hour, at
+the striking of the helmsman's bell, was heard--"What d'ye
+see?--sharp! sharp!"
+
+But when three or four days had slided by, after meeting the
+children-seeking Rachel; and no spout had yet been seen; the
+monomaniac old man seemed distrustful of his crew's fidelity; at
+least, of nearly all except the Pagan harpooneers; he seemed to
+doubt, even, whether Stubb and Flask might not willingly overlook the
+sight he sought. But if these suspicions were really his, he
+sagaciously refrained from verbally expressing them, however his
+actions might seem to hint them.
+
+"I will have the first sight of the whale myself,"--he said. "Aye!
+Ahab must have the doubloon! and with his own hands he rigged a nest
+of basketed bowlines; and sending a hand aloft, with a single sheaved
+block, to secure to the main-mast head, he received the two ends of
+the downward-reeved rope; and attaching one to his basket prepared a
+pin for the other end, in order to fasten it at the rail. This done,
+with that end yet in his hand and standing beside the pin, he looked
+round upon his crew, sweeping from one to the other; pausing his
+glance long upon Daggoo, Queequeg, Tashtego; but shunning Fedallah;
+and then settling his firm relying eye upon the chief mate,
+said,--"Take the rope, sir--I give it into thy hands, Starbuck."
+Then arranging his person in the basket, he gave the word for them to
+hoist him to his perch, Starbuck being the one who secured the rope
+at last; and afterwards stood near it. And thus, with one hand
+clinging round the royal mast, Ahab gazed abroad upon the sea for
+miles and miles,--ahead, astern, this side, and that,--within the
+wide expanded circle commanded at so great a height.
+
+When in working with his hands at some lofty almost isolated place in
+the rigging, which chances to afford no foothold, the sailor at sea
+is hoisted up to that spot, and sustained there by the rope; under
+these circumstances, its fastened end on deck is always given in
+strict charge to some one man who has the special watch of it.
+Because in such a wilderness of running rigging, whose various
+different relations aloft cannot always be infallibly discerned by
+what is seen of them at the deck; and when the deck-ends of these
+ropes are being every few minutes cast down from the fastenings, it
+would be but a natural fatality, if, unprovided with a constant
+watchman, the hoisted sailor should by some carelessness of the crew
+be cast adrift and fall all swooping to the sea. So Ahab's
+proceedings in this matter were not unusual; the only strange thing
+about them seemed to be, that Starbuck, almost the one only man who
+had ever ventured to oppose him with anything in the slightest degree
+approaching to decision--one of those too, whose faithfulness on the
+look-out he had seemed to doubt somewhat;--it was strange, that this
+was the very man he should select for his watchman; freely giving his
+whole life into such an otherwise distrusted person's hands.
+
+Now, the first time Ahab was perched aloft; ere he had been there ten
+minutes; one of those red-billed savage sea-hawks which so often fly
+incommodiously close round the manned mast-heads of whalemen in these
+latitudes; one of these birds came wheeling and screaming round his
+head in a maze of untrackably swift circlings. Then it darted a
+thousand feet straight up into the air; then spiralized downwards,
+and went eddying again round his head.
+
+But with his gaze fixed upon the dim and distant horizon, Ahab seemed
+not to mark this wild bird; nor, indeed, would any one else have
+marked it much, it being no uncommon circumstance; only now almost
+the least heedful eye seemed to see some sort of cunning meaning in
+almost every sight.
+
+"Your hat, your hat, sir!" suddenly cried the Sicilian seaman, who
+being posted at the mizen-mast-head, stood directly behind Ahab,
+though somewhat lower than his level, and with a deep gulf of air
+dividing them.
+
+But already the sable wing was before the old man's eyes; the long
+hooked bill at his head: with a scream, the black hawk darted away
+with his prize.
+
+An eagle flew thrice round Tarquin's head, removing his cap to
+replace it, and thereupon Tanaquil, his wife, declared that Tarquin
+would be king of Rome. But only by the replacing of the cap was that
+omen accounted good. Ahab's hat was never restored; the wild hawk
+flew on and on with it; far in advance of the prow: and at last
+disappeared; while from the point of that disappearance, a minute
+black spot was dimly discerned, falling from that vast height into
+the sea.
+
+
+
+CHAPTER 131
+
+The Pequod Meets The Delight.
+
+
+The intense Pequod sailed on; the rolling waves and days went by; the
+life-buoy-coffin still lightly swung; and another ship, most
+miserably misnamed the Delight, was descried. As she drew nigh, all
+eyes were fixed upon her broad beams, called shears, which, in some
+whaling-ships, cross the quarter-deck at the height of eight or nine
+feet; serving to carry the spare, unrigged, or disabled boats.
+
+Upon the stranger's shears were beheld the shattered, white ribs, and
+some few splintered planks, of what had once been a whale-boat; but
+you now saw through this wreck, as plainly as you see through the
+peeled, half-unhinged, and bleaching skeleton of a horse.
+
+"Hast seen the White Whale?"
+
+"Look!" replied the hollow-cheeked captain from his taffrail; and
+with his trumpet he pointed to the wreck.
+
+"Hast killed him?"
+
+"The harpoon is not yet forged that ever will do that," answered the
+other, sadly glancing upon a rounded hammock on the deck, whose
+gathered sides some noiseless sailors were busy in sewing together.
+
+"Not forged!" and snatching Perth's levelled iron from the crotch,
+Ahab held it out, exclaiming--"Look ye, Nantucketer; here in this
+hand I hold his death! Tempered in blood, and tempered by lightning
+are these barbs; and I swear to temper them triply in that hot place
+behind the fin, where the White Whale most feels his accursed life!"
+
+"Then God keep thee, old man--see'st thou that"--pointing to the
+hammock--"I bury but one of five stout men, who were alive only
+yesterday; but were dead ere night. Only THAT one I bury; the rest
+were buried before they died; you sail upon their tomb." Then
+turning to his crew--"Are ye ready there? place the plank then on the
+rail, and lift the body; so, then--Oh! God"--advancing towards the
+hammock with uplifted hands--"may the resurrection and the life--"
+
+"Brace forward! Up helm!" cried Ahab like lightning to his men.
+
+But the suddenly started Pequod was not quick enough to escape the
+sound of the splash that the corpse soon made as it struck the sea;
+not so quick, indeed, but that some of the flying bubbles might have
+sprinkled her hull with their ghostly baptism.
+
+As Ahab now glided from the dejected Delight, the strange life-buoy
+hanging at the Pequod's stern came into conspicuous relief.
+
+"Ha! yonder! look yonder, men!" cried a foreboding voice in her wake.
+"In vain, oh, ye strangers, ye fly our sad burial; ye but turn us
+your taffrail to show us your coffin!"
+
+
+
+CHAPTER 132
+
+The Symphony.
+
+
+It was a clear steel-blue day. The firmaments of air and sea were
+hardly separable in that all-pervading azure; only, the pensive air
+was transparently pure and soft, with a woman's look, and the robust
+and man-like sea heaved with long, strong, lingering swells, as
+Samson's chest in his sleep.
+
+Hither, and thither, on high, glided the snow-white wings of small,
+unspeckled birds; these were the gentle thoughts of the feminine air;
+but to and fro in the deeps, far down in the bottomless blue, rushed
+mighty leviathans, sword-fish, and sharks; and these were the strong,
+troubled, murderous thinkings of the masculine sea.
+
+But though thus contrasting within, the contrast was only in shades
+and shadows without; those two seemed one; it was only the sex, as it
+were, that distinguished them.
+
+Aloft, like a royal czar and king, the sun seemed giving this gentle
+air to this bold and rolling sea; even as bride to groom. And at the
+girdling line of the horizon, a soft and tremulous motion--most seen
+here at the Equator--denoted the fond, throbbing trust, the loving
+alarms, with which the poor bride gave her bosom away.
+
+Tied up and twisted; gnarled and knotted with wrinkles; haggardly
+firm and unyielding; his eyes glowing like coals, that still glow in
+the ashes of ruin; untottering Ahab stood forth in the clearness of
+the morn; lifting his splintered helmet of a brow to the fair girl's
+forehead of heaven.
+
+Oh, immortal infancy, and innocency of the azure! Invisible winged
+creatures that frolic all round us! Sweet childhood of air and sky!
+how oblivious were ye of old Ahab's close-coiled woe! But so have I
+seen little Miriam and Martha, laughing-eyed elves, heedlessly gambol
+around their old sire; sporting with the circle of singed locks which
+grew on the marge of that burnt-out crater of his brain.
+
+Slowly crossing the deck from the scuttle, Ahab leaned over the side
+and watched how his shadow in the water sank and sank to his gaze,
+the more and the more that he strove to pierce the profundity. But
+the lovely aromas in that enchanted air did at last seem to dispel,
+for a moment, the cankerous thing in his soul. That glad, happy air,
+that winsome sky, did at last stroke and caress him; the step-mother
+world, so long cruel--forbidding--now threw affectionate arms round
+his stubborn neck, and did seem to joyously sob over him, as if over
+one, that however wilful and erring, she could yet find it in her
+heart to save and to bless. From beneath his slouched hat Ahab
+dropped a tear into the sea; nor did all the Pacific contain such
+wealth as that one wee drop.
+
+Starbuck saw the old man; saw him, how he heavily leaned over the
+side; and he seemed to hear in his own true heart the measureless
+sobbing that stole out of the centre of the serenity around. Careful
+not to touch him, or be noticed by him, he yet drew near to him, and
+stood there.
+
+Ahab turned.
+
+"Starbuck!"
+
+"Sir."
+
+"Oh, Starbuck! it is a mild, mild wind, and a mild looking sky. On
+such a day--very much such a sweetness as this--I struck my first
+whale--a boy-harpooneer of eighteen! Forty--forty--forty years
+ago!--ago! Forty years of continual whaling! forty years of
+privation, and peril, and storm-time! forty years on the pitiless
+sea! for forty years has Ahab forsaken the peaceful land, for forty
+years to make war on the horrors of the deep! Aye and yes, Starbuck,
+out of those forty years I have not spent three ashore. When I think
+of this life I have led; the desolation of solitude it has been; the
+masoned, walled-town of a Captain's exclusiveness, which admits but
+small entrance to any sympathy from the green country without--oh,
+weariness! heaviness! Guinea-coast slavery of solitary
+command!--when I think of all this; only half-suspected, not so
+keenly known to me before--and how for forty years I have fed upon
+dry salted fare--fit emblem of the dry nourishment of my soil!--when
+the poorest landsman has had fresh fruit to his daily hand, and
+broken the world's fresh bread to my mouldy crusts--away, whole
+oceans away, from that young girl-wife I wedded past fifty, and
+sailed for Cape Horn the next day, leaving but one dent in my
+marriage pillow--wife? wife?--rather a widow with her husband alive!
+Aye, I widowed that poor girl when I married her, Starbuck; and
+then, the madness, the frenzy, the boiling blood and the smoking
+brow, with which, for a thousand lowerings old Ahab has furiously,
+foamingly chased his prey--more a demon than a man!--aye, aye! what a
+forty years' fool--fool--old fool, has old Ahab been! Why this
+strife of the chase? why weary, and palsy the arm at the oar, and the
+iron, and the lance? how the richer or better is Ahab now? Behold.
+Oh, Starbuck! is it not hard, that with this weary load I bear, one
+poor leg should have been snatched from under me? Here, brush this
+old hair aside; it blinds me, that I seem to weep. Locks so grey did
+never grow but from out some ashes! But do I look very old, so very,
+very old, Starbuck? I feel deadly faint, bowed, and humped, as
+though I were Adam, staggering beneath the piled centuries since
+Paradise. God! God! God!--crack my heart!--stave my
+brain!--mockery! mockery! bitter, biting mockery of grey hairs, have
+I lived enough joy to wear ye; and seem and feel thus intolerably
+old? Close! stand close to me, Starbuck; let me look into a human
+eye; it is better than to gaze into sea or sky; better than to gaze
+upon God. By the green land; by the bright hearth-stone! this is the
+magic glass, man; I see my wife and my child in thine eye. No, no;
+stay on board, on board!--lower not when I do; when branded Ahab
+gives chase to Moby Dick. That hazard shall not be thine. No, no!
+not with the far away home I see in that eye!"
+
+"Oh, my Captain! my Captain! noble soul! grand old heart, after all!
+why should any one give chase to that hated fish! Away with me! let
+us fly these deadly waters! let us home! Wife and child, too, are
+Starbuck's--wife and child of his brotherly, sisterly, play-fellow
+youth; even as thine, sir, are the wife and child of thy loving,
+longing, paternal old age! Away! let us away!--this instant let me
+alter the course! How cheerily, how hilariously, O my Captain, would
+we bowl on our way to see old Nantucket again! I think, sir, they
+have some such mild blue days, even as this, in Nantucket."
+
+"They have, they have. I have seen them--some summer days in the
+morning. About this time--yes, it is his noon nap now--the boy
+vivaciously wakes; sits up in bed; and his mother tells him of me, of
+cannibal old me; how I am abroad upon the deep, but will yet come
+back to dance him again."
+
+"'Tis my Mary, my Mary herself! She promised that my boy, every
+morning, should be carried to the hill to catch the first glimpse of
+his father's sail! Yes, yes! no more! it is done! we head for
+Nantucket! Come, my Captain, study out the course, and let us away!
+See, see! the boy's face from the window! the boy's hand on the
+hill!"
+
+But Ahab's glance was averted; like a blighted fruit tree he shook,
+and cast his last, cindered apple to the soil.
+
+"What is it, what nameless, inscrutable, unearthly thing is it; what
+cozening, hidden lord and master, and cruel, remorseless emperor
+commands me; that against all natural lovings and longings, I so keep
+pushing, and crowding, and jamming myself on all the time; recklessly
+making me ready to do what in my own proper, natural heart, I durst
+not so much as dare? Is Ahab, Ahab? Is it I, God, or who, that
+lifts this arm? But if the great sun move not of himself; but is as an
+errand-boy in heaven; nor one single star can revolve, but by some
+invisible power; how then can this one small heart beat; this one
+small brain think thoughts; unless God does that beating, does that
+thinking, does that living, and not I. By heaven, man, we are turned
+round and round in this world, like yonder windlass, and Fate is the
+handspike. And all the time, lo! that smiling sky, and this
+unsounded sea! Look! see yon Albicore! who put it into him to chase
+and fang that flying-fish? Where do murderers go, man! Who's to
+doom, when the judge himself is dragged to the bar? But it is a
+mild, mild wind, and a mild looking sky; and the air smells now, as
+if it blew from a far-away meadow; they have been making hay
+somewhere under the slopes of the Andes, Starbuck, and the mowers are
+sleeping among the new-mown hay. Sleeping? Aye, toil we how we may,
+we all sleep at last on the field. Sleep? Aye, and rust amid
+greenness; as last year's scythes flung down, and left in the half-cut
+swaths--Starbuck!"
+
+But blanched to a corpse's hue with despair, the Mate had stolen
+away.
+
+Ahab crossed the deck to gaze over on the other side; but started at
+two reflected, fixed eyes in the water there. Fedallah was
+motionlessly leaning over the same rail.
+
+
+
+CHAPTER 133
+
+The Chase--First Day.
+
+
+That night, in the mid-watch, when the old man--as his wont at
+intervals--stepped forth from the scuttle in which he leaned, and
+went to his pivot-hole, he suddenly thrust out his face fiercely,
+snuffing up the sea air as a sagacious ship's dog will, in drawing
+nigh to some barbarous isle. He declared that a whale must be near.
+Soon that peculiar odor, sometimes to a great distance given forth by
+the living sperm whale, was palpable to all the watch; nor was any
+mariner surprised when, after inspecting the compass, and then the
+dog-vane, and then ascertaining the precise bearing of the odor as
+nearly as possible, Ahab rapidly ordered the ship's course to be
+slightly altered, and the sail to be shortened.
+
+The acute policy dictating these movements was sufficiently
+vindicated at daybreak, by the sight of a long sleek on the sea
+directly and lengthwise ahead, smooth as oil, and resembling in the
+pleated watery wrinkles bordering it, the polished metallic-like
+marks of some swift tide-rip, at the mouth of a deep, rapid stream.
+
+"Man the mast-heads! Call all hands!"
+
+Thundering with the butts of three clubbed handspikes on the
+forecastle deck, Daggoo roused the sleepers with such judgment claps
+that they seemed to exhale from the scuttle, so instantaneously did
+they appear with their clothes in their hands.
+
+"What d'ye see?" cried Ahab, flattening his face to the sky.
+
+"Nothing, nothing sir!" was the sound hailing down in reply.
+
+"T'gallant sails!--stunsails! alow and aloft, and on both sides!"
+
+All sail being set, he now cast loose the life-line, reserved for
+swaying him to the main royal-mast head; and in a few moments they
+were hoisting him thither, when, while but two thirds of the way
+aloft, and while peering ahead through the horizontal vacancy between
+the main-top-sail and top-gallant-sail, he raised a gull-like cry in
+the air. "There she blows!--there she blows! A hump like a
+snow-hill! It is Moby Dick!"
+
+Fired by the cry which seemed simultaneously taken up by the three
+look-outs, the men on deck rushed to the rigging to behold the famous
+whale they had so long been pursuing. Ahab had now gained his final
+perch, some feet above the other look-outs, Tashtego standing just
+beneath him on the cap of the top-gallant-mast, so that the Indian's
+head was almost on a level with Ahab's heel. From this height the
+whale was now seen some mile or so ahead, at every roll of the sea
+revealing his high sparkling hump, and regularly jetting his silent
+spout into the air. To the credulous mariners it seemed the same
+silent spout they had so long ago beheld in the moonlit Atlantic and
+Indian Oceans.
+
+"And did none of ye see it before?" cried Ahab, hailing the perched
+men all around him.
+
+"I saw him almost that same instant, sir, that Captain Ahab did, and
+I cried out," said Tashtego.
+
+"Not the same instant; not the same--no, the doubloon is mine, Fate
+reserved the doubloon for me. I only; none of ye could have raised
+the White Whale first. There she blows!--there she blows!--there
+she blows! There again!--there again!" he cried, in long-drawn,
+lingering, methodic tones, attuned to the gradual prolongings of the
+whale's visible jets. "He's going to sound! In stunsails! Down
+top-gallant-sails! Stand by three boats. Mr. Starbuck, remember,
+stay on board, and keep the ship. Helm there! Luff, luff a point!
+So; steady, man, steady! There go flukes! No, no; only black water!
+All ready the boats there? Stand by, stand by! Lower me, Mr.
+Starbuck; lower, lower,--quick, quicker!" and he slid through the air
+to the deck.
+
+"He is heading straight to leeward, sir," cried Stubb, "right away
+from us; cannot have seen the ship yet."
+
+"Be dumb, man! Stand by the braces! Hard down the helm!--brace up!
+Shiver her!--shiver her!--So; well that! Boats, boats!"
+
+Soon all the boats but Starbuck's were dropped; all the boat-sails
+set--all the paddles plying; with rippling swiftness, shooting to
+leeward; and Ahab heading the onset. A pale, death-glimmer lit up
+Fedallah's sunken eyes; a hideous motion gnawed his mouth.
+
+Like noiseless nautilus shells, their light prows sped through the
+sea; but only slowly they neared the foe. As they neared him, the
+ocean grew still more smooth; seemed drawing a carpet over its waves;
+seemed a noon-meadow, so serenely it spread. At length the
+breathless hunter came so nigh his seemingly unsuspecting prey, that his
+entire dazzling hump was distinctly visible, sliding along the sea as
+if an isolated thing, and continually set in a revolving ring of
+finest, fleecy, greenish foam. He saw the vast, involved wrinkles of
+the slightly projecting head beyond. Before it, far out on the soft
+Turkish-rugged waters, went the glistening white shadow from his
+broad, milky forehead, a musical rippling playfully accompanying the
+shade; and behind, the blue waters interchangeably flowed over into
+the moving valley of his steady wake; and on either hand bright
+bubbles arose and danced by his side. But these were broken again by
+the light toes of hundreds of gay fowl softly feathering the sea,
+alternate with their fitful flight; and like to some flag-staff
+rising from the painted hull of an argosy, the tall but shattered
+pole of a recent lance projected from the white whale's back; and at
+intervals one of the cloud of soft-toed fowls hovering, and to and
+fro skimming like a canopy over the fish, silently perched and rocked
+on this pole, the long tail feathers streaming like pennons.
+
+A gentle joyousness--a mighty mildness of repose in swiftness,
+invested the gliding whale. Not the white bull Jupiter swimming away
+with ravished Europa clinging to his graceful horns; his lovely,
+leering eyes sideways intent upon the maid; with smooth bewitching
+fleetness, rippling straight for the nuptial bower in Crete; not
+Jove, not that great majesty Supreme! did surpass the glorified White
+Whale as he so divinely swam.
+
+On each soft side--coincident with the parted swell, that but once
+leaving him, then flowed so wide away--on each bright side, the whale
+shed off enticings. No wonder there had been some among the hunters
+who namelessly transported and allured by all this serenity, had
+ventured to assail it; but had fatally found that quietude but the
+vesture of tornadoes. Yet calm, enticing calm, oh, whale! thou
+glidest on, to all who for the first time eye thee, no matter how
+many in that same way thou may'st have bejuggled and destroyed
+before.
+
+And thus, through the serene tranquillities of the tropical sea,
+among waves whose hand-clappings were suspended by exceeding rapture,
+Moby Dick moved on, still withholding from sight the full terrors of
+his submerged trunk, entirely hiding the wrenched hideousness of his
+jaw. But soon the fore part of him slowly rose from the water; for
+an instant his whole marbleized body formed a high arch, like
+Virginia's Natural Bridge, and warningly waving his bannered flukes
+in the air, the grand god revealed himself, sounded, and went out of
+sight. Hoveringly halting, and dipping on the wing, the white
+sea-fowls longingly lingered over the agitated pool that he left.
+
+With oars apeak, and paddles down, the sheets of their sails adrift,
+the three boats now stilly floated, awaiting Moby Dick's
+reappearance.
+
+"An hour," said Ahab, standing rooted in his boat's stern; and he
+gazed beyond the whale's place, towards the dim blue spaces and wide
+wooing vacancies to leeward. It was only an instant; for again his
+eyes seemed whirling round in his head as he swept the watery circle.
+The breeze now freshened; the sea began to swell.
+
+"The birds!--the birds!" cried Tashtego.
+
+In long Indian file, as when herons take wing, the white birds were
+now all flying towards Ahab's boat; and when within a few yards began
+fluttering over the water there, wheeling round and round, with
+joyous, expectant cries. Their vision was keener than man's; Ahab
+could discover no sign in the sea. But suddenly as he peered down
+and down into its depths, he profoundly saw a white living spot no
+bigger than a white weasel, with wonderful celerity uprising, and
+magnifying as it rose, till it turned, and then there were plainly
+revealed two long crooked rows of white, glistening teeth, floating
+up from the undiscoverable bottom. It was Moby Dick's open mouth and
+scrolled jaw; his vast, shadowed bulk still half blending with the
+blue of the sea. The glittering mouth yawned beneath the boat like
+an open-doored marble tomb; and giving one sidelong sweep with his
+steering oar, Ahab whirled the craft aside from this tremendous
+apparition. Then, calling upon Fedallah to change places with him,
+went forward to the bows, and seizing Perth's harpoon, commanded his
+crew to grasp their oars and stand by to stern.
+
+Now, by reason of this timely spinning round the boat upon its axis,
+its bow, by anticipation, was made to face the whale's head while yet
+under water. But as if perceiving this stratagem, Moby Dick, with
+that malicious intelligence ascribed to him, sidelingly transplanted
+himself, as it were, in an instant, shooting his pleated head
+lengthwise beneath the boat.
+
+Through and through; through every plank and each rib, it thrilled
+for an instant, the whale obliquely lying on his back, in the manner
+of a biting shark, slowly and feelingly taking its bows full within
+his mouth, so that the long, narrow, scrolled lower jaw curled high
+up into the open air, and one of the teeth caught in a row-lock. The
+bluish pearl-white of the inside of the jaw was within six inches of
+Ahab's head, and reached higher than that. In this attitude the
+White Whale now shook the slight cedar as a mildly cruel cat her
+mouse. With unastonished eyes Fedallah gazed, and crossed his arms;
+but the tiger-yellow crew were tumbling over each other's heads to
+gain the uttermost stern.
+
+And now, while both elastic gunwales were springing in and out, as
+the whale dallied with the doomed craft in this devilish way; and
+from his body being submerged beneath the boat, he could not be
+darted at from the bows, for the bows were almost inside of him, as
+it were; and while the other boats involuntarily paused, as before a
+quick crisis impossible to withstand, then it was that monomaniac
+Ahab, furious with this tantalizing vicinity of his foe, which placed
+him all alive and helpless in the very jaws he hated; frenzied with
+all this, he seized the long bone with his naked hands, and wildly
+strove to wrench it from its gripe. As now he thus vainly strove,
+the jaw slipped from him; the frail gunwales bent in, collapsed, and
+snapped, as both jaws, like an enormous shears, sliding further aft,
+bit the craft completely in twain, and locked themselves fast again
+in the sea, midway between the two floating wrecks. These floated
+aside, the broken ends drooping, the crew at the stern-wreck clinging
+to the gunwales, and striving to hold fast to the oars to lash them
+across.
+
+At that preluding moment, ere the boat was yet snapped, Ahab, the
+first to perceive the whale's intent, by the crafty upraising of his
+head, a movement that loosed his hold for the time; at that moment
+his hand had made one final effort to push the boat out of the bite.
+But only slipping further into the whale's mouth, and tilting over
+sideways as it slipped, the boat had shaken off his hold on the jaw;
+spilled him out of it, as he leaned to the push; and so he fell
+flat-faced upon the sea.
+
+Ripplingly withdrawing from his prey, Moby Dick now lay at a little
+distance, vertically thrusting his oblong white head up and down in
+the billows; and at the same time slowly revolving his whole spindled
+body; so that when his vast wrinkled forehead rose--some twenty or
+more feet out of the water--the now rising swells, with all their
+confluent waves, dazzlingly broke against it; vindictively tossing
+their shivered spray still higher into the air.* So, in a gale, the
+but half baffled Channel billows only recoil from the base of the
+Eddystone, triumphantly to overleap its summit with their scud.
+
+
+*This motion is peculiar to the sperm whale. It receives its
+designation (pitchpoling) from its being likened to that preliminary
+up-and-down poise of the whale-lance, in the exercise called
+pitchpoling, previously described. By this motion the whale must
+best and most comprehensively view whatever objects may be encircling
+him.
+
+
+But soon resuming his horizontal attitude, Moby Dick swam swiftly
+round and round the wrecked crew; sideways churning the water in his
+vengeful wake, as if lashing himself up to still another and more
+deadly assault. The sight of the splintered boat seemed to madden
+him, as the blood of grapes and mulberries cast before Antiochus's
+elephants in the book of Maccabees. Meanwhile Ahab half smothered in
+the foam of the whale's insolent tail, and too much of a cripple to
+swim,--though he could still keep afloat, even in the heart of such a
+whirlpool as that; helpless Ahab's head was seen, like a tossed
+bubble which the least chance shock might burst. From the boat's
+fragmentary stern, Fedallah incuriously and mildly eyed him; the
+clinging crew, at the other drifting end, could not succor him; more
+than enough was it for them to look to themselves. For so
+revolvingly appalling was the White Whale's aspect, and so
+planetarily swift the ever-contracting circles he made, that he
+seemed horizontally swooping upon them. And though the other boats,
+unharmed, still hovered hard by; still they dared not pull into the
+eddy to strike, lest that should be the signal for the instant
+destruction of the jeopardized castaways, Ahab and all; nor in that
+case could they themselves hope to escape. With straining eyes,
+then, they remained on the outer edge of the direful zone, whose
+centre had now become the old man's head.
+
+Meantime, from the beginning all this had been descried from the
+ship's mast heads; and squaring her yards, she had borne down upon
+the scene; and was now so nigh, that Ahab in the water hailed
+her!--"Sail on the"--but that moment a breaking sea dashed on him
+from Moby Dick, and whelmed him for the time. But struggling out of
+it again, and chancing to rise on a towering crest, he
+shouted,--"Sail on the whale!--Drive him off!"
+
+The Pequod's prows were pointed; and breaking up the charmed circle,
+she effectually parted the white whale from his victim. As he
+sullenly swam off, the boats flew to the rescue.
+
+Dragged into Stubb's boat with blood-shot, blinded eyes, the white
+brine caking in his wrinkles; the long tension of Ahab's bodily
+strength did crack, and helplessly he yielded to his body's doom: for
+a time, lying all crushed in the bottom of Stubb's boat, like one
+trodden under foot of herds of elephants. Far inland, nameless wails
+came from him, as desolate sounds from out ravines.
+
+But this intensity of his physical prostration did but so much the
+more abbreviate it. In an instant's compass, great hearts sometimes
+condense to one deep pang, the sum total of those shallow pains
+kindly diffused through feebler men's whole lives. And so, such
+hearts, though summary in each one suffering; still, if the gods
+decree it, in their life-time aggregate a whole age of woe, wholly
+made up of instantaneous intensities; for even in their pointless
+centres, those noble natures contain the entire circumferences of
+inferior souls.
+
+"The harpoon," said Ahab, half way rising, and draggingly leaning on
+one bended arm--"is it safe?"
+
+"Aye, sir, for it was not darted; this is it," said Stubb, showing
+it.
+
+"Lay it before me;--any missing men?"
+
+"One, two, three, four, five;--there were five oars, sir, and here
+are five men."
+
+"That's good.--Help me, man; I wish to stand. So, so, I see him!
+there! there! going to leeward still; what a leaping spout!--Hands
+off from me! The eternal sap runs up in Ahab's bones again! Set the
+sail; out oars; the helm!"
+
+It is often the case that when a boat is stove, its crew, being
+picked up by another boat, help to work that second boat; and the
+chase is thus continued with what is called double-banked oars. It
+was thus now. But the added power of the boat did not equal the
+added power of the whale, for he seemed to have treble-banked his
+every fin; swimming with a velocity which plainly showed, that if
+now, under these circumstances, pushed on, the chase would prove an
+indefinitely prolonged, if not a hopeless one; nor could any crew
+endure for so long a period, such an unintermitted, intense straining
+at the oar; a thing barely tolerable only in some one brief
+vicissitude. The ship itself, then, as it sometimes happens, offered
+the most promising intermediate means of overtaking the chase.
+Accordingly, the boats now made for her, and were soon swayed up to
+their cranes--the two parts of the wrecked boat having been
+previously secured by her--and then hoisting everything to her side,
+and stacking her canvas high up, and sideways outstretching it with
+stun-sails, like the double-jointed wings of an albatross; the Pequod
+bore down in the leeward wake of Moby-Dick. At the well known,
+methodic intervals, the whale's glittering spout was regularly
+announced from the manned mast-heads; and when he would be reported
+as just gone down, Ahab would take the time, and then pacing the
+deck, binnacle-watch in hand, so soon as the last second of the
+allotted hour expired, his voice was heard.--"Whose is the doubloon
+now? D'ye see him?" and if the reply was, No, sir! straightway he
+commanded them to lift him to his perch. In this way the day wore
+on; Ahab, now aloft and motionless; anon, unrestingly pacing the
+planks.
+
+As he was thus walking, uttering no sound, except to hail the men
+aloft, or to bid them hoist a sail still higher, or to spread one to
+a still greater breadth--thus to and fro pacing, beneath his slouched
+hat, at every turn he passed his own wrecked boat, which had been
+dropped upon the quarter-deck, and lay there reversed; broken bow to
+shattered stern. At last he paused before it; and as in an already
+over-clouded sky fresh troops of clouds will sometimes sail across,
+so over the old man's face there now stole some such added gloom as
+this.
+
+Stubb saw him pause; and perhaps intending, not vainly, though, to
+evince his own unabated fortitude, and thus keep up a valiant place
+in his Captain's mind, he advanced, and eyeing the wreck
+exclaimed--"The thistle the ass refused; it pricked his mouth too
+keenly, sir; ha! ha!"
+
+"What soulless thing is this that laughs before a wreck? Man, man!
+did I not know thee brave as fearless fire (and as mechanical) I
+could swear thou wert a poltroon. Groan nor laugh should be heard
+before a wreck."
+
+"Aye, sir," said Starbuck drawing near, "'tis a solemn sight; an
+omen, and an ill one."
+
+"Omen? omen?--the dictionary! If the gods think to speak outright to
+man, they will honourably speak outright; not shake their heads, and
+give an old wives' darkling hint.--Begone! Ye two are the opposite
+poles of one thing; Starbuck is Stubb reversed, and Stubb is
+Starbuck; and ye two are all mankind; and Ahab stands alone among the
+millions of the peopled earth, nor gods nor men his neighbors! Cold,
+cold--I shiver!--How now? Aloft there! D'ye see him? Sing out for
+every spout, though he spout ten times a second!"
+
+The day was nearly done; only the hem of his golden robe was
+rustling. Soon, it was almost dark, but the look-out men still
+remained unset.
+
+"Can't see the spout now, sir;--too dark"--cried a voice from the
+air.
+
+"How heading when last seen?"
+
+"As before, sir,--straight to leeward."
+
+"Good! he will travel slower now 'tis night. Down royals and
+top-gallant stun-sails, Mr. Starbuck. We must not run over him
+before morning; he's making a passage now, and may heave-to a while.
+Helm there! keep her full before the wind!--Aloft! come down!--Mr.
+Stubb, send a fresh hand to the fore-mast head, and see it manned
+till morning."--Then advancing towards the doubloon in the
+main-mast--"Men, this gold is mine, for I earned it; but I shall let
+it abide here till the White Whale is dead; and then, whosoever of ye
+first raises him, upon the day he shall be killed, this gold is that
+man's; and if on that day I shall again raise him, then, ten times
+its sum shall be divided among all of ye! Away now!--the deck is
+thine, sir!"
+
+And so saying, he placed himself half way within the scuttle, and
+slouching his hat, stood there till dawn, except when at intervals
+rousing himself to see how the night wore on.
+
+
+
+CHAPTER 134
+
+The Chase--Second Day.
+
+
+At day-break, the three mast-heads were punctually manned afresh.
+
+"D'ye see him?" cried Ahab after allowing a little space for the
+light to spread.
+
+"See nothing, sir."
+
+"Turn up all hands and make sail! he travels faster than I thought
+for;--the top-gallant sails!--aye, they should have been kept on her
+all night. But no matter--'tis but resting for the rush."
+
+Here be it said, that this pertinacious pursuit of one particular
+whale, continued through day into night, and through night into day,
+is a thing by no means unprecedented in the South sea fishery. For
+such is the wonderful skill, prescience of experience, and invincible
+confidence acquired by some great natural geniuses among the
+Nantucket commanders; that from the simple observation of a whale
+when last descried, they will, under certain given circumstances,
+pretty accurately foretell both the direction in which he will
+continue to swim for a time, while out of sight, as well as his
+probable rate of progression during that period. And, in these
+cases, somewhat as a pilot, when about losing sight of a coast, whose
+general trending he well knows, and which he desires shortly to
+return to again, but at some further point; like as this pilot stands
+by his compass, and takes the precise bearing of the cape at present
+visible, in order the more certainly to hit aright the remote, unseen
+headland, eventually to be visited: so does the fisherman, at his
+compass, with the whale; for after being chased, and diligently
+marked, through several hours of daylight, then, when night obscures
+the fish, the creature's future wake through the darkness is almost
+as established to the sagacious mind of the hunter, as the pilot's
+coast is to him. So that to this hunter's wondrous skill, the
+proverbial evanescence of a thing writ in water, a wake, is to all
+desired purposes well nigh as reliable as the steadfast land. And as
+the mighty iron Leviathan of the modern railway is so familiarly
+known in its every pace, that, with watches in their hands, men time
+his rate as doctors that of a baby's pulse; and lightly say of it,
+the up train or the down train will reach such or such a spot, at
+such or such an hour; even so, almost, there are occasions when these
+Nantucketers time that other Leviathan of the deep, according to the
+observed humor of his speed; and say to themselves, so many hours
+hence this whale will have gone two hundred miles, will have about
+reached this or that degree of latitude or longitude. But to render
+this acuteness at all successful in the end, the wind and the sea
+must be the whaleman's allies; for of what present avail to the
+becalmed or windbound mariner is the skill that assures him he is
+exactly ninety-three leagues and a quarter from his port? Inferable
+from these statements, are many collateral subtile matters touching
+the chase of whales.
+
+The ship tore on; leaving such a furrow in the sea as when a
+cannon-ball, missent, becomes a plough-share and turns up the level
+field.
+
+"By salt and hemp!" cried Stubb, "but this swift motion of the deck
+creeps up one's legs and tingles at the heart. This ship and I are
+two brave fellows!--Ha, ha! Some one take me up, and launch me,
+spine-wise, on the sea,--for by live-oaks! my spine's a keel. Ha,
+ha! we go the gait that leaves no dust behind!"
+
+"There she blows--she blows!--she blows!--right ahead!" was now the
+mast-head cry.
+
+"Aye, aye!" cried Stubb, "I knew it--ye can't escape--blow on and
+split your spout, O whale! the mad fiend himself is after ye! blow
+your trump--blister your lungs!--Ahab will dam off your blood, as a
+miller shuts his watergate upon the stream!"
+
+And Stubb did but speak out for well nigh all that crew. The
+frenzies of the chase had by this time worked them bubblingly up,
+like old wine worked anew. Whatever pale fears and forebodings some
+of them might have felt before; these were not only now kept out of
+sight through the growing awe of Ahab, but they were broken up, and
+on all sides routed, as timid prairie hares that scatter before the
+bounding bison. The hand of Fate had snatched all their souls; and
+by the stirring perils of the previous day; the rack of the past
+night's suspense; the fixed, unfearing, blind, reckless way in which
+their wild craft went plunging towards its flying mark; by all these
+things, their hearts were bowled along. The wind that made great
+bellies of their sails, and rushed the vessel on by arms invisible as
+irresistible; this seemed the symbol of that unseen agency which so
+enslaved them to the race.
+
+They were one man, not thirty. For as the one ship that held them
+all; though it was put together of all contrasting things--oak, and
+maple, and pine wood; iron, and pitch, and hemp--yet all these ran
+into each other in the one concrete hull, which shot on its way, both
+balanced and directed by the long central keel; even so, all the
+individualities of the crew, this man's valor, that man's fear; guilt
+and guiltiness, all varieties were welded into oneness, and were all
+directed to that fatal goal which Ahab their one lord and keel did
+point to.
+
+The rigging lived. The mast-heads, like the tops of tall palms, were
+outspreadingly tufted with arms and legs. Clinging to a spar with
+one hand, some reached forth the other with impatient wavings;
+others, shading their eyes from the vivid sunlight, sat far out on
+the rocking yards; all the spars in full bearing of mortals, ready
+and ripe for their fate. Ah! how they still strove through that
+infinite blueness to seek out the thing that might destroy them!
+
+"Why sing ye not out for him, if ye see him?" cried Ahab, when, after
+the lapse of some minutes since the first cry, no more had been
+heard. "Sway me up, men; ye have been deceived; not Moby Dick casts
+one odd jet that way, and then disappears."
+
+It was even so; in their headlong eagerness, the men had mistaken
+some other thing for the whale-spout, as the event itself soon
+proved; for hardly had Ahab reached his perch; hardly was the rope
+belayed to its pin on deck, when he struck the key-note to an
+orchestra, that made the air vibrate as with the combined discharges
+of rifles. The triumphant halloo of thirty buckskin lungs was heard,
+as--much nearer to the ship than the place of the imaginary jet, less
+than a mile ahead--Moby Dick bodily burst into view! For not by any
+calm and indolent spoutings; not by the peaceable gush of that mystic
+fountain in his head, did the White Whale now reveal his vicinity;
+but by the far more wondrous phenomenon of breaching. Rising with
+his utmost velocity from the furthest depths, the Sperm Whale thus
+booms his entire bulk into the pure element of air, and piling up a
+mountain of dazzling foam, shows his place to the distance of seven
+miles and more. In those moments, the torn, enraged waves he shakes
+off, seem his mane; in some cases, this breaching is his act of
+defiance.
+
+"There she breaches! there she breaches!" was the cry, as in his
+immeasurable bravadoes the White Whale tossed himself salmon-like to
+Heaven. So suddenly seen in the blue plain of the sea, and relieved
+against the still bluer margin of the sky, the spray that he raised,
+for the moment, intolerably glittered and glared like a glacier; and
+stood there gradually fading and fading away from its first sparkling
+intensity, to the dim mistiness of an advancing shower in a vale.
+
+"Aye, breach your last to the sun, Moby Dick!" cried Ahab, "thy hour
+and thy harpoon are at hand!--Down! down all of ye, but one man at
+the fore. The boats!--stand by!"
+
+Unmindful of the tedious rope-ladders of the shrouds, the men, like
+shooting stars, slid to the deck, by the isolated backstays and
+halyards; while Ahab, less dartingly, but still rapidly was dropped
+from his perch.
+
+"Lower away," he cried, so soon as he had reached his boat--a spare
+one, rigged the afternoon previous. "Mr. Starbuck, the ship is
+thine--keep away from the boats, but keep near them. Lower, all!"
+
+As if to strike a quick terror into them, by this time being the
+first assailant himself, Moby Dick had turned, and was now coming for
+the three crews. Ahab's boat was central; and cheering his men, he
+told them he would take the whale head-and-head,--that is, pull
+straight up to his forehead,--a not uncommon thing; for when within a
+certain limit, such a course excludes the coming onset from the
+whale's sidelong vision. But ere that close limit was gained, and
+while yet all three boats were plain as the ship's three masts to his
+eye; the White Whale churning himself into furious speed, almost in
+an instant as it were, rushing among the boats with open jaws, and a
+lashing tail, offered appalling battle on every side; and heedless of
+the irons darted at him from every boat, seemed only intent on
+annihilating each separate plank of which those boats were made. But
+skilfully manoeuvred, incessantly wheeling like trained chargers in
+the field; the boats for a while eluded him; though, at times, but by
+a plank's breadth; while all the time, Ahab's unearthly slogan tore
+every other cry but his to shreds.
+
+But at last in his untraceable evolutions, the White Whale so crossed
+and recrossed, and in a thousand ways entangled the slack of the
+three lines now fast to him, that they foreshortened, and, of
+themselves, warped the devoted boats towards the planted irons in
+him; though now for a moment the whale drew aside a little, as if to
+rally for a more tremendous charge. Seizing that opportunity, Ahab
+first paid out more line: and then was rapidly hauling and jerking
+in upon it again--hoping that way to disencumber it of some
+snarls--when lo!--a sight more savage than the embattled teeth of
+sharks!
+
+Caught and twisted--corkscrewed in the mazes of the line, loose
+harpoons and lances, with all their bristling barbs and points, came
+flashing and dripping up to the chocks in the bows of Ahab's boat.
+Only one thing could be done. Seizing the boat-knife, he critically
+reached within--through--and then, without--the rays of steel;
+dragged in the line beyond, passed it, inboard, to the bowsman, and
+then, twice sundering the rope near the chocks--dropped the
+intercepted fagot of steel into the sea; and was all fast again.
+That instant, the White Whale made a sudden rush among the remaining
+tangles of the other lines; by so doing, irresistibly dragged the
+more involved boats of Stubb and Flask towards his flukes; dashed
+them together like two rolling husks on a surf-beaten beach, and
+then, diving down into the sea, disappeared in a boiling maelstrom,
+in which, for a space, the odorous cedar chips of the wrecks danced
+round and round, like the grated nutmeg in a swiftly stirred bowl of
+punch.
+
+While the two crews were yet circling in the waters, reaching out
+after the revolving line-tubs, oars, and other floating furniture,
+while aslope little Flask bobbed up and down like an empty vial,
+twitching his legs upwards to escape the dreaded jaws of sharks; and
+Stubb was lustily singing out for some one to ladle him up; and while
+the old man's line--now parting--admitted of his pulling into the
+creamy pool to rescue whom he could;--in that wild simultaneousness
+of a thousand concreted perils,--Ahab's yet unstricken boat seemed
+drawn up towards Heaven by invisible wires,--as, arrow-like, shooting
+perpendicularly from the sea, the White Whale dashed his broad
+forehead against its bottom, and sent it, turning over and over, into
+the air; till it fell again--gunwale downwards--and Ahab and his men
+struggled out from under it, like seals from a sea-side cave.
+
+The first uprising momentum of the whale--modifying its direction as
+he struck the surface--involuntarily launched him along it, to a
+little distance from the centre of the destruction he had made; and
+with his back to it, he now lay for a moment slowly feeling with his
+flukes from side to side; and whenever a stray oar, bit of plank, the
+least chip or crumb of the boats touched his skin, his tail swiftly
+drew back, and came sideways smiting the sea. But soon, as if
+satisfied that his work for that time was done, he pushed his pleated
+forehead through the ocean, and trailing after him the intertangled
+lines, continued his leeward way at a traveller's methodic pace.
+
+As before, the attentive ship having descried the whole fight, again
+came bearing down to the rescue, and dropping a boat, picked up the
+floating mariners, tubs, oars, and whatever else could be caught at,
+and safely landed them on her decks. Some sprained shoulders,
+wrists, and ankles; livid contusions; wrenched harpoons and lances;
+inextricable intricacies of rope; shattered oars and planks; all
+these were there; but no fatal or even serious ill seemed to have
+befallen any one. As with Fedallah the day before, so Ahab was now
+found grimly clinging to his boat's broken half, which afforded a
+comparatively easy float; nor did it so exhaust him as the previous
+day's mishap.
+
+But when he was helped to the deck, all eyes were fastened upon him;
+as instead of standing by himself he still half-hung upon the
+shoulder of Starbuck, who had thus far been the foremost to assist
+him. His ivory leg had been snapped off, leaving but one short sharp
+splinter.
+
+"Aye, aye, Starbuck, 'tis sweet to lean sometimes, be the leaner who
+he will; and would old Ahab had leaned oftener than he has."
+
+"The ferrule has not stood, sir," said the carpenter, now coming up;
+"I put good work into that leg."
+
+"But no bones broken, sir, I hope," said Stubb with true concern.
+
+"Aye! and all splintered to pieces, Stubb!--d'ye see it.--But even
+with a broken bone, old Ahab is untouched; and I account no living
+bone of mine one jot more me, than this dead one that's lost. Nor
+white whale, nor man, nor fiend, can so much as graze old Ahab in his
+own proper and inaccessible being. Can any lead touch yonder floor,
+any mast scrape yonder roof?--Aloft there! which way?"
+
+"Dead to leeward, sir."
+
+"Up helm, then; pile on the sail again, ship keepers! down the rest
+of the spare boats and rig them--Mr. Starbuck away, and muster the
+boat's crews."
+
+"Let me first help thee towards the bulwarks, sir."
+
+"Oh, oh, oh! how this splinter gores me now! Accursed fate! that the
+unconquerable captain in the soul should have such a craven mate!"
+
+"Sir?"
+
+"My body, man, not thee. Give me something for a cane--there, that
+shivered lance will do. Muster the men. Surely I have not seen him
+yet. By heaven it cannot be!--missing?--quick! call them all."
+
+The old man's hinted thought was true. Upon mustering the company,
+the Parsee was not there.
+
+"The Parsee!" cried Stubb--"he must have been caught in--"
+
+"The black vomit wrench thee!--run all of ye above, alow, cabin,
+forecastle--find him--not gone--not gone!"
+
+But quickly they returned to him with the tidings that the Parsee was
+nowhere to be found.
+
+"Aye, sir," said Stubb--"caught among the tangles of your line--I
+thought I saw him dragging under."
+
+"MY line! MY line? Gone?--gone? What means that little word?--What
+death-knell rings in it, that old Ahab shakes as if he were the
+belfry. The harpoon, too!--toss over the litter there,--d'ye see
+it?--the forged iron, men, the white whale's--no, no, no,--blistered
+fool! this hand did dart it!--'tis in the fish!--Aloft there! Keep
+him nailed--Quick!--all hands to the rigging of the boats--collect
+the oars--harpooneers! the irons, the irons!--hoist the royals higher--a
+pull on all the sheets!--helm there! steady, steady for your life!
+I'll ten times girdle the unmeasured globe; yea and dive straight
+through it, but I'll slay him yet!
+
+"Great God! but for one single instant show thyself," cried Starbuck;
+"never, never wilt thou capture him, old man--In Jesus' name no more
+of this, that's worse than devil's madness. Two days chased; twice
+stove to splinters; thy very leg once more snatched from under thee;
+thy evil shadow gone--all good angels mobbing thee with warnings:--
+what more wouldst thou have?--Shall we keep chasing this murderous
+fish till he swamps the last man? Shall we be dragged by him to the
+bottom of the sea? Shall we be towed by him to the infernal world?
+Oh, oh,--Impiety and blasphemy to hunt him more!"
+
+"Starbuck, of late I've felt strangely moved to thee; ever since that
+hour we both saw--thou know'st what, in one another's eyes. But in
+this matter of the whale, be the front of thy face to me as the palm
+of this hand--a lipless, unfeatured blank. Ahab is for ever Ahab,
+man. This whole act's immutably decreed. 'Twas rehearsed by thee
+and me a billion years before this ocean rolled. Fool! I am the
+Fates' lieutenant; I act under orders. Look thou, underling! that
+thou obeyest mine.--Stand round me, men. Ye see an old man cut down
+to the stump; leaning on a shivered lance; propped up on a lonely
+foot. 'Tis Ahab--his body's part; but Ahab's soul's a centipede,
+that moves upon a hundred legs. I feel strained, half stranded, as
+ropes that tow dismasted frigates in a gale; and I may look so. But
+ere I break, yell hear me crack; and till ye hear THAT, know that
+Ahab's hawser tows his purpose yet. Believe ye, men, in the things
+called omens? Then laugh aloud, and cry encore! For ere they drown,
+drowning things will twice rise to the surface; then rise again, to
+sink for evermore. So with Moby Dick--two days he's floated--tomorrow
+will be the third. Aye, men, he'll rise once more,--but only to
+spout his last! D'ye feel brave men, brave?"
+
+"As fearless fire," cried Stubb.
+
+"And as mechanical," muttered Ahab. Then as the men went forward, he
+muttered on: "The things called omens! And yesterday I talked the
+same to Starbuck there, concerning my broken boat. Oh! how valiantly
+I seek to drive out of others' hearts what's clinched so fast in
+mine!--The Parsee--the Parsee!--gone, gone? and he was to go
+before:--but still was to be seen again ere I could perish--How's
+that?--There's a riddle now might baffle all the lawyers backed by
+the ghosts of the whole line of judges:--like a hawk's beak it pecks
+my brain. I'LL, I'LL solve it, though!"
+
+When dusk descended, the whale was still in sight to leeward.
+
+So once more the sail was shortened, and everything passed nearly as
+on the previous night; only, the sound of hammers, and the hum of the
+grindstone was heard till nearly daylight, as the men toiled by
+lanterns in the complete and careful rigging of the spare boats and
+sharpening their fresh weapons for the morrow. Meantime, of the
+broken keel of Ahab's wrecked craft the carpenter made him another
+leg; while still as on the night before, slouched Ahab stood fixed
+within his scuttle; his hid, heliotrope glance anticipatingly gone
+backward on its dial; sat due eastward for the earliest sun.
+
+
+
+CHAPTER 135
+
+The Chase.--Third Day.
+
+
+The morning of the third day dawned fair and fresh, and once more the
+solitary night-man at the fore-mast-head was relieved by crowds of
+the daylight look-outs, who dotted every mast and almost every spar.
+
+"D'ye see him?" cried Ahab; but the whale was not yet in sight.
+
+"In his infallible wake, though; but follow that wake, that's all.
+Helm there; steady, as thou goest, and hast been going. What a
+lovely day again! were it a new-made world, and made for a
+summer-house to the angels, and this morning the first of its
+throwing open to them, a fairer day could not dawn upon that world.
+Here's food for thought, had Ahab time to think; but Ahab never
+thinks; he only feels, feels, feels; THAT'S tingling enough for
+mortal man! to think's audacity. God only has that right and
+privilege. Thinking is, or ought to be, a coolness and a calmness;
+and our poor hearts throb, and our poor brains beat too much for
+that. And yet, I've sometimes thought my brain was very calm--frozen
+calm, this old skull cracks so, like a glass in which the contents
+turned to ice, and shiver it. And still this hair is growing now;
+this moment growing, and heat must breed it; but no, it's like that
+sort of common grass that will grow anywhere, between the earthy
+clefts of Greenland ice or in Vesuvius lava. How the wild winds blow
+it; they whip it about me as the torn shreds of split sails lash the
+tossed ship they cling to. A vile wind that has no doubt blown ere
+this through prison corridors and cells, and wards of hospitals, and
+ventilated them, and now comes blowing hither as innocent as fleeces.
+Out upon it!--it's tainted. Were I the wind, I'd blow no more on
+such a wicked, miserable world. I'd crawl somewhere to a cave, and
+slink there. And yet, 'tis a noble and heroic thing, the wind! who
+ever conquered it? In every fight it has the last and bitterest
+blow. Run tilting at it, and you but run through it. Ha! a coward
+wind that strikes stark naked men, but will not stand to receive a
+single blow. Even Ahab is a braver thing--a nobler thing than THAT.
+Would now the wind but had a body; but all the things that most
+exasperate and outrage mortal man, all these things are bodiless, but
+only bodiless as objects, not as agents. There's a most special, a
+most cunning, oh, a most malicious difference! And yet, I say again,
+and swear it now, that there's something all glorious and gracious in
+the wind. These warm Trade Winds, at least, that in the clear
+heavens blow straight on, in strong and steadfast, vigorous mildness;
+and veer not from their mark, however the baser currents of the sea
+may turn and tack, and mightiest Mississippies of the land swift and
+swerve about, uncertain where to go at last. And by the eternal
+Poles! these same Trades that so directly blow my good ship on; these
+Trades, or something like them--something so unchangeable, and full
+as strong, blow my keeled soul along! To it! Aloft there! What
+d'ye see?"
+
+"Nothing, sir."
+
+"Nothing! and noon at hand! The doubloon goes a-begging! See the
+sun! Aye, aye, it must be so. I've oversailed him. How, got the
+start? Aye, he's chasing ME now; not I, HIM--that's bad; I might
+have known it, too. Fool! the lines--the harpoons he's towing. Aye,
+aye, I have run him by last night. About! about! Come down, all of
+ye, but the regular look outs! Man the braces!"
+
+Steering as she had done, the wind had been somewhat on the Pequod's
+quarter, so that now being pointed in the reverse direction, the
+braced ship sailed hard upon the breeze as she rechurned the cream in
+her own white wake.
+
+"Against the wind he now steers for the open jaw," murmured Starbuck
+to himself, as he coiled the new-hauled main-brace upon the rail.
+"God keep us, but already my bones feel damp within me, and from the
+inside wet my flesh. I misdoubt me that I disobey my God in obeying
+him!"
+
+"Stand by to sway me up!" cried Ahab, advancing to the hempen basket.
+"We should meet him soon."
+
+"Aye, aye, sir," and straightway Starbuck did Ahab's bidding, and
+once more Ahab swung on high.
+
+A whole hour now passed; gold-beaten out to ages. Time itself now
+held long breaths with keen suspense. But at last, some three points
+off the weather bow, Ahab descried the spout again, and instantly
+from the three mast-heads three shrieks went up as if the tongues of
+fire had voiced it.
+
+"Forehead to forehead I meet thee, this third time, Moby Dick! On
+deck there!--brace sharper up; crowd her into the wind's eye. He's
+too far off to lower yet, Mr. Starbuck. The sails shake! Stand over
+that helmsman with a top-maul! So, so; he travels fast, and I must
+down. But let me have one more good round look aloft here at the
+sea; there's time for that. An old, old sight, and yet somehow so
+young; aye, and not changed a wink since I first saw it, a boy, from
+the sand-hills of Nantucket! The same!--the same!--the same to Noah
+as to me. There's a soft shower to leeward. Such lovely
+leewardings! They must lead somewhere--to something else than common
+land, more palmy than the palms. Leeward! the white whale goes that
+way; look to windward, then; the better if the bitterer quarter. But
+good bye, good bye, old mast-head! What's this?--green? aye, tiny
+mosses in these warped cracks. No such green weather stains on
+Ahab's head! There's the difference now between man's old age and
+matter's. But aye, old mast, we both grow old together; sound in our
+hulls, though, are we not, my ship? Aye, minus a leg, that's all.
+By heaven this dead wood has the better of my live flesh every way.
+I can't compare with it; and I've known some ships made of dead trees
+outlast the lives of men made of the most vital stuff of vital
+fathers. What's that he said? he should still go before me, my
+pilot; and yet to be seen again? But where? Will I have eyes at the
+bottom of the sea, supposing I descend those endless stairs? and all
+night I've been sailing from him, wherever he did sink to. Aye, aye,
+like many more thou told'st direful truth as touching thyself, O
+Parsee; but, Ahab, there thy shot fell short. Good-bye,
+mast-head--keep a good eye upon the whale, the while I'm gone. We'll
+talk to-morrow, nay, to-night, when the white whale lies down there,
+tied by head and tail."
+
+He gave the word; and still gazing round him, was steadily lowered
+through the cloven blue air to the deck.
+
+In due time the boats were lowered; but as standing in his shallop's
+stern, Ahab just hovered upon the point of the descent, he waved to
+the mate,--who held one of the tackle-ropes on deck--and bade him
+pause.
+
+"Starbuck!"
+
+"Sir?"
+
+"For the third time my soul's ship starts upon this voyage,
+Starbuck."
+
+"Aye, sir, thou wilt have it so."
+
+"Some ships sail from their ports, and ever afterwards are missing,
+Starbuck!"
+
+"Truth, sir: saddest truth."
+
+"Some men die at ebb tide; some at low water; some at the full of the
+flood;--and I feel now like a billow that's all one crested comb,
+Starbuck. I am old;--shake hands with me, man."
+
+Their hands met; their eyes fastened; Starbuck's tears the glue.
+
+"Oh, my captain, my captain!--noble heart--go not--go not!--see, it's
+a brave man that weeps; how great the agony of the persuasion then!"
+
+"Lower away!"--cried Ahab, tossing the mate's arm from him. "Stand
+by the crew!"
+
+In an instant the boat was pulling round close under the stern.
+
+"The sharks! the sharks!" cried a voice from the low cabin-window
+there; "O master, my master, come back!"
+
+But Ahab heard nothing; for his own voice was high-lifted then; and
+the boat leaped on.
+
+Yet the voice spake true; for scarce had he pushed from the ship,
+when numbers of sharks, seemingly rising from out the dark waters
+beneath the hull, maliciously snapped at the blades of the oars,
+every time they dipped in the water; and in this way accompanied the
+boat with their bites. It is a thing not uncommonly happening to the
+whale-boats in those swarming seas; the sharks at times apparently
+following them in the same prescient way that vultures hover over the
+banners of marching regiments in the east. But these were the first
+sharks that had been observed by the Pequod since the White Whale had
+been first descried; and whether it was that Ahab's crew were all
+such tiger-yellow barbarians, and therefore their flesh more musky to
+the senses of the sharks--a matter sometimes well known to affect
+them,--however it was, they seemed to follow that one boat without
+molesting the others.
+
+"Heart of wrought steel!" murmured Starbuck gazing over the side, and
+following with his eyes the receding boat--"canst thou yet ring
+boldly to that sight?--lowering thy keel among ravening sharks, and
+followed by them, open-mouthed to the chase; and this the critical
+third day?--For when three days flow together in one continuous
+intense pursuit; be sure the first is the morning, the second the
+noon, and the third the evening and the end of that thing--be that
+end what it may. Oh! my God! what is this that shoots through me,
+and leaves me so deadly calm, yet expectant,--fixed at the top of a
+shudder! Future things swim before me, as in empty outlines and
+skeletons; all the past is somehow grown dim. Mary, girl! thou
+fadest in pale glories behind me; boy! I seem to see but thy eyes
+grown wondrous blue. Strangest problems of life seem clearing; but
+clouds sweep between--Is my journey's end coming? My legs feel
+faint; like his who has footed it all day. Feel thy heart,--beats
+it yet? Stir thyself, Starbuck!--stave it off--move, move! speak
+aloud!--Mast-head there! See ye my boy's hand on the
+hill?--Crazed;--aloft there!--keep thy keenest eye upon the boats:--
+mark well the whale!--Ho! again!--drive off that hawk! see! he
+pecks--he tears the vane"--pointing to the red flag flying at the
+main-truck--"Ha! he soars away with it!--Where's the old man now?
+see'st thou that sight, oh Ahab!--shudder, shudder!"
+
+The boats had not gone very far, when by a signal from the
+mast-heads--a downward pointed arm, Ahab knew that the whale had
+sounded; but intending to be near him at the next rising, he held on
+his way a little sideways from the vessel; the becharmed crew
+maintaining the profoundest silence, as the head-beat waves hammered
+and hammered against the opposing bow.
+
+"Drive, drive in your nails, oh ye waves! to their uttermost heads
+drive them in! ye but strike a thing without a lid; and no coffin and
+no hearse can be mine:--and hemp only can kill me! Ha! ha!"
+
+Suddenly the waters around them slowly swelled in broad circles; then
+quickly upheaved, as if sideways sliding from a submerged berg of
+ice, swiftly rising to the surface. A low rumbling sound was heard;
+a subterraneous hum; and then all held their breaths; as bedraggled
+with trailing ropes, and harpoons, and lances, a vast form shot
+lengthwise, but obliquely from the sea. Shrouded in a thin drooping
+veil of mist, it hovered for a moment in the rainbowed air; and then
+fell swamping back into the deep. Crushed thirty feet upwards, the
+waters flashed for an instant like heaps of fountains, then brokenly
+sank in a shower of flakes, leaving the circling surface creamed like
+new milk round the marble trunk of the whale.
+
+"Give way!" cried Ahab to the oarsmen, and the boats darted forward
+to the attack; but maddened by yesterday's fresh irons that corroded
+in him, Moby Dick seemed combinedly possessed by all the angels that
+fell from heaven. The wide tiers of welded tendons overspreading his
+broad white forehead, beneath the transparent skin, looked knitted
+together; as head on, he came churning his tail among the boats; and
+once more flailed them apart; spilling out the irons and lances from
+the two mates' boats, and dashing in one side of the upper part of
+their bows, but leaving Ahab's almost without a scar.
+
+While Daggoo and Queequeg were stopping the strained planks; and as
+the whale swimming out from them, turned, and showed one entire flank
+as he shot by them again; at that moment a quick cry went up. Lashed
+round and round to the fish's back; pinioned in the turns upon turns
+in which, during the past night, the whale had reeled the involutions
+of the lines around him, the half torn body of the Parsee was seen;
+his sable raiment frayed to shreds; his distended eyes turned full
+upon old Ahab.
+
+The harpoon dropped from his hand.
+
+"Befooled, befooled!"--drawing in a long lean breath--"Aye, Parsee!
+I see thee again.--Aye, and thou goest before; and this, THIS then is
+the hearse that thou didst promise. But I hold thee to the last
+letter of thy word. Where is the second hearse? Away, mates, to the
+ship! those boats are useless now; repair them if ye can in time, and
+return to me; if not, Ahab is enough to die--Down, men! the first
+thing that but offers to jump from this boat I stand in, that thing I
+harpoon. Ye are not other men, but my arms and my legs; and so obey
+me.--Where's the whale? gone down again?"
+
+But he looked too nigh the boat; for as if bent upon escaping with
+the corpse he bore, and as if the particular place of the last
+encounter had been but a stage in his leeward voyage, Moby Dick was
+now again steadily swimming forward; and had almost passed the
+ship,--which thus far had been sailing in the contrary direction to
+him, though for the present her headway had been stopped. He seemed
+swimming with his utmost velocity, and now only intent upon pursuing
+his own straight path in the sea.
+
+"Oh! Ahab," cried Starbuck, "not too late is it, even now, the third
+day, to desist. See! Moby Dick seeks thee not. It is thou, thou,
+that madly seekest him!"
+
+Setting sail to the rising wind, the lonely boat was swiftly impelled
+to leeward, by both oars and canvas. And at last when Ahab was
+sliding by the vessel, so near as plainly to distinguish Starbuck's
+face as he leaned over the rail, he hailed him to turn the vessel
+about, and follow him, not too swiftly, at a judicious interval.
+Glancing upwards, he saw Tashtego, Queequeg, and Daggoo, eagerly
+mounting to the three mast-heads; while the oarsmen were rocking in
+the two staved boats which had but just been hoisted to the side, and
+were busily at work in repairing them. One after the other, through
+the port-holes, as he sped, he also caught flying glimpses of Stubb
+and Flask, busying themselves on deck among bundles of new irons and
+lances. As he saw all this; as he heard the hammers in the broken
+boats; far other hammers seemed driving a nail into his heart. But
+he rallied. And now marking that the vane or flag was gone from the
+main-mast-head, he shouted to Tashtego, who had just gained that
+perch, to descend again for another flag, and a hammer and nails, and
+so nail it to the mast.
+
+Whether fagged by the three days' running chase, and the resistance
+to his swimming in the knotted hamper he bore; or whether it was some
+latent deceitfulness and malice in him: whichever was true, the White
+Whale's way now began to abate, as it seemed, from the boat so
+rapidly nearing him once more; though indeed the whale's last start
+had not been so long a one as before. And still as Ahab glided over
+the waves the unpitying sharks accompanied him; and so pertinaciously
+stuck to the boat; and so continually bit at the plying oars, that
+the blades became jagged and crunched, and left small splinters in
+the sea, at almost every dip.
+
+"Heed them not! those teeth but give new rowlocks to your oars. Pull
+on! 'tis the better rest, the shark's jaw than the yielding water."
+
+"But at every bite, sir, the thin blades grow smaller and smaller!"
+
+"They will last long enough! pull on!--But who can tell"--he
+muttered--"whether these sharks swim to feast on the whale or on
+Ahab?--But pull on! Aye, all alive, now--we near him. The helm!
+take the helm! let me pass,"--and so saying two of the oarsmen helped
+him forward to the bows of the still flying boat.
+
+At length as the craft was cast to one side, and ran ranging along
+with the White Whale's flank, he seemed strangely oblivious of its
+advance--as the whale sometimes will--and Ahab was fairly within the
+smoky mountain mist, which, thrown off from the whale's spout, curled
+round his great, Monadnock hump; he was even thus close to him; when,
+with body arched back, and both arms lengthwise high-lifted to the
+poise, he darted his fierce iron, and his far fiercer curse into the
+hated whale. As both steel and curse sank to the socket, as if
+sucked into a morass, Moby Dick sideways writhed; spasmodically
+rolled his nigh flank against the bow, and, without staving a hole in
+it, so suddenly canted the boat over, that had it not been for the
+elevated part of the gunwale to which he then clung, Ahab would once
+more have been tossed into the sea. As it was, three of the
+oarsmen--who foreknew not the precise instant of the dart, and were
+therefore unprepared for its effects--these were flung out; but so
+fell, that, in an instant two of them clutched the gunwale again, and
+rising to its level on a combing wave, hurled themselves bodily
+inboard again; the third man helplessly dropping astern, but still
+afloat and swimming.
+
+Almost simultaneously, with a mighty volition of ungraduated,
+instantaneous swiftness, the White Whale darted through the weltering
+sea. But when Ahab cried out to the steersman to take new turns with
+the line, and hold it so; and commanded the crew to turn round on
+their seats, and tow the boat up to the mark; the moment the
+treacherous line felt that double strain and tug, it snapped in the
+empty air!
+
+"What breaks in me? Some sinew cracks!--'tis whole again; oars!
+oars! Burst in upon him!"
+
+Hearing the tremendous rush of the sea-crashing boat, the whale
+wheeled round to present his blank forehead at bay; but in that
+evolution, catching sight of the nearing black hull of the ship;
+seemingly seeing in it the source of all his persecutions; bethinking
+it--it may be--a larger and nobler foe; of a sudden, he bore down
+upon its advancing prow, smiting his jaws amid fiery showers of foam.
+
+Ahab staggered; his hand smote his forehead. "I grow blind; hands!
+stretch out before me that I may yet grope my way. Is't night?"
+
+"The whale! The ship!" cried the cringing oarsmen.
+
+"Oars! oars! Slope downwards to thy depths, O sea, that ere it be
+for ever too late, Ahab may slide this last, last time upon his
+mark! I see: the ship! the ship! Dash on, my men! Will ye not
+save my ship?"
+
+But as the oarsmen violently forced their boat through the
+sledge-hammering seas, the before whale-smitten bow-ends of two
+planks burst through, and in an instant almost, the temporarily
+disabled boat lay nearly level with the waves; its half-wading,
+splashing crew, trying hard to stop the gap and bale out the pouring
+water.
+
+Meantime, for that one beholding instant, Tashtego's mast-head hammer
+remained suspended in his hand; and the red flag, half-wrapping him
+as with a plaid, then streamed itself straight out from him, as his
+own forward-flowing heart; while Starbuck and Stubb, standing upon
+the bowsprit beneath, caught sight of the down-coming monster just as
+soon as he.
+
+"The whale, the whale! Up helm, up helm! Oh, all ye sweet powers of
+air, now hug me close! Let not Starbuck die, if die he must, in a
+woman's fainting fit. Up helm, I say--ye fools, the jaw! the jaw!
+Is this the end of all my bursting prayers? all my life-long
+fidelities? Oh, Ahab, Ahab, lo, thy work. Steady! helmsman, steady.
+Nay, nay! Up helm again! He turns to meet us! Oh, his
+unappeasable brow drives on towards one, whose duty tells him he
+cannot depart. My God, stand by me now!"
+
+"Stand not by me, but stand under me, whoever you are that will now
+help Stubb; for Stubb, too, sticks here. I grin at thee, thou
+grinning whale! Who ever helped Stubb, or kept Stubb awake, but
+Stubb's own unwinking eye? And now poor Stubb goes to bed upon a
+mattrass that is all too soft; would it were stuffed with brushwood!
+I grin at thee, thou grinning whale! Look ye, sun, moon, and stars!
+I call ye assassins of as good a fellow as ever spouted up his ghost.
+For all that, I would yet ring glasses with ye, would ye but hand
+the cup! Oh, oh! oh, oh! thou grinning whale, but there'll be plenty
+of gulping soon! Why fly ye not, O Ahab! For me, off shoes and
+jacket to it; let Stubb die in his drawers! A most mouldy and over
+salted death, though;--cherries! cherries! cherries! Oh, Flask, for
+one red cherry ere we die!"
+
+"Cherries? I only wish that we were where they grow. Oh, Stubb, I
+hope my poor mother's drawn my part-pay ere this; if not, few coppers
+will now come to her, for the voyage is up."
+
+From the ship's bows, nearly all the seamen now hung inactive;
+hammers, bits of plank, lances, and harpoons, mechanically retained
+in their hands, just as they had darted from their various
+employments; all their enchanted eyes intent upon the whale, which
+from side to side strangely vibrating his predestinating head, sent a
+broad band of overspreading semicircular foam before him as he
+rushed. Retribution, swift vengeance, eternal malice were in his
+whole aspect, and spite of all that mortal man could do, the solid
+white buttress of his forehead smote the ship's starboard bow, till
+men and timbers reeled. Some fell flat upon their faces. Like
+dislodged trucks, the heads of the harpooneers aloft shook on their
+bull-like necks. Through the breach, they heard the waters pour, as
+mountain torrents down a flume.
+
+"The ship! The hearse!--the second hearse!" cried Ahab from the
+boat; "its wood could only be American!"
+
+Diving beneath the settling ship, the whale ran quivering along its
+keel; but turning under water, swiftly shot to the surface again, far
+off the other bow, but within a few yards of Ahab's boat, where, for
+a time, he lay quiescent.
+
+"I turn my body from the sun. What ho, Tashtego! let me hear thy
+hammer. Oh! ye three unsurrendered spires of mine; thou uncracked
+keel; and only god-bullied hull; thou firm deck, and haughty helm,
+and Pole-pointed prow,--death-glorious ship! must ye then perish,
+and without me? Am I cut off from the last fond pride of meanest
+shipwrecked captains? Oh, lonely death on lonely life! Oh, now I
+feel my topmost greatness lies in my topmost grief. Ho, ho! from all
+your furthest bounds, pour ye now in, ye bold billows of my whole
+foregone life, and top this one piled comber of my death! Towards
+thee I roll, thou all-destroying but unconquering whale; to the last
+I grapple with thee; from hell's heart I stab at thee; for hate's
+sake I spit my last breath at thee. Sink all coffins and all hearses
+to one common pool! and since neither can be mine, let me then tow to
+pieces, while still chasing thee, though tied to thee, thou damned
+whale! THUS, I give up the spear!"
+
+The harpoon was darted; the stricken whale flew forward; with
+igniting velocity the line ran through the grooves;--ran foul. Ahab
+stooped to clear it; he did clear it; but the flying turn caught him
+round the neck, and voicelessly as Turkish mutes bowstring their
+victim, he was shot out of the boat, ere the crew knew he was gone.
+Next instant, the heavy eye-splice in the rope's final end flew out
+of the stark-empty tub, knocked down an oarsman, and smiting the sea,
+disappeared in its depths.
+
+For an instant, the tranced boat's crew stood still; then turned.
+"The ship? Great God, where is the ship?" Soon they through dim,
+bewildering mediums saw her sidelong fading phantom, as in the
+gaseous Fata Morgana; only the uppermost masts out of water; while
+fixed by infatuation, or fidelity, or fate, to their once lofty
+perches, the pagan harpooneers still maintained their sinking
+lookouts on the sea. And now, concentric circles seized the lone
+boat itself, and all its crew, and each floating oar, and every
+lance-pole, and spinning, animate and inanimate, all round and round
+in one vortex, carried the smallest chip of the Pequod out of sight.
+
+But as the last whelmings intermixingly poured themselves over the
+sunken head of the Indian at the mainmast, leaving a few inches of
+the erect spar yet visible, together with long streaming yards of the
+flag, which calmly undulated, with ironical coincidings, over the
+destroying billows they almost touched;--at that instant, a red arm
+and a hammer hovered backwardly uplifted in the open air, in the act
+of nailing the flag faster and yet faster to the subsiding spar. A
+sky-hawk that tauntingly had followed the main-truck downwards from
+its natural home among the stars, pecking at the flag, and
+incommoding Tashtego there; this bird now chanced to intercept its
+broad fluttering wing between the hammer and the wood; and
+simultaneously feeling that etherial thrill, the submerged savage
+beneath, in his death-gasp, kept his hammer frozen there; and so the
+bird of heaven, with archangelic shrieks, and his imperial beak
+thrust upwards, and his whole captive form folded in the flag of
+Ahab, went down with his ship, which, like Satan, would not sink to
+hell till she had dragged a living part of heaven along with her, and
+helmeted herself with it.
+
+Now small fowls flew screaming over the yet yawning gulf; a sullen
+white surf beat against its steep sides; then all collapsed, and the
+great shroud of the sea rolled on as it rolled five thousand years
+ago.
+
+
+
+Epilogue
+
+"AND I ONLY AM ESCAPED ALONE TO TELL THEE"
+Job.
+
+The drama's done. Why then here does any one step forth?--Because
+one did survive the wreck.
+
+It so chanced, that after the Parsee's disappearance, I was he whom
+the Fates ordained to take the place of Ahab's bowsman, when that
+bowsman assumed the vacant post; the same, who, when on the last day
+the three men were tossed from out of the rocking boat, was dropped
+astern. So, floating on the margin of the ensuing scene, and in full
+sight of it, when the halfspent suction of the sunk ship reached me,
+I was then, but slowly, drawn towards the closing vortex. When I
+reached it, it had subsided to a creamy pool. Round and round, then,
+and ever contracting towards the button-like black bubble at the axis
+of that slowly wheeling circle, like another Ixion I did revolve.
+Till, gaining that vital centre, the black bubble upward burst; and
+now, liberated by reason of its cunning spring, and, owing to its
+great buoyancy, rising with great force, the coffin life-buoy shot
+lengthwise from the sea, fell over, and floated by my side. Buoyed
+up by that coffin, for almost one whole day and night, I floated on a
+soft and dirgelike main. The unharming sharks, they glided by as if
+with padlocks on their mouths; the savage sea-hawks sailed with
+sheathed beaks. On the second day, a sail drew near, nearer, and
+picked me up at last. It was the devious-cruising Rachel, that in
+her retracing search after her missing children, only found another
+orphan.
+
+
+
+
+End of this Project Gutenberg etext of Moby Dick, by Herman Melville
+
diff --git a/okio-assetfilesystem/src/androidTest/kotlin/okio/assetfilesystem/AssetFileSystemTest.kt b/okio-assetfilesystem/src/androidTest/kotlin/okio/assetfilesystem/AssetFileSystemTest.kt
new file mode 100644
index 00000000..719cbe4b
--- /dev/null
+++ b/okio-assetfilesystem/src/androidTest/kotlin/okio/assetfilesystem/AssetFileSystemTest.kt
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.assetfilesystem
+
+import androidx.test.platform.app.InstrumentationRegistry
+import assertk.assertThat
+import assertk.assertions.containsAll
+import assertk.assertions.containsExactly
+import assertk.assertions.hasMessage
+import assertk.assertions.isEmpty
+import assertk.assertions.isEqualTo
+import assertk.assertions.isFalse
+import assertk.assertions.isNotNull
+import assertk.assertions.isNull
+import assertk.assertions.isTrue
+import kotlin.test.assertFailsWith
+import okio.Buffer
+import okio.BufferedSource
+import okio.FileHandle
+import okio.FileNotFoundException
+import okio.IOException
+import okio.Path.Companion.toPath
+import okio.buffer
+import org.junit.Test
+
+class AssetFileSystemTest {
+ private val context = InstrumentationRegistry.getInstrumentation().context
+ private val fs = context.assets.asFileSystem()
+
+ @Test fun canonicalizeValid() {
+ assertThat(fs.canonicalize("/".toPath())).isEqualTo("/".toPath())
+ assertThat(fs.canonicalize(".".toPath())).isEqualTo("/".toPath())
+ assertThat(fs.canonicalize("not/a/path/../../..".toPath())).isEqualTo("/".toPath())
+ assertThat(fs.canonicalize("file.txt".toPath())).isEqualTo("/file.txt".toPath())
+ assertThat(fs.canonicalize("stuff/../file.txt".toPath())).isEqualTo("/file.txt".toPath())
+ assertThat(fs.canonicalize("dir".toPath())).isEqualTo("/dir".toPath())
+ assertThat(fs.canonicalize("dir/whevs/..".toPath())).isEqualTo("/dir".toPath())
+ assertThat(fs.canonicalize("dir/nested.txt".toPath())).isEqualTo("/dir/nested.txt".toPath())
+ assertThat(fs.canonicalize("dir/whevs/../nested.txt".toPath())).isEqualTo("/dir/nested.txt".toPath())
+ }
+
+ @Test fun canonicalizeInvalidThrows() {
+ assertFailsWith<FileNotFoundException> {
+ fs.canonicalize("not/a/path".toPath())
+ }
+ }
+
+ @Test fun listRoot() {
+ val list = fs.list("/".toPath())
+ assertThat(list).containsAll(
+ "dir".toPath(),
+ "file.txt".toPath(),
+ "moby10b.txt".toPath(),
+ )
+ }
+
+ @Test fun listRootCanonicalizes() {
+ val list = fs.list("foo/bar/../..".toPath())
+ assertThat(list).containsAll(
+ "dir".toPath(),
+ "file.txt".toPath(),
+ "moby10b.txt".toPath(),
+ )
+ }
+
+ @Test fun listDirectory() {
+ val list = fs.list("dir".toPath())
+ assertThat(list).containsExactly("nested.txt".toPath())
+ }
+
+ @Test fun listDirectoryCanonicalizes() {
+ val list = fs.list("dir/not/real/../..".toPath())
+ assertThat(list).containsExactly("nested.txt".toPath())
+ }
+
+ @Test fun listNonExistentDirectoryThrows() {
+ assertFailsWith<FileNotFoundException> {
+ fs.list("nope/".toPath())
+ }
+ }
+
+ @Test fun listFileThrows() {
+ assertFailsWith<FileNotFoundException> {
+ fs.list("dir/nested.txt".toPath())
+ }
+ }
+
+ @Test fun listOrNullDirectory() {
+ val list = fs.listOrNull("dir".toPath())
+ assertThat(list).isNotNull().containsExactly("nested.txt".toPath())
+ }
+
+ @Test fun listOrNullDirectoryCanonicalizes() {
+ val list = fs.listOrNull("dir/not/real/../..".toPath())
+ assertThat(list).isNotNull().containsExactly("nested.txt".toPath())
+ }
+
+ @Test fun listOrNullNonExistentDirectory() {
+ val list = fs.listOrNull("nope".toPath())
+ assertThat(list).isNull()
+ }
+
+ @Test fun listOrNullFile() {
+ val list = fs.listOrNull("dir/nested.txt".toPath())
+ assertThat(list).isNull()
+ }
+
+ @Test fun metadataFile() {
+ val metadata = fs.metadataOrNull("file.txt".toPath())!!
+
+ // Data we can get:
+ assertThat(metadata.isRegularFile).isTrue()
+ assertThat(metadata.isDirectory).isFalse()
+
+ // Data we cannot get or is impossible:
+ assertThat(metadata.size).isNull()
+ assertThat(metadata.symlinkTarget).isNull()
+ assertThat(metadata.createdAtMillis).isNull()
+ assertThat(metadata.lastModifiedAtMillis).isNull()
+ assertThat(metadata.lastAccessedAtMillis).isNull()
+ assertThat(metadata.extras).isEmpty()
+ }
+
+ @Test fun metadataDirectory() {
+ val metadata = fs.metadataOrNull("dir".toPath())!!
+
+ // Data we can get:
+ assertThat(metadata.isRegularFile).isFalse()
+ assertThat(metadata.isDirectory).isTrue()
+
+ // Data we cannot get or is impossible:
+ assertThat(metadata.symlinkTarget).isNull()
+ assertThat(metadata.size).isNull()
+ assertThat(metadata.createdAtMillis).isNull()
+ assertThat(metadata.lastModifiedAtMillis).isNull()
+ assertThat(metadata.lastAccessedAtMillis).isNull()
+ assertThat(metadata.extras).isEmpty()
+ }
+
+ @Test fun metadataDirectoryCanonicalizes() {
+ val metadata = fs.metadataOrNull("dir/not/real/../..".toPath())!!
+ assertThat(metadata.isDirectory).isTrue()
+ }
+
+ @Test fun metadataNonExistentPath() {
+ val metadata = fs.metadataOrNull("not/a/path".toPath())
+ assertThat(metadata).isNull()
+ }
+
+ @Test fun sourceFile() {
+ val file = fs.source("file.txt".toPath()).buffer().use(BufferedSource::readUtf8)
+ assertThat(file).isEqualTo("File!\n")
+ val nested = fs.source("dir/nested.txt".toPath()).buffer().use(BufferedSource::readUtf8)
+ assertThat(nested).isEqualTo("Nested!\n")
+ }
+
+ @Test fun sourceDirectory() {
+ assertFailsWith<FileNotFoundException> {
+ fs.source("dir".toPath())
+ }
+ }
+
+ @Test fun sourceNonExistent() {
+ assertFailsWith<FileNotFoundException> {
+ fs.source("not/a/path".toPath())
+ }
+ }
+
+ @Test fun openReadOnlyInvalidThrows() {
+ assertFailsWith<FileNotFoundException> {
+ fs.openReadOnly("not/a/path".toPath())
+ }
+ }
+
+ @Test fun openReadOnlyDirectoryThrows() {
+ assertFailsWith<FileNotFoundException> {
+ fs.openReadOnly("dir".toPath())
+ }
+ }
+
+ @Test fun openReadOnlySize() {
+ val smallSize = fs.openReadOnly("file.txt".toPath()).use(FileHandle::size)
+ assertThat(smallSize).isEqualTo(6)
+
+ val mobySize = fs.openReadOnly("moby10b.txt".toPath()).use(FileHandle::size)
+ assertThat(mobySize).isEqualTo(1232923)
+ }
+
+ @Test fun openReadOnlyRandomAccessForward() {
+ fs.openReadOnly("moby10b.txt".toPath()).use { handle ->
+ val buffer = Buffer()
+ handle.read(34251, buffer, 16)
+ assertThat(buffer.readUtf8()).isEqualTo("Call me Ishmael.")
+ handle.read(148605, buffer, 49)
+ assertThat(buffer.readUtf8()).isEqualTo("It is not down in any map; true places never are.")
+ handle.read(1051694, buffer, 50)
+ assertThat(buffer.readUtf8()).isEqualTo("his forehead's veins swelled like overladen brooks")
+ handle.read(148605, buffer, 49)
+ assertThat(buffer.readUtf8()).isEqualTo("It is not down in any map; true places never are.")
+ handle.read(34251, buffer, 16)
+ assertThat(buffer.readUtf8()).isEqualTo("Call me Ishmael.")
+ }
+ }
+
+ @Test fun openReadOnlyRandomAccessThenSize() {
+ fs.openReadOnly("moby10b.txt".toPath()).use { handle ->
+ val buffer = Buffer()
+ handle.read(34251, buffer, 16)
+ assertThat(buffer.readUtf8()).isEqualTo("Call me Ishmael.")
+
+ assertThat(handle.size()).isEqualTo(1232923)
+ }
+ }
+
+ @Test fun openReadOnlyFlushThrows() {
+ fs.openReadOnly("file.txt".toPath()).use { handle ->
+ val t = assertFailsWith<IllegalStateException> {
+ handle.flush()
+ }
+ assertThat(t).hasMessage("file handle is read-only")
+ }
+ }
+
+ @Test fun openReadOnlyResizeThrows() {
+ fs.openReadOnly("file.txt".toPath()).use { handle ->
+ val t = assertFailsWith<IllegalStateException> {
+ handle.resize(10L)
+ }
+ assertThat(t).hasMessage("file handle is read-only")
+ }
+ }
+
+ @Test fun openReadOnlyWriteThrows() {
+ fs.openReadOnly("file.txt".toPath()).use { handle ->
+ val t = assertFailsWith<IllegalStateException> {
+ handle.write(0, Buffer().writeUtf8("Sup"), 3)
+ }
+ assertThat(t).hasMessage("file handle is read-only")
+ }
+ }
+
+ @Test fun sinkThrows() {
+ val t = assertFailsWith<IOException> {
+ fs.sink("file.txt".toPath())
+ }
+ assertThat(t).hasMessage("asset file systems are read-only")
+ }
+
+ @Test fun appendingSinkThrows() {
+ val t = assertFailsWith<IOException> {
+ fs.appendingSink("file.txt".toPath())
+ }
+ assertThat(t).hasMessage("asset file systems are read-only")
+ }
+
+ @Test fun createDirectoryThrows() {
+ val t = assertFailsWith<IOException> {
+ fs.createDirectory("new-dir".toPath(), mustCreate = true)
+ }
+ assertThat(t).hasMessage("asset file systems are read-only")
+ }
+
+ @Test fun atomicMoveThrows() {
+ val t = assertFailsWith<IOException> {
+ fs.atomicMove("file.txt".toPath(), "new-file.txt".toPath())
+ }
+ assertThat(t).hasMessage("asset file systems are read-only")
+ }
+
+ @Test fun deleteThrows() {
+ val t = assertFailsWith<IOException> {
+ fs.delete("file.txt".toPath())
+ }
+ assertThat(t).hasMessage("asset file systems are read-only")
+ }
+
+ @Test fun createSymlinkThrows() {
+ val t = assertFailsWith<IOException> {
+ fs.createSymlink("file.txt".toPath(), "new-file.txt".toPath())
+ }
+ assertThat(t).hasMessage("asset file systems are read-only")
+ }
+
+ @Test fun openReadWriteThrows() {
+ val t = assertFailsWith<IOException> {
+ fs.openReadWrite("file.txt".toPath())
+ }
+ assertThat(t).hasMessage("asset file systems are read-only")
+ }
+}
diff --git a/okio-assetfilesystem/src/main/kotlin/okio/assetfilesystem/AssetFileSystem.kt b/okio-assetfilesystem/src/main/kotlin/okio/assetfilesystem/AssetFileSystem.kt
new file mode 100644
index 00000000..b63dca20
--- /dev/null
+++ b/okio-assetfilesystem/src/main/kotlin/okio/assetfilesystem/AssetFileSystem.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.assetfilesystem
+
+import android.content.res.AssetManager
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.io.InputStream
+import okio.FileHandle
+import okio.FileMetadata
+import okio.FileSystem
+import okio.Path
+import okio.Path.Companion.toPath
+import okio.Sink
+import okio.Source
+import okio.source
+
+/**
+ * Expose this [AssetManager] as an Okio [FileSystem].
+ *
+ * Note: Assets are a read-only view on a file system and so any attempt to mutate
+ * will throw an [IOException].
+ */
+fun AssetManager.asFileSystem(): FileSystem = AssetFileSystem(this)
+
+private class AssetFileSystem(
+ private val assets: AssetManager,
+) : FileSystem() {
+ override fun canonicalize(path: Path): Path {
+ val canonical = canonicalizeInternal(path)
+ if (canonical.existsInternal()) {
+ return canonical
+ }
+ throw FileNotFoundException("$path")
+ }
+
+ private fun canonicalizeInternal(path: Path) = ROOT.resolve(path, normalize = true)
+
+ private fun Path.toAssetRelativePathString(): String {
+ return toString().removePrefix("/")
+ }
+
+ /**
+ * Determine if [this] is a valid path to a file or directory.
+ *
+ * If this function returns true, a call to [AssetManager.open] will either return successfully
+ * or throw [FileNotFoundException] based on whether [this] is a file or directory, respectively.
+ */
+ private fun Path.existsInternal(): Boolean {
+ if (this == ROOT) {
+ return true
+ }
+
+ // Both non-existent paths and paths to existing files return an empty array when listing.
+ // Determine if a path exists by checking if its name is present in the parent's list.
+ val parent = checkNotNull(parent) { "Path has no parent. Did you canonicalize? $this" }
+ val children = assets.list(parent.toAssetRelativePathString()).orEmpty()
+ return name in children
+ }
+
+ override fun metadataOrNull(path: Path): FileMetadata? {
+ val canonical = canonicalizeInternal(path)
+ if (canonical.existsInternal()) {
+ val pathString = canonical.toAssetRelativePathString()
+ return try {
+ assets.open(pathString).close()
+ FileMetadata(
+ isRegularFile = true,
+ isDirectory = false,
+ )
+ } catch (_: FileNotFoundException) {
+ FileMetadata(
+ isRegularFile = false,
+ isDirectory = true,
+ )
+ }
+ }
+ return null
+ }
+
+ override fun list(dir: Path): List<Path> {
+ val canonical = canonicalizeInternal(dir)
+ if (canonical.existsInternal()) {
+ val pathString = canonical.toAssetRelativePathString()
+ try {
+ // This will throw if the path points to a file.
+ assets.open(pathString).close()
+ } catch (_: FileNotFoundException) {
+ return assets.list(pathString)
+ ?.map { it.toPath() }
+ .orEmpty()
+ }
+ }
+ throw FileNotFoundException("$dir")
+ }
+
+ override fun listOrNull(dir: Path): List<Path>? {
+ return try {
+ list(dir)
+ } catch (_: IOException) {
+ null
+ }
+ }
+
+ override fun openReadOnly(file: Path): FileHandle {
+ val pathString = canonicalizeInternal(file).toAssetRelativePathString()
+ val inputStream = assets.open(pathString)
+ return AssetFileHandle(assets, pathString, inputStream)
+ }
+
+ override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
+ throw IOException("asset file systems are read-only")
+ }
+
+ override fun source(file: Path): Source {
+ return assets.open(canonicalizeInternal(file).toAssetRelativePathString()).source()
+ }
+
+ override fun sink(file: Path, mustCreate: Boolean): Sink {
+ throw IOException("asset file systems are read-only")
+ }
+
+ override fun appendingSink(file: Path, mustExist: Boolean): Sink {
+ throw IOException("asset file systems are read-only")
+ }
+
+ override fun createDirectory(dir: Path, mustCreate: Boolean) {
+ throw IOException("asset file systems are read-only")
+ }
+
+ override fun atomicMove(source: Path, target: Path) {
+ throw IOException("asset file systems are read-only")
+ }
+
+ override fun delete(path: Path, mustExist: Boolean) {
+ throw IOException("asset file systems are read-only")
+ }
+
+ override fun createSymlink(source: Path, target: Path) {
+ throw IOException("asset file systems are read-only")
+ }
+
+ private companion object {
+ val ROOT = "/".toPath()
+ }
+}
+
+private class AssetFileHandle(
+ private val assets: AssetManager,
+ private val pathString: String,
+ private var inputStream: InputStream,
+) : FileHandle(false) {
+ private var currentOffset = 0
+ private var size = -1
+
+ override fun protectedRead(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ): Int {
+ // If we need to jump backwards or have reached the end of the file,
+ // close the existing stream and open a new one.
+ if (currentOffset > fileOffset || currentOffset == size) {
+ inputStream.close()
+ inputStream = assets.open(pathString)
+ currentOffset = 0
+ }
+
+ while (true) {
+ val skip = fileOffset - currentOffset
+ if (skip == 0L) break
+ val skipped = inputStream.skip(skip).toInt()
+ if (skipped == 0) {
+ // Since we know skip is never negative, a skip of 0 means EOF.
+ // Record this as the file size to trigger stream recreation.
+ size = currentOffset
+ throw IllegalArgumentException("fileOffset $fileOffset > size $size")
+ }
+ currentOffset += skipped
+ }
+
+ val read = inputStream.read(array, arrayOffset, byteCount)
+ if (read == -1) {
+ // A read of -1 means EOF. Record this as the file size to trigger stream recreation.
+ size = currentOffset
+ } else {
+ currentOffset += read
+ }
+ return read
+ }
+
+ override fun protectedSize(): Long {
+ if (size == -1) {
+ while (true) {
+ val skipped = inputStream.skip(1024 * 1024).toInt()
+ if (skipped == 0) {
+ size = currentOffset
+ break
+ }
+ currentOffset += skipped
+ }
+ }
+ return size.toLong()
+ }
+
+ override fun protectedClose() {
+ inputStream.close()
+ }
+
+ override fun protectedWrite(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ) {
+ throw AssertionError()
+ }
+
+ override fun protectedFlush() {
+ throw AssertionError()
+ }
+
+ override fun protectedResize(size: Long) {
+ throw AssertionError()
+ }
+}
diff --git a/okio-bom/build.gradle.kts b/okio-bom/build.gradle.kts
new file mode 100644
index 00000000..8417d761
--- /dev/null
+++ b/okio-bom/build.gradle.kts
@@ -0,0 +1,12 @@
+plugins {
+ id("com.vanniktech.maven.publish.base")
+ id("java-platform")
+}
+
+collectBomConstraints()
+
+extensions.configure<PublishingExtension> {
+ publications.create("maven", MavenPublication::class) {
+ from(project.components.getByName("javaPlatform"))
+ }
+}
diff --git a/okio-fakefilesystem/README.md b/okio-fakefilesystem/README.md
new file mode 100644
index 00000000..5460c503
--- /dev/null
+++ b/okio-fakefilesystem/README.md
@@ -0,0 +1,4 @@
+Okio Fake File System
+---------------------
+
+This module contains an in-memory file system.
diff --git a/okio-fakefilesystem/api/okio-fakefilesystem.api b/okio-fakefilesystem/api/okio-fakefilesystem.api
new file mode 100644
index 00000000..1d319e43
--- /dev/null
+++ b/okio-fakefilesystem/api/okio-fakefilesystem.api
@@ -0,0 +1,41 @@
+public final class okio/fakefilesystem/FakeFileSystem : okio/FileSystem {
+ public final field clock Lkotlinx/datetime/Clock;
+ public fun <init> ()V
+ public fun <init> (Lkotlinx/datetime/Clock;)V
+ public synthetic fun <init> (Lkotlinx/datetime/Clock;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun allPaths ()Ljava/util/Set;
+ public fun appendingSink (Lokio/Path;Z)Lokio/Sink;
+ public fun atomicMove (Lokio/Path;Lokio/Path;)V
+ public fun canonicalize (Lokio/Path;)Lokio/Path;
+ public final fun checkNoOpenFiles ()V
+ public fun createDirectory (Lokio/Path;Z)V
+ public fun createSymlink (Lokio/Path;Lokio/Path;)V
+ public fun delete (Lokio/Path;Z)V
+ public final fun emulateUnix ()V
+ public final fun emulateWindows ()V
+ public final fun getAllowClobberingEmptyDirectories ()Z
+ public final fun getAllowDeletingOpenFiles ()Z
+ public final fun getAllowMovingOpenFiles ()Z
+ public final fun getAllowReadsWhileWriting ()Z
+ public final fun getAllowSymlinks ()Z
+ public final fun getAllowWritesWhileWriting ()Z
+ public final fun getWorkingDirectory ()Lokio/Path;
+ public fun list (Lokio/Path;)Ljava/util/List;
+ public fun listOrNull (Lokio/Path;)Ljava/util/List;
+ public fun metadataOrNull (Lokio/Path;)Lokio/FileMetadata;
+ public final fun openPaths ()Ljava/util/List;
+ public fun openReadOnly (Lokio/Path;)Lokio/FileHandle;
+ public fun openReadWrite (Lokio/Path;ZZ)Lokio/FileHandle;
+ public final fun setAllowClobberingEmptyDirectories (Z)V
+ public final fun setAllowDeletingOpenFiles (Z)V
+ public final fun setAllowMovingOpenFiles (Z)V
+ public final fun setAllowReadsWhileWriting (Z)V
+ public final fun setAllowSymlinks (Z)V
+ public final fun setAllowWritesWhileWriting (Z)V
+ public final fun setExtra (Lokio/Path;Lkotlin/reflect/KClass;Ljava/lang/Object;)V
+ public final fun setWorkingDirectory (Lokio/Path;)V
+ public fun sink (Lokio/Path;Z)Lokio/Sink;
+ public fun source (Lokio/Path;)Lokio/Source;
+ public fun toString ()Ljava/lang/String;
+}
+
diff --git a/okio-fakefilesystem/build.gradle.kts b/okio-fakefilesystem/build.gradle.kts
new file mode 100644
index 00000000..afebff16
--- /dev/null
+++ b/okio-fakefilesystem/build.gradle.kts
@@ -0,0 +1,79 @@
+import com.vanniktech.maven.publish.JavadocJar.Dokka
+import com.vanniktech.maven.publish.KotlinMultiplatform
+import com.vanniktech.maven.publish.MavenPublishBaseExtension
+
+plugins {
+ kotlin("multiplatform")
+ id("org.jetbrains.dokka")
+ id("com.vanniktech.maven.publish.base")
+ id("binary-compatibility-validator")
+ id("build-support")
+}
+
+kotlin {
+ jvm {
+ }
+ if (kmpJsEnabled) {
+ js {
+ compilations.all {
+ kotlinOptions {
+ moduleKind = "umd"
+ sourceMap = true
+ metaInfo = true
+ }
+ }
+ nodejs {
+ testTask {
+ useMocha {
+ timeout = "30s"
+ }
+ }
+ }
+ browser {
+ }
+ }
+ }
+ if (kmpNativeEnabled) {
+ configureOrCreateNativePlatforms()
+ }
+ sourceSets {
+ val commonMain by getting {
+ dependencies {
+ api(libs.kotlin.time)
+ api(projects.okio)
+ }
+ }
+ val commonTest by getting
+ if (kmpWasmEnabled) {
+ // Add support for wasmWasi when https://github.com/Kotlin/kotlinx-datetime/issues/324 is resolved.
+ configureOrCreateWasmPlatform(wasi = false)
+ createSourceSet("wasmMain", parent = commonMain, children = listOf("wasmJs"))
+ createSourceSet("wasmTest", parent = commonTest, children = listOf("wasmJs"))
+ }
+ }
+}
+
+tasks {
+ val jvmJar by getting(Jar::class) {
+ // BundleTaskConvention() crashes unless there's a 'main' source set.
+ sourceSets.create(SourceSet.MAIN_SOURCE_SET_NAME)
+ val bndConvention = aQute.bnd.gradle.BundleTaskConvention(this)
+ bndConvention.setBnd(
+ """
+ Export-Package: okio.fakefilesystem
+ Automatic-Module-Name: okio.fakefilesystem
+ Bundle-SymbolicName: com.squareup.okio.fakefilesystem
+ """
+ )
+ // Call the convention when the task has finished to modify the jar to contain OSGi metadata.
+ doLast {
+ bndConvention.buildBundle()
+ }
+ }
+}
+
+configure<MavenPublishBaseExtension> {
+ configure(
+ KotlinMultiplatform(javadocJar = Dokka("dokkaGfm"))
+ )
+}
diff --git a/okio-fakefilesystem/src/commonMain/kotlin/okio/fakefilesystem/FakeFileSystem.kt b/okio-fakefilesystem/src/commonMain/kotlin/okio/fakefilesystem/FakeFileSystem.kt
new file mode 100644
index 00000000..fb2bd655
--- /dev/null
+++ b/okio-fakefilesystem/src/commonMain/kotlin/okio/fakefilesystem/FakeFileSystem.kt
@@ -0,0 +1,768 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.fakefilesystem
+
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmName
+import kotlin.reflect.KClass
+import kotlinx.datetime.Clock
+import kotlinx.datetime.Instant
+import okio.ArrayIndexOutOfBoundsException
+import okio.Buffer
+import okio.ByteString
+import okio.FileHandle
+import okio.FileMetadata
+import okio.FileNotFoundException
+import okio.FileSystem
+import okio.IOException
+import okio.Path
+import okio.Path.Companion.toPath
+import okio.Sink
+import okio.Source
+import okio.fakefilesystem.FakeFileSystem.Element.Directory
+import okio.fakefilesystem.FakeFileSystem.Element.File
+import okio.fakefilesystem.FakeFileSystem.Element.Symlink
+import okio.fakefilesystem.FakeFileSystem.Operation.READ
+import okio.fakefilesystem.FakeFileSystem.Operation.WRITE
+
+/**
+ * A fully in-memory file system useful for testing. It includes features to support writing
+ * better tests.
+ *
+ * Use [openPaths] to see which paths have been opened for read or write, but not yet closed. Tests
+ * should call [checkNoOpenFiles] in `tearDown()` to confirm that no file streams were leaked.
+ *
+ * Strict By Default
+ * -----------------
+ *
+ * These actions are not allowed and throw an [IOException] if attempted:
+ *
+ * * Moving a file that is currently open for reading or writing.
+ * * Deleting a file that is currently open for reading or writing.
+ * * Moving a file to a path that currently resolves to an empty directory.
+ * * Reading and writing the same file at the same time.
+ * * Opening a file for writing that is already open for writing.
+ *
+ * Programs that do not attempt any of the above operations should work fine on both UNIX and
+ * Windows systems. Relax these constraints individually or call [emulateWindows] or [emulateUnix];
+ * to apply the constraints of a particular operating system.
+ */
+class FakeFileSystem(
+ @JvmField
+ val clock: Clock = Clock.System,
+) : FileSystem() {
+
+ /** File system roots. Each element is a Directory and is created on-demand. */
+ private val roots = mutableMapOf<Path, Directory>()
+
+ /** Files that are currently open and need to be closed to avoid resource leaks. */
+ private val openFiles = mutableListOf<OpenFile>()
+
+ /**
+ * An absolute path with this file system's current working directory. Relative paths will be
+ * resolved against this directory when they are used.
+ */
+ var workingDirectory: Path = "/".toPath()
+ set(value) {
+ require(value.isAbsolute) {
+ "expected an absolute path but was $value"
+ }
+ field = value
+ }
+
+ /**
+ * True to allow files to be moved even if they're currently open for read or write. UNIX file
+ * systems typically allow open files to be moved; Windows file systems do not.
+ */
+ var allowMovingOpenFiles = false
+
+ /**
+ * True to allow files to be deleted even if they're currently open for read or write. UNIX file
+ * systems typically allow open files to be deleted; Windows file systems do not.
+ */
+ var allowDeletingOpenFiles = false
+
+ /**
+ * True to allow the target of an [atomicMove] operation to be an empty directory. Windows file
+ * systems typically allow files to replace empty directories; UNIX file systems do not.
+ */
+ var allowClobberingEmptyDirectories = false
+
+ /**
+ * True to permit a file to have multiple [sinks][sink] open at the same time. Both Windows and
+ * UNIX file systems permit this but the result may be undefined.
+ */
+ var allowWritesWhileWriting = false
+
+ /**
+ * True to permit a file to have a [source] and [sink] open at the same time. Both Windows and
+ * UNIX file systems permit this but the result may be undefined.
+ */
+ var allowReadsWhileWriting = false
+
+ /**
+ * True to allow symlinks to be created. UNIX file systems typically allow symlinks; Windows file
+ * systems do not. Setting this to false after creating a symlink does not prevent that symlink
+ * from being returned or used.
+ */
+ var allowSymlinks = false
+
+ /**
+ * Canonical paths for every file and directory in this file system. This omits file system roots
+ * like `C:\` and `/`.
+ */
+ @get:JvmName("allPaths")
+ val allPaths: Set<Path>
+ get() {
+ val result = mutableListOf<Path>()
+ for (path in roots.keys) {
+ result += listRecursively(path)
+ }
+ result.sort()
+ return result.toSet()
+ }
+
+ /**
+ * Canonical paths currently opened for reading or writing in the order they were opened. This may
+ * contain duplicates if a single path is open by multiple readers.
+ *
+ * Note that this may contain paths not present in [allPaths]. This occurs if a file is deleted
+ * while it is still open.
+ *
+ * The returned list is ordered by the order that the paths were opened.
+ */
+ @get:JvmName("openPaths")
+ val openPaths: List<Path>
+ get() = openFiles.map { it.canonicalPath }
+
+ /**
+ * Confirm that all files that have been opened on this file system (with [source], [sink], and
+ * [appendingSink]) have since been closed. Call this in your test's `tearDown()` function to
+ * confirm that your program hasn't leaked any open files.
+ *
+ * Forgetting to close a file on a real file system is a severe error that may lead to a program
+ * crash. The operating system enforces a limit on how many files may be open simultaneously. On
+ * Linux this is [getrlimit] and is commonly adjusted with the `ulimit` command.
+ *
+ * [getrlimit]: https://man7.org/linux/man-pages/man2/getrlimit.2.html
+ *
+ * @throws IllegalStateException if any files are open when this function is called.
+ */
+ fun checkNoOpenFiles() {
+ val firstOpenFile = openFiles.firstOrNull() ?: return
+ throw IllegalStateException(
+ """
+ |expected 0 open files, but found:
+ | ${openFiles.joinToString(separator = "\n ") { it.canonicalPath.toString() }}
+ """.trimMargin(),
+ firstOpenFile.backtrace,
+ )
+ }
+
+ /**
+ * Configure this file system to use a Windows-like working directory (`F:\`, unless the working
+ * directory is already Windows-like) and to follow a Windows-like policy on what operations
+ * are permitted.
+ */
+ fun emulateWindows() {
+ if ("\\" !in workingDirectory.toString()) {
+ workingDirectory = "F:\\".toPath()
+ }
+ allowMovingOpenFiles = false
+ allowDeletingOpenFiles = false
+ allowClobberingEmptyDirectories = true
+ allowWritesWhileWriting = true
+ allowReadsWhileWriting = true
+ }
+
+ /**
+ * Configure this file system to use a UNIX-like working directory (`/`, unless the working
+ * directory is already UNIX-like) and to follow a UNIX-like policy on what operations are
+ * permitted.
+ */
+ fun emulateUnix() {
+ if ("/" !in workingDirectory.toString()) {
+ workingDirectory = "/".toPath()
+ }
+ allowMovingOpenFiles = true
+ allowDeletingOpenFiles = true
+ allowClobberingEmptyDirectories = false
+ allowWritesWhileWriting = true
+ allowReadsWhileWriting = true
+ allowSymlinks = true
+ }
+
+ override fun canonicalize(path: Path): Path {
+ val canonicalPath = canonicalizeInternal(path)
+
+ val lookupResult = lookupPath(canonicalPath)
+ if (lookupResult?.element == null) {
+ throw FileNotFoundException("no such file: $path")
+ }
+
+ return lookupResult.path
+ }
+
+ /** Don't throw [FileNotFoundException] if the path doesn't identify a file. */
+ private fun canonicalizeInternal(path: Path): Path {
+ return workingDirectory.resolve(path, normalize = true)
+ }
+
+ /**
+ * Sets the metadata of type [type] on [path] to [value]. If [value] is null this clears that
+ * metadata.
+ *
+ * Extras are not copied by [copy] but they are moved with [atomicMove].
+ *
+ * @throws IOException if [path] does not exist.
+ */
+ @Throws(IOException::class)
+ fun <T : Any> setExtra(path: Path, type: KClass<out T>, value: T?) {
+ val canonicalPath = canonicalizeInternal(path)
+ val lookupResult = lookupPath(
+ canonicalPath = canonicalPath,
+ createRootOnDemand = canonicalPath.isRoot,
+ resolveLastSymlink = false,
+ )
+ val element = lookupResult?.element ?: throw FileNotFoundException("no such file: $path")
+ if (value == null) {
+ element.extras.remove(type)
+ } else {
+ element.extras[type] = value
+ }
+ }
+
+ override fun metadataOrNull(path: Path): FileMetadata? {
+ val canonicalPath = canonicalizeInternal(path)
+ val lookupResult = lookupPath(
+ canonicalPath = canonicalPath,
+ createRootOnDemand = canonicalPath.isRoot,
+ resolveLastSymlink = false,
+ )
+ return lookupResult?.element?.metadata
+ }
+
+ override fun list(dir: Path): List<Path> = list(dir, throwOnFailure = true)!!
+
+ override fun listOrNull(dir: Path): List<Path>? = list(dir, throwOnFailure = false)
+
+ private fun list(dir: Path, throwOnFailure: Boolean): List<Path>? {
+ val canonicalPath = canonicalizeInternal(dir)
+ val lookupResult = lookupPath(canonicalPath)
+ if (lookupResult?.element == null) {
+ if (throwOnFailure) throw FileNotFoundException("no such directory: $dir") else return null
+ }
+ val element = lookupResult.element as? Directory
+ ?: if (throwOnFailure) throw IOException("not a directory: $dir") else return null
+
+ element.access(now = clock.now())
+ return element.children.keys.map { dir / it }.sorted()
+ }
+
+ override fun source(file: Path): Source {
+ val fileHandle = openReadOnly(file)
+ return fileHandle.source()
+ .also { fileHandle.close() }
+ }
+
+ override fun sink(file: Path, mustCreate: Boolean): Sink {
+ val fileHandle = open(file, readWrite = true, mustCreate = mustCreate)
+ fileHandle.resize(0L) // If the file already has data, get rid of it.
+ return fileHandle.sink()
+ .also { fileHandle.close() }
+ }
+
+ override fun appendingSink(file: Path, mustExist: Boolean): Sink {
+ val fileHandle = open(file, readWrite = true, mustExist = mustExist)
+ return fileHandle.appendingSink()
+ .also { fileHandle.close() }
+ }
+
+ override fun openReadOnly(file: Path): FileHandle {
+ return open(file, readWrite = false)
+ }
+
+ override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
+ return open(file, readWrite = true, mustCreate = mustCreate, mustExist = mustExist)
+ }
+
+ private fun open(
+ file: Path,
+ readWrite: Boolean,
+ mustCreate: Boolean = false,
+ mustExist: Boolean = false,
+ ): FileHandle {
+ require(!mustCreate || !mustExist) {
+ "Cannot require mustCreate and mustExist at the same time."
+ }
+
+ val canonicalPath = canonicalizeInternal(file)
+ val lookupResult = lookupPath(canonicalPath, createRootOnDemand = readWrite)
+ val now = clock.now()
+ val element: File
+ val operation: Operation
+
+ if (lookupResult?.element == null && mustExist) {
+ throw IOException("$file doesn't exist.")
+ }
+ if (lookupResult?.element != null && mustCreate) {
+ throw IOException("$file already exists.")
+ }
+
+ if (readWrite) {
+ // Note that this case is used for both write and read/write.
+ if (lookupResult?.element is Directory) {
+ throw IOException("destination is a directory: $file")
+ }
+ if (!allowWritesWhileWriting) {
+ findOpenFile(canonicalPath, operation = WRITE)?.let {
+ throw IOException("file is already open for writing $file", it.backtrace)
+ }
+ }
+ if (!allowReadsWhileWriting) {
+ findOpenFile(canonicalPath, operation = READ)?.let {
+ throw IOException("file is already open for reading $file", it.backtrace)
+ }
+ }
+
+ val parent = lookupResult?.parent
+ ?: throw FileNotFoundException("parent directory does not exist")
+ parent.access(now, true)
+
+ val existing = lookupResult.element
+ element = File(createdAt = existing?.createdAt ?: now)
+ parent.children[lookupResult.segment!!] = element
+ operation = WRITE
+
+ if (existing is File) {
+ element.data = existing.data
+ }
+ } else {
+ val existing = lookupResult?.element
+ ?: throw FileNotFoundException("no such file: $file")
+ element = existing as? File ?: throw IOException("not a file: $file")
+ operation = READ
+
+ if (!allowReadsWhileWriting) {
+ findOpenFile(canonicalPath, operation = WRITE)?.let {
+ throw IOException("file is already open for writing $file", it.backtrace)
+ }
+ }
+ }
+
+ element.access(now = clock.now(), modified = readWrite)
+
+ val openFile = OpenFile(canonicalPath, operation, Exception("file opened for $operation here"))
+ openFiles += openFile
+
+ return FakeFileHandle(
+ readWrite = readWrite,
+ openFile = openFile,
+ file = element,
+ )
+ }
+
+ override fun createDirectory(dir: Path, mustCreate: Boolean) {
+ val canonicalPath = canonicalizeInternal(dir)
+
+ val lookupResult = lookupPath(canonicalPath, createRootOnDemand = true)
+
+ if (canonicalPath.isRoot) {
+ // Looking it up was sufficient. Don't crash when creating roots that already exist.
+ return
+ }
+
+ if (mustCreate && lookupResult?.element != null) {
+ throw IOException("already exists: $dir")
+ }
+
+ val parentDirectory = lookupResult.requireParent()
+ parentDirectory.children[canonicalPath.nameBytes] = Directory(createdAt = clock.now())
+ }
+
+ override fun atomicMove(
+ source: Path,
+ target: Path,
+ ) {
+ val canonicalSource = canonicalizeInternal(source)
+ val canonicalTarget = canonicalizeInternal(target)
+
+ val targetLookupResult = lookupPath(canonicalTarget, createRootOnDemand = true)
+ val sourceLookupResult = lookupPath(canonicalSource, resolveLastSymlink = false)
+
+ // Universal constraints.
+ if (targetLookupResult?.element is Directory) {
+ throw IOException("target is a directory: $target")
+ }
+ val targetParent = targetLookupResult.requireParent()
+ if (!allowMovingOpenFiles) {
+ findOpenFile(canonicalSource)?.let {
+ throw IOException("source is open $source", it.backtrace)
+ }
+ findOpenFile(canonicalTarget)?.let {
+ throw IOException("target is open $target", it.backtrace)
+ }
+ }
+ if (!allowClobberingEmptyDirectories) {
+ if (sourceLookupResult?.element is Directory && targetLookupResult?.element is File) {
+ throw IOException("source is a directory and target is a file")
+ }
+ }
+
+ val sourceParent = sourceLookupResult.requireParent()
+ val removed = sourceParent.children.remove(canonicalSource.nameBytes)
+ ?: throw FileNotFoundException("source doesn't exist: $source")
+ targetParent.children[canonicalTarget.nameBytes] = removed
+ }
+
+ override fun delete(path: Path, mustExist: Boolean) {
+ val canonicalPath = canonicalizeInternal(path)
+
+ val lookupResult = lookupPath(
+ canonicalPath = canonicalPath,
+ createRootOnDemand = true,
+ resolveLastSymlink = false,
+ )
+
+ if (lookupResult?.element == null) {
+ if (mustExist) {
+ throw FileNotFoundException("no such file: $path")
+ } else {
+ return
+ }
+ }
+
+ if (lookupResult.element is Directory && lookupResult.element.children.isNotEmpty()) {
+ throw IOException("non-empty directory")
+ }
+
+ if (!allowDeletingOpenFiles) {
+ findOpenFile(canonicalPath)?.let {
+ throw IOException("file is open $path", it.backtrace)
+ }
+ }
+
+ val directory = lookupResult.requireParent()
+ directory.children.remove(canonicalPath.nameBytes)
+ }
+
+ override fun createSymlink(
+ source: Path,
+ target: Path,
+ ) {
+ val canonicalSource = canonicalizeInternal(source)
+
+ val existingLookupResult = lookupPath(canonicalSource, createRootOnDemand = true)
+ if (existingLookupResult?.element != null) {
+ throw IOException("already exists: $source")
+ }
+ val parent = existingLookupResult.requireParent()
+
+ if (!allowSymlinks) {
+ throw IOException("symlinks are not supported")
+ }
+
+ parent.children[canonicalSource.nameBytes] = Symlink(createdAt = clock.now(), target)
+ }
+
+ /**
+ * Walks the file system looking for [canonicalPath], following symlinks encountered along the
+ * way. This function is designed to be used both when looking up existing files and when creating
+ * new files into an existing directory.
+ *
+ * It returns either:
+ *
+ * * a path lookup result with an element if that file or directory or symlink exists. This is
+ * useful when reading or writing an existing fie.
+ *
+ * * a path lookup result that only got as far as the canonical path's parent, if the parent
+ * exists but the child file does not. This is useful when creating a new file.
+ *
+ * * null, if not even the parent directory exists. A file cannot yet be created with this path
+ * because there is no parent to attach it to.
+ *
+ * This will create the root of the returned path if it does not exist.
+ *
+ * @param canonicalPath a normalized path, typically the result of [FakeFileSystem.canonicalizeInternal].
+ * @param recurseCount used internally to detect cycles.
+ * @param resolveLastSymlink true if the result's element must not itself be a symlink. Use this
+ * for looking up metadata, or operations that apply to the path like delete and move. We
+ * always follow symlinks for enclosing directories.
+ * @param createRootOnDemand true to create a root directory like `C:\` or `/` if it doesn't
+ * exist. Pass true for mutating operations.
+ */
+ private fun lookupPath(
+ canonicalPath: Path,
+ recurseCount: Int = 0,
+ resolveLastSymlink: Boolean = true,
+ createRootOnDemand: Boolean = false,
+ ): PathLookupResult? {
+ // 40 is chosen for consistency with the Linux kernel (which previously used 8).
+ if (recurseCount > 40) {
+ throw IOException("symlink cycle?")
+ }
+
+ val rootPath = canonicalPath.root!!
+ var root = roots[rootPath]
+
+ // If the path is a root, create it on demand.
+ if (root == null) {
+ if (!createRootOnDemand) return null
+ root = Directory(createdAt = clock.now())
+ roots[rootPath] = root
+ }
+
+ var parent: Directory? = null
+ var lastSegment: ByteString? = null
+ var current: Element = root
+ var currentPath: Path = rootPath
+
+ var segmentsTraversed = 0
+ val segments = canonicalPath.segmentsBytes
+ for (segment in segments) {
+ lastSegment = segment
+
+ // Push the newest segment.
+ if (current !is Directory) {
+ throw IOException("not a directory: $currentPath")
+ }
+ parent = current
+ current = current.children[segment] ?: break
+ currentPath /= segment
+ segmentsTraversed++
+
+ // If it's a symlink, recurse to follow it.
+ val isLastSegment = segmentsTraversed == segments.size
+ val followSymlinks = !isLastSegment || resolveLastSymlink
+ if (current is Symlink && followSymlinks) {
+ current.access(now = clock.now())
+ // We wanna normalize it in case the target is relative and starts with `..`.
+ currentPath = currentPath.parent!!.resolve(current.target, normalize = true)
+ val symlinkLookupResult = lookupPath(
+ canonicalPath = currentPath,
+ recurseCount = recurseCount + 1,
+ createRootOnDemand = createRootOnDemand,
+ ) ?: break
+ parent = symlinkLookupResult.parent
+ lastSegment = symlinkLookupResult.segment
+ current = symlinkLookupResult.element ?: break
+ currentPath = symlinkLookupResult.path
+ }
+ }
+
+ return when (segmentsTraversed) {
+ segments.size -> {
+ PathLookupResult(currentPath, parent, lastSegment, current) // The file.
+ }
+ segments.size - 1 -> {
+ PathLookupResult(currentPath, parent, lastSegment, null) // The enclosing directory.
+ }
+ else -> null // We found nothing.
+ }
+ }
+
+ private class PathLookupResult(
+ /** The canonical path for the looked up path or its enclosing directory. */
+ val path: Path,
+ /** Only null if the looked up path is a root. */
+ val parent: Directory?,
+ /** Only null if the looked up path is a root. */
+ val segment: ByteString?,
+ /** Non-null if this is a root. Also not null if this file exists. */
+ val element: Element?,
+ )
+
+ private fun PathLookupResult?.requireParent(): Directory {
+ return this?.parent ?: throw IOException("parent directory does not exist")
+ }
+
+ private sealed class Element(
+ val createdAt: Instant,
+ ) {
+ var lastModifiedAt: Instant = createdAt
+ var lastAccessedAt: Instant = createdAt
+ val extras = mutableMapOf<KClass<*>, Any>()
+
+ class File(createdAt: Instant) : Element(createdAt) {
+ var data: ByteString = ByteString.EMPTY
+
+ override val metadata: FileMetadata
+ get() = FileMetadata(
+ isRegularFile = true,
+ size = data.size.toLong(),
+ createdAt = createdAt,
+ lastModifiedAt = lastModifiedAt,
+ lastAccessedAt = lastAccessedAt,
+ extras = extras,
+ )
+ }
+
+ class Directory(createdAt: Instant) : Element(createdAt) {
+ /** Keys are path segments. */
+ val children = mutableMapOf<ByteString, Element>()
+
+ override val metadata: FileMetadata
+ get() = FileMetadata(
+ isDirectory = true,
+ createdAt = createdAt,
+ lastModifiedAt = lastModifiedAt,
+ lastAccessedAt = lastAccessedAt,
+ extras = extras,
+ )
+ }
+
+ class Symlink(
+ createdAt: Instant,
+ /** This may be an absolute or relative path. */
+ val target: Path,
+ ) : Element(createdAt) {
+ override val metadata: FileMetadata
+ get() = FileMetadata(
+ symlinkTarget = target,
+ createdAt = createdAt,
+ lastModifiedAt = lastModifiedAt,
+ lastAccessedAt = lastAccessedAt,
+ extras = extras,
+ )
+ }
+
+ fun access(
+ now: Instant,
+ modified: Boolean = false,
+ ) {
+ lastAccessedAt = now
+ if (modified) {
+ lastModifiedAt = now
+ }
+ }
+
+ abstract val metadata: FileMetadata
+ }
+
+ private fun findOpenFile(
+ canonicalPath: Path,
+ operation: Operation? = null,
+ ): OpenFile? {
+ return openFiles.firstOrNull {
+ it.canonicalPath == canonicalPath && (operation == null || operation == it.operation)
+ }
+ }
+
+ private fun checkOffsetAndCount(
+ size: Long,
+ offset: Long,
+ byteCount: Long,
+ ) {
+ if (offset or byteCount < 0 || offset > size || size - offset < byteCount) {
+ throw ArrayIndexOutOfBoundsException("size=$size offset=$offset byteCount=$byteCount")
+ }
+ }
+
+ private class OpenFile(
+ val canonicalPath: Path,
+ val operation: Operation,
+ val backtrace: Throwable,
+ )
+
+ private enum class Operation {
+ READ,
+ WRITE,
+ }
+
+ private inner class FakeFileHandle(
+ readWrite: Boolean,
+ private val openFile: OpenFile,
+ private val file: File,
+ ) : FileHandle(readWrite) {
+ private var closed = false
+
+ override fun protectedResize(size: Long) {
+ check(!closed) { "closed" }
+
+ val delta = size - file.data.size
+ if (delta > 0) {
+ file.data = Buffer()
+ .write(file.data)
+ .write(ByteArray(delta.toInt()))
+ .readByteString()
+ } else {
+ file.data = file.data.substring(0, size.toInt())
+ }
+
+ file.access(now = clock.now(), modified = true)
+ }
+
+ override fun protectedSize(): Long {
+ check(!closed) { "closed" }
+ return file.data.size.toLong()
+ }
+
+ override fun protectedRead(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ): Int {
+ check(!closed) { "closed" }
+ checkOffsetAndCount(array.size.toLong(), arrayOffset.toLong(), byteCount.toLong())
+
+ val fileOffsetInt = fileOffset.toInt()
+ val toCopy = minOf(file.data.size - fileOffsetInt, byteCount)
+ if (toCopy <= 0) return -1
+ for (i in 0 until toCopy) {
+ array[i + arrayOffset] = file.data[i + fileOffsetInt]
+ }
+ return toCopy
+ }
+
+ override fun protectedWrite(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ) {
+ check(!closed) { "closed" }
+ checkOffsetAndCount(array.size.toLong(), arrayOffset.toLong(), byteCount.toLong())
+
+ val buffer = Buffer()
+ buffer.write(file.data, 0, minOf(fileOffset.toInt(), file.data.size))
+ while (buffer.size < fileOffset) {
+ buffer.writeByte(0)
+ }
+ buffer.write(array, arrayOffset, byteCount)
+ if (buffer.size < file.data.size) {
+ buffer.write(file.data, buffer.size.toInt(), file.data.size - buffer.size.toInt())
+ }
+ file.data = buffer.snapshot()
+ file.access(now = clock.now(), modified = true)
+ }
+
+ override fun protectedFlush() {
+ check(!closed) { "closed" }
+ }
+
+ override fun protectedClose() {
+ if (closed) return
+ closed = true
+ file.access(now = clock.now(), modified = readWrite)
+ openFiles -= openFile
+ }
+
+ override fun toString() = "FileHandler(${openFile.canonicalPath})"
+ }
+
+ override fun toString() = "FakeFileSystem"
+}
diff --git a/okio-fakefilesystem/src/commonMain/kotlin/okio/fakefilesystem/FileMetadataCommon.kt b/okio-fakefilesystem/src/commonMain/kotlin/okio/fakefilesystem/FileMetadataCommon.kt
new file mode 100644
index 00000000..303e12a9
--- /dev/null
+++ b/okio-fakefilesystem/src/commonMain/kotlin/okio/fakefilesystem/FileMetadataCommon.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("-Time")
+
+package okio.fakefilesystem
+
+import kotlin.jvm.JvmName
+import kotlin.reflect.KClass
+import kotlinx.datetime.Instant
+import okio.FileMetadata
+import okio.Path
+
+@JvmName("newFileMetadata")
+internal fun FileMetadata(
+ isRegularFile: Boolean = false,
+ isDirectory: Boolean = false,
+ symlinkTarget: Path? = null,
+ size: Long? = null,
+ createdAt: Instant? = null,
+ lastModifiedAt: Instant? = null,
+ lastAccessedAt: Instant? = null,
+ extras: Map<KClass<*>, Any> = mapOf(),
+): FileMetadata {
+ return FileMetadata(
+ isRegularFile = isRegularFile,
+ isDirectory = isDirectory,
+ symlinkTarget = symlinkTarget,
+ size = size,
+ createdAtMillis = createdAt?.toEpochMilliseconds(),
+ lastModifiedAtMillis = lastModifiedAt?.toEpochMilliseconds(),
+ lastAccessedAtMillis = lastAccessedAt?.toEpochMilliseconds(),
+ extras = extras,
+ )
+}
diff --git a/okio-nodefilesystem/build.gradle.kts b/okio-nodefilesystem/build.gradle.kts
new file mode 100644
index 00000000..0509e4e5
--- /dev/null
+++ b/okio-nodefilesystem/build.gradle.kts
@@ -0,0 +1,63 @@
+import com.vanniktech.maven.publish.JavadocJar.Dokka
+import com.vanniktech.maven.publish.KotlinJs
+import com.vanniktech.maven.publish.MavenPublishBaseExtension
+
+plugins {
+ kotlin("js")
+ id("org.jetbrains.dokka")
+ id("com.vanniktech.maven.publish.base")
+ id("binary-compatibility-validator")
+}
+
+kotlin {
+ js {
+ configure(listOf(compilations.getByName("main"), compilations.getByName("test"))) {
+ tasks.getByName(compileKotlinTaskName) {
+ kotlinOptions {
+ moduleKind = "umd"
+ sourceMap = true
+ metaInfo = true
+ }
+ }
+ }
+ nodejs {
+ testTask {
+ useMocha {
+ timeout = "30s"
+ }
+ }
+ }
+ }
+ sourceSets {
+ all {
+ languageSettings.optIn("kotlin.RequiresOptIn")
+ }
+ matching { it.name.endsWith("Test") }.all {
+ languageSettings {
+ optIn("kotlin.time.ExperimentalTime")
+ }
+ }
+ val main by getting {
+ dependencies {
+ implementation(projects.okio)
+ // Uncomment this to generate fs.fs.module_node.kt. Use it when updating fs.kt.
+ // implementation(npm("@types/node", "14.14.16", true))
+ }
+ }
+ val test by getting {
+ dependencies {
+ implementation(libs.kotlin.test)
+ implementation(libs.kotlin.time)
+
+ implementation(projects.okioFakefilesystem)
+ implementation(projects.okioTestingSupport)
+ }
+ }
+ }
+}
+
+configure<MavenPublishBaseExtension> {
+ configure(
+ KotlinJs(javadocJar = Dokka("dokkaGfm"))
+ )
+}
diff --git a/okio-nodefilesystem/src/main/kotlin/okio/FileSink.kt b/okio-nodefilesystem/src/main/kotlin/okio/FileSink.kt
new file mode 100644
index 00000000..904adc63
--- /dev/null
+++ b/okio-nodefilesystem/src/main/kotlin/okio/FileSink.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+internal class FileSink(
+ private val fd: Number,
+) : Sink {
+ private var closed = false
+
+ override fun write(source: Buffer, byteCount: Long) {
+ require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
+ require(source.size >= byteCount) { "source.size=${source.size} < byteCount=$byteCount" }
+ check(!closed) { "closed" }
+
+ val data = source.readByteArray(byteCount)
+ val writtenByteCount = writeSync(fd, data)
+ if (writtenByteCount.toLong() != byteCount) {
+ throw IOException("expected $byteCount but was $writtenByteCount")
+ }
+ }
+
+ override fun flush() {
+ }
+
+ override fun timeout(): Timeout {
+ return Timeout.NONE
+ }
+
+ override fun close() {
+ if (closed) return
+ closed = true
+ closeSync(fd)
+ }
+}
diff --git a/okio-nodefilesystem/src/main/kotlin/okio/FileSource.kt b/okio-nodefilesystem/src/main/kotlin/okio/FileSource.kt
new file mode 100644
index 00000000..a363a9cc
--- /dev/null
+++ b/okio-nodefilesystem/src/main/kotlin/okio/FileSource.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+internal class FileSource(
+ private val fd: Number,
+) : Source {
+ private var position_ = 0L
+ private var closed = false
+
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
+ check(!closed) { "closed" }
+
+ val data = ByteArray(byteCount.toInt())
+ val readByteCount = readSync(
+ fd = fd,
+ buffer = data,
+ length = byteCount.toDouble(),
+ offset = 0.0,
+ position = position_.toDouble(),
+ ).toInt()
+
+ if (readByteCount == 0) return -1L
+
+ position_ += readByteCount
+
+ sink.write(data, offset = 0, byteCount = readByteCount)
+
+ return readByteCount.toLong()
+ }
+
+ override fun timeout(): Timeout = Timeout.NONE
+
+ override fun close() {
+ if (closed) return
+ closed = true
+ closeSync(fd)
+ }
+}
diff --git a/okio-nodefilesystem/src/main/kotlin/okio/FsJs.kt b/okio-nodefilesystem/src/main/kotlin/okio/FsJs.kt
new file mode 100644
index 00000000..2a308f06
--- /dev/null
+++ b/okio-nodefilesystem/src/main/kotlin/okio/FsJs.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This class declares the subset of Node.js file system APIs that we need in Okio.
+ *
+ *
+ * Why not Dukat?
+ * --------------
+ *
+ * This file does manually what ideally [Dukat] would do automatically.
+ *
+ * Dukat's generated stubs need awkward call sites to disambiguate overloads. For example, to call
+ * `mkdirSync()` we must specify an options parameter even though we just want the default:
+ *
+ * mkdirSync(dir.toString(), options = undefined as MakeDirectoryOptions?)
+ *
+ * By defining our own externals, we can omit the unwanted optional parameter from the declaration.
+ * This leads to nicer calling code!
+ *
+ * mkdirSync(dir.toString())
+ *
+ * Dukat also gets the nullability wrong for `Dirent.readSync()`.
+ *
+ *
+ * Why not Kotlinx-nodejs?
+ * -----------------------
+ *
+ * Even better than using Dukat directly would be to use the [official artifact][kotlinx_nodejs],
+ * itself generated with Dukat. We also don't use the official Node.js artifact for the reasons
+ * above, and also because it has an unstable API.
+ *
+ *
+ * Updating this file
+ * ------------------
+ *
+ * To declare new external APIs, run Dukat to generate a full set of Node stubs. The easiest way to
+ * do this is to add an NPM dependency on `@types/node` in `jsMain`, like this:
+ *
+ * ```
+ * jsMain {
+ * ...
+ * dependencies {
+ * implementation(npm("@types/node", "14.14.16", true))
+ * ...
+ * }
+ * }
+ * ```
+ *
+ * This will create a file with a full set of APIs to copy-paste from.
+ *
+ * ```
+ * okio/build/externals/okio-parent-okio/src/fs.fs.module_node.kt
+ * ```
+ *
+ * [Dukat]: https://github.com/kotlin/dukat
+ * [kotlinx_nodejs]: https://github.com/Kotlin/kotlinx-nodejs
+ */
+@file:JsModule("fs")
+@file:JsNonModule
+
+package okio
+
+import kotlin.js.Date
+
+internal external fun closeSync(fd: Number)
+
+internal external fun mkdirSync(path: String): String?
+
+internal external fun openSync(path: String, flags: String): Double
+
+internal external fun opendirSync(path: String): Dir
+
+internal external fun readlinkSync(path: String): String
+
+internal external fun readSync(fd: Number, buffer: ByteArray, offset: Double, length: Double, position: Double?): Double
+
+internal external fun realpathSync(path: String): String
+
+internal external fun renameSync(oldPath: String, newPath: String)
+
+internal external fun rmdirSync(path: String)
+
+internal external fun lstatSync(path: String): Stats
+
+internal external fun fstatSync(fd: Number): Stats
+
+internal external fun unlinkSync(path: String)
+
+internal external fun writeSync(fd: Number, buffer: ByteArray): Double
+
+internal external fun writeSync(fd: Number, buffer: ByteArray, offset: Double, length: Double, position: Double): Double
+
+internal external fun ftruncateSync(fd: Number, len: Double)
+
+internal external fun symlinkSync(target: String, path: String)
+
+internal open external class Dir {
+ open var path: String
+ open fun closeSync()
+
+ // Note that dukat's signature of readSync() returns a non-nullable Dirent; that's incorrect.
+ open fun readSync(): Dirent?
+}
+
+internal open external class Dirent {
+ open fun isFile(): Boolean
+ open fun isDirectory(): Boolean
+ open fun isBlockDevice(): Boolean
+ open fun isCharacterDevice(): Boolean
+ open fun isSymbolicLink(): Boolean
+ open fun isFIFO(): Boolean
+ open fun isSocket(): Boolean
+ open var name: String
+}
+
+internal external interface StatsBase<T> {
+ fun isFile(): Boolean
+ fun isDirectory(): Boolean
+ fun isBlockDevice(): Boolean
+ fun isCharacterDevice(): Boolean
+ fun isSymbolicLink(): Boolean
+ fun isFIFO(): Boolean
+ fun isSocket(): Boolean
+ var dev: T
+ var ino: T
+ var mode: T
+ var nlink: T
+ var uid: T
+ var gid: T
+ var rdev: T
+ var size: T
+ var blksize: T
+ var blocks: T
+ var atimeMs: T
+ var mtimeMs: T
+ var ctimeMs: T
+ var birthtimeMs: T
+ var atime: Date
+ var mtime: Date
+ var ctime: Date
+ var birthtime: Date
+}
+
+internal open external class Stats : StatsBase<Number> {
+ override fun isFile(): Boolean
+ override fun isDirectory(): Boolean
+ override fun isBlockDevice(): Boolean
+ override fun isCharacterDevice(): Boolean
+ override fun isSymbolicLink(): Boolean
+ override fun isFIFO(): Boolean
+ override fun isSocket(): Boolean
+ override var dev: Number
+ override var ino: Number
+ override var mode: Number
+ override var nlink: Number
+ override var uid: Number
+ override var gid: Number
+ override var rdev: Number
+ override var size: Number
+ override var blksize: Number
+ override var blocks: Number
+ override var atimeMs: Number
+ override var mtimeMs: Number
+ override var ctimeMs: Number
+ override var birthtimeMs: Number
+ override var atime: Date
+ override var mtime: Date
+ override var ctime: Date
+ override var birthtime: Date
+}
diff --git a/okio-nodefilesystem/src/main/kotlin/okio/NodeJsFileHandle.kt b/okio-nodefilesystem/src/main/kotlin/okio/NodeJsFileHandle.kt
new file mode 100644
index 00000000..4b40d41b
--- /dev/null
+++ b/okio-nodefilesystem/src/main/kotlin/okio/NodeJsFileHandle.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+internal class NodeJsFileHandle(
+ private val fd: Number,
+ readWrite: Boolean,
+) : FileHandle(readWrite) {
+ override fun protectedSize(): Long {
+ val stats = fstatSync(fd)
+ return stats.size.toLong()
+ }
+
+ override fun protectedRead(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ): Int {
+ val readByteCount = readSync(
+ fd = fd,
+ buffer = array,
+ length = byteCount.toDouble(),
+ offset = arrayOffset.toDouble(),
+ position = fileOffset.toDouble(),
+ ).toInt()
+
+ if (readByteCount == 0) return -1
+
+ return readByteCount
+ }
+
+ override fun protectedWrite(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ) {
+ val writtenByteCount = writeSync(
+ fd = fd,
+ buffer = array,
+ offset = arrayOffset.toDouble(),
+ length = byteCount.toDouble(),
+ position = fileOffset.toDouble(),
+ )
+
+ if (writtenByteCount.toInt() != byteCount) {
+ throw IOException("expected $byteCount but was $writtenByteCount")
+ }
+ }
+
+ override fun protectedFlush() {
+ }
+
+ override fun protectedResize(size: Long) {
+ ftruncateSync(fd, size.toDouble())
+ }
+
+ override fun protectedClose() {
+ closeSync(fd)
+ }
+}
diff --git a/okio-nodefilesystem/src/main/kotlin/okio/NodeJsFileSystem.kt b/okio-nodefilesystem/src/main/kotlin/okio/NodeJsFileSystem.kt
new file mode 100644
index 00000000..2c7cf418
--- /dev/null
+++ b/okio-nodefilesystem/src/main/kotlin/okio/NodeJsFileSystem.kt
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import okio.Path.Companion.toPath
+
+/**
+ * Use [Node.js APIs][node_fs] to implement the Okio file system interface.
+ *
+ * This class needs to make calls to some fs APIs that have multiple competing overloads. To
+ * unambiguously select an overload this passes `undefined` as the target type to some functions.
+ *
+ * [node_fs]: https://nodejs.org/dist/latest-v14.x/docs/api/fs.html
+ */
+object NodeJsFileSystem : FileSystem() {
+ private var S_IFMT = 0xf000 // fs.constants.S_IFMT
+ private var S_IFREG = 0x8000 // fs.constants.S_IFREG
+ private var S_IFDIR = 0x4000 // fs.constants.S_IFDIR
+ private var S_IFLNK = 0xa000 // fs.constants.S_IFLNK
+
+ override fun canonicalize(path: Path): Path {
+ try {
+ val canonicalPath = realpathSync(path.toString())
+ return canonicalPath.toPath()
+ } catch (e: Throwable) {
+ throw e.toIOException()
+ }
+ }
+
+ override fun metadataOrNull(path: Path): FileMetadata? {
+ val pathString = path.toString()
+ val stat = try {
+ lstatSync(pathString)
+ } catch (e: Throwable) {
+ if (e.errorCode == "ENOENT") return null // "No such file or directory".
+ throw IOException(e.message)
+ }
+
+ var symlinkTarget: Path? = null
+ if ((stat.mode.toInt() and S_IFMT) == S_IFLNK) {
+ try {
+ symlinkTarget = readlinkSync(pathString).toPath()
+ } catch (e: Throwable) {
+ throw e.toIOException()
+ }
+ }
+
+ return FileMetadata(
+ isRegularFile = (stat.mode.toInt() and S_IFMT) == S_IFREG,
+ isDirectory = (stat.mode.toInt() and S_IFMT) == S_IFDIR,
+ symlinkTarget = symlinkTarget,
+ size = stat.size.toLong(),
+ createdAtMillis = stat.birthtimeMs.toLong(),
+ lastModifiedAtMillis = stat.mtimeMs.toLong(),
+ lastAccessedAtMillis = stat.atimeMs.toLong(),
+ )
+ }
+
+ /**
+ * Returns the error code on this `SystemError`. This uses `asDynamic()` because our JS bindings
+ * don't (yet) include the `SystemError` type.
+ *
+ * https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_class_systemerror
+ * https://nodejs.org/dist/latest-v14.x/docs/api/errors.html#errors_common_system_errors
+ */
+ private val Throwable.errorCode
+ get() = asDynamic().code
+
+ override fun list(dir: Path): List<Path> = list(dir, throwOnFailure = true)!!
+
+ override fun listOrNull(dir: Path): List<Path>? = list(dir, throwOnFailure = false)
+
+ private fun list(dir: Path, throwOnFailure: Boolean): List<Path>? {
+ try {
+ val opendir = opendirSync(dir.toString())
+ try {
+ val result = mutableListOf<Path>()
+ while (true) {
+ val dirent = opendir.readSync() ?: break
+ result += dir / dirent.name
+ }
+ result.sort()
+ return result
+ } finally {
+ opendir.closeSync()
+ }
+ } catch (e: Throwable) {
+ if (throwOnFailure) {
+ throw e.toIOException()
+ } else {
+ return null
+ }
+ }
+ }
+
+ override fun openReadOnly(file: Path): FileHandle {
+ val fd = openFd(file, flags = "r")
+ return NodeJsFileHandle(fd, readWrite = false)
+ }
+
+ override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
+ require(!mustCreate || !mustExist) {
+ "Cannot require mustCreate and mustExist at the same time."
+ }
+ val fd = if (Path.DIRECTORY_SEPARATOR == "\\") {
+ // On NodeJS on Windows there's no file system flag that does all of the following:
+ // - open a file for reading, writing, seeking, and resizing
+ // - create it doesn't exist
+ // - do not truncate it if it does exist
+ // Work around this by attempting to open a file that does exist (r+), falling back to
+ // creating a file that does not exist (wx+) if that throws. This is not atomic.
+ // https://nodejs.org/api/fs.html#fs_file_system_flags
+ try {
+ if (mustCreate && exists(file)) throw IOException("$file already exists.")
+ openFd(file, "r+")
+ } catch (e: FileNotFoundException) {
+ if (mustExist) throw IOException("$file doesn't exist.")
+ openFd(file, "wx+")
+ }
+ } else {
+ // Note that on Linux, positional writes don't work when the file is opened in append mode, so
+ // we don't want to use the `a` flag,
+ val flags = when {
+ mustCreate -> "wx+"
+ mustExist || exists(file) -> "r+"
+ else -> "w+"
+ }
+ openFd(file, flags)
+ }
+ return NodeJsFileHandle(fd, readWrite = true)
+ }
+
+ override fun source(file: Path): Source {
+ val fd = openFd(file, flags = "r")
+ return FileSource(fd)
+ }
+
+ override fun sink(file: Path, mustCreate: Boolean): Sink {
+ val fd = openFd(file, flags = if (mustCreate) "wx" else "w")
+ return FileSink(fd)
+ }
+
+ override fun appendingSink(file: Path, mustExist: Boolean): Sink {
+ // There is a `r+` flag which we could have used to force existence of [file] but this flag
+ // doesn't allow opening for appending, and we don't currently have a way to move the cursor to
+ // the end of the file. We are then forcing existence non-atomically.
+ if (mustExist && !exists(file)) throw IOException("$file doesn't exist.")
+ val fd = openFd(file, flags = "a")
+ return FileSink(fd)
+ }
+
+ private fun openFd(file: Path, flags: String): Double {
+ try {
+ return openSync(file.toString(), flags = flags)
+ } catch (e: Throwable) {
+ throw e.toIOException()
+ }
+ }
+
+ override fun createDirectory(dir: Path, mustCreate: Boolean) {
+ try {
+ mkdirSync(dir.toString())
+ } catch (e: Throwable) {
+ val alreadyExist = metadataOrNull(dir)?.isDirectory == true
+ if (alreadyExist) {
+ if (mustCreate) {
+ throw IOException("$dir already exist.")
+ } else {
+ return
+ }
+ }
+
+ throw e.toIOException()
+ }
+ }
+
+ override fun atomicMove(source: Path, target: Path) {
+ try {
+ renameSync(source.toString(), target.toString())
+ } catch (e: Throwable) {
+ throw e.toIOException()
+ }
+ }
+
+ /**
+ * We don't know if [path] is a file or a directory, but we don't (yet) have an API to delete
+ * either type. Just try each in sequence.
+ *
+ * TODO(jwilson): switch to fs.rmSync() when our minimum requirements are Node 14.14.0.
+ */
+ override fun delete(path: Path, mustExist: Boolean) {
+ try {
+ unlinkSync(path.toString())
+ return
+ } catch (e: Throwable) {
+ }
+ try {
+ rmdirSync(path.toString())
+ } catch (e: Throwable) {
+ if (e.errorCode == "ENOENT") {
+ if (mustExist) {
+ throw FileNotFoundException("no such file: $path")
+ } else {
+ return
+ }
+ }
+ throw e.toIOException()
+ }
+ }
+
+ override fun createSymlink(source: Path, target: Path) {
+ if (source.parent == null || !exists(source.parent!!)) {
+ throw IOException("parent directory does not exist: ${source.parent}")
+ }
+
+ if (exists(source)) {
+ throw IOException("already exists: $source")
+ }
+
+ symlinkSync(target.toString(), source.toString())
+ }
+
+ private fun Throwable.toIOException(): IOException {
+ return when (errorCode) {
+ "ENOENT" -> FileNotFoundException(message)
+ else -> IOException(message)
+ }
+ }
+
+ override fun toString() = "NodeJsSystemFileSystem"
+}
diff --git a/okio-nodefilesystem/src/test/kotlin/okio/NodeJsFileSystemTest.kt b/okio-nodefilesystem/src/test/kotlin/okio/NodeJsFileSystemTest.kt
new file mode 100644
index 00000000..3afc3ed6
--- /dev/null
+++ b/okio-nodefilesystem/src/test/kotlin/okio/NodeJsFileSystemTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.datetime.Clock
+
+class NodeJsFileSystemTest : AbstractFileSystemTest(
+ clock = Clock.System,
+ fileSystem = NodeJsFileSystem,
+ windowsLimitations = Path.DIRECTORY_SEPARATOR == "\\",
+ allowClobberingEmptyDirectories = Path.DIRECTORY_SEPARATOR == "\\",
+ allowAtomicMoveFromFileToDirectory = false,
+ temporaryDirectory = FileSystem.SYSTEM_TEMPORARY_DIRECTORY,
+)
diff --git a/okio-testing-support/README.md b/okio-testing-support/README.md
new file mode 100644
index 00000000..f5146211
--- /dev/null
+++ b/okio-testing-support/README.md
@@ -0,0 +1,5 @@
+Okio Testing Support
+====================
+
+This module offers utilities and support for testing Okio itself. It's not intended for use by
+other projects or consumers of the Okio library.
diff --git a/okio-testing-support/build.gradle.kts b/okio-testing-support/build.gradle.kts
new file mode 100644
index 00000000..bdf3506f
--- /dev/null
+++ b/okio-testing-support/build.gradle.kts
@@ -0,0 +1,61 @@
+plugins {
+ kotlin("multiplatform")
+ id("build-support")
+}
+
+kotlin {
+ configureOrCreateOkioPlatforms()
+
+ sourceSets {
+ all {
+ languageSettings.apply {
+ optIn("kotlin.time.ExperimentalTime")
+ }
+ }
+
+ val commonMain by getting {
+ dependencies {
+ api(projects.okio)
+ api(libs.kotlin.test)
+ }
+ }
+
+ val nonWasmMain by creating {
+ dependsOn(commonMain)
+ dependencies {
+ api(libs.kotlin.time)
+ implementation(projects.okioFakefilesystem)
+ }
+ }
+
+ if (kmpJsEnabled) {
+ val jsMain by getting {
+ dependsOn(nonWasmMain)
+ }
+ }
+
+ val jvmMain by getting {
+ dependsOn(nonWasmMain)
+ dependencies {
+ // On the JVM the kotlin-test library resolves to one of three implementations based on
+ // which testing framework is in use. JUnit is used downstream, but Gradle can't know that
+ // here and thus fails to select a variant automatically. Declare it manually instead.
+ api(libs.kotlin.test.junit)
+ }
+ }
+
+ if (kmpNativeEnabled) {
+ createSourceSet("nativeMain", children = nativeTargets)
+ .also { nativeMain ->
+ nativeMain.dependsOn(nonWasmMain)
+ }
+ }
+
+ if (kmpWasmEnabled) {
+ createSourceSet("wasmMain", children = wasmTargets)
+ .also { wasmMain ->
+ wasmMain.dependsOn(commonMain)
+ }
+ }
+ }
+}
diff --git a/okio-testing-support/src/commonMain/kotlin/okio/AbstractFileSystemTest.kt b/okio-testing-support/src/commonMain/kotlin/okio/AbstractFileSystemTest.kt
new file mode 100644
index 00000000..31ec6cba
--- /dev/null
+++ b/okio-testing-support/src/commonMain/kotlin/okio/AbstractFileSystemTest.kt
@@ -0,0 +1,2651 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.test.BeforeTest
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.minutes
+import kotlin.time.Duration.Companion.seconds
+import okio.ByteString.Companion.encodeUtf8
+import okio.ByteString.Companion.toByteString
+import okio.Path.Companion.toPath
+
+/** This test assumes that okio-files/ is the current working directory when executed. */
+abstract class AbstractFileSystemTest(
+ val clock: Clock,
+ val fileSystem: FileSystem,
+ val windowsLimitations: Boolean,
+ val allowClobberingEmptyDirectories: Boolean,
+ val allowAtomicMoveFromFileToDirectory: Boolean,
+ val allowRenameWhenTargetIsOpen: Boolean = !windowsLimitations,
+ temporaryDirectory: Path,
+) {
+ val base: Path = temporaryDirectory / "${this::class.simpleName}-${randomToken(16)}"
+ private val isNodeJsFileSystem = fileSystem::class.simpleName?.startsWith("NodeJs") ?: false
+ private val isWasiFileSystem = fileSystem::class.simpleName?.startsWith("Wasi") ?: false
+ private val isWrappingJimFileSystem = this::class.simpleName?.contains("JimFileSystem") ?: false
+
+ @BeforeTest
+ fun setUp() {
+ fileSystem.createDirectories(base)
+ }
+
+ @Test
+ fun doesNotExistsWithInvalidPathDoesNotThrow() {
+ if (isNodeJsFileSystemOnWindows()) return
+
+ val slash = Path.DIRECTORY_SEPARATOR
+ // We are testing: `\\127.0.0.1\..\localhost\c$\Windows`.
+ val file =
+ "${slash}${slash}127.0.0.1$slash..${slash}localhost${slash}c\$${slash}Windows".toPath()
+
+ assertFalse(fileSystem.exists(file))
+ }
+
+ @Test
+ fun canonicalizeDotReturnsCurrentWorkingDirectory() {
+ if (fileSystem.isFakeFileSystem || fileSystem is ForwardingFileSystem) return
+ val cwd = fileSystem.canonicalize(".".toPath())
+ val cwdString = cwd.toString()
+ val slash = Path.DIRECTORY_SEPARATOR
+ assertTrue(cwdString) {
+ if (isWrappingJimFileSystem) {
+ cwdString.endsWith("work")
+ } else if (isWasiFileSystem) {
+ cwdString.endsWith("/tmp")
+ } else {
+ cwdString.endsWith("okio${slash}okio") ||
+ cwdString.endsWith("${slash}okio-parent-okio-nodefilesystem-test") ||
+ cwdString.contains("/CoreSimulator/Devices/") || // iOS simulator.
+ cwdString == "/" // Android emulator.
+ }
+ }
+ }
+
+ @Test
+ fun currentWorkingDirectoryIsADirectory() {
+ val metadata = fileSystem.metadata(".".toPath())
+ assertTrue(metadata.isDirectory)
+ assertFalse(metadata.isRegularFile)
+ }
+
+ @Test
+ fun canonicalizeAbsolutePathNoSuchFile() {
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.canonicalize(base / "no-such-file")
+ }
+ }
+
+ @Test
+ fun canonicalizeRelativePathNoSuchFile() {
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.canonicalize("no-such-file".toPath())
+ }
+ }
+
+ @Test
+ fun canonicalizeFollowsSymlinkDirectories() {
+ if (!supportsSymlink()) return
+ val base = fileSystem.canonicalize(base)
+
+ fileSystem.createDirectory(base / "real-directory")
+
+ val expected = base / "real-directory" / "real-file.txt"
+ expected.writeUtf8("hello")
+
+ fileSystem.createSymlink(base / "symlink-directory", base / "real-directory")
+
+ val canonicalPath = fileSystem.canonicalize(base / "symlink-directory" / "real-file.txt")
+ assertEquals(expected, canonicalPath)
+ }
+
+ @Test
+ fun canonicalizeFollowsSymlinkFiles() {
+ if (!supportsSymlink()) return
+ val base = fileSystem.canonicalize(base)
+
+ fileSystem.createDirectory(base / "real-directory")
+
+ val expected = base / "real-directory" / "real-file.txt"
+ expected.writeUtf8("hello")
+
+ fileSystem.createSymlink(
+ base / "real-directory" / "symlink-file.txt",
+ expected,
+ )
+
+ val canonicalPath = fileSystem.canonicalize(base / "real-directory" / "symlink-file.txt")
+ assertEquals(expected, canonicalPath)
+ }
+
+ @Test
+ fun canonicalizeFollowsMultipleDirectoriesAndMultipleFiles() {
+ if (!supportsSymlink()) return
+ val base = fileSystem.canonicalize(base)
+
+ fileSystem.createDirectory(base / "real-directory")
+
+ val expected = base / "real-directory" / "real-file.txt"
+ expected.writeUtf8("hello")
+
+ fileSystem.createSymlink(
+ base / "real-directory" / "one-symlink-file.txt",
+ expected,
+ )
+
+ fileSystem.createSymlink(
+ base / "real-directory" / "two-symlink-file.txt",
+ base / "real-directory" / "one-symlink-file.txt",
+ )
+
+ fileSystem.createSymlink(
+ base / "one-symlink-directory",
+ base / "real-directory",
+ )
+
+ fileSystem.createSymlink(
+ base / "two-symlink-directory",
+ base / "one-symlink-directory",
+ )
+
+ assertEquals(
+ expected,
+ fileSystem.canonicalize(base / "two-symlink-directory" / "two-symlink-file.txt"),
+ )
+ assertEquals(
+ expected,
+ fileSystem.canonicalize(base / "two-symlink-directory" / "one-symlink-file.txt"),
+ )
+ assertEquals(
+ expected,
+ fileSystem.canonicalize(base / "two-symlink-directory" / "real-file.txt"),
+ )
+
+ assertEquals(
+ expected,
+ fileSystem.canonicalize(base / "one-symlink-directory" / "two-symlink-file.txt"),
+ )
+ assertEquals(
+ expected,
+ fileSystem.canonicalize(base / "one-symlink-directory" / "one-symlink-file.txt"),
+ )
+ assertEquals(
+ expected,
+ fileSystem.canonicalize(base / "one-symlink-directory" / "real-file.txt"),
+ )
+
+ assertEquals(
+ expected,
+ fileSystem.canonicalize(base / "real-directory" / "two-symlink-file.txt"),
+ )
+ assertEquals(
+ expected,
+ fileSystem.canonicalize(base / "real-directory" / "one-symlink-file.txt"),
+ )
+ assertEquals(
+ expected,
+ fileSystem.canonicalize(expected),
+ )
+ }
+
+ @Test
+ fun canonicalizeReturnsDeeperPath() {
+ if (!supportsSymlink()) return
+ val base = fileSystem.canonicalize(base)
+
+ fileSystem.createDirectories(base / "a" / "b" / "c")
+
+ val expected = base / "a" / "b" / "c" / "d.txt"
+ expected.writeUtf8("hello")
+
+ fileSystem.createSymlink(
+ base / "e.txt",
+ "a".toPath() / "b" / "c" / "d.txt",
+ )
+
+ assertEquals(
+ expected,
+ fileSystem.canonicalize(base / "e.txt"),
+ )
+ }
+
+ @Test
+ fun canonicalizeReturnsShallowerPath() {
+ if (!supportsSymlink()) return
+ val base = fileSystem.canonicalize(base)
+
+ val expected = base / "a.txt"
+ expected.writeUtf8("hello")
+
+ fileSystem.createDirectories(base / "b" / "c" / "d")
+ fileSystem.createSymlink(
+ base / "b" / "c" / "d" / "e.txt",
+ "../".toPath() / ".." / ".." / "a.txt",
+ )
+
+ assertEquals(
+ expected,
+ fileSystem.canonicalize(base / "b" / "c" / "d" / "e.txt"),
+ )
+ }
+
+ @Test
+ fun list() {
+ val target = base / "list"
+ target.writeUtf8("hello, world!")
+ val entries = fileSystem.list(base)
+ assertTrue(entries.toString()) { target in entries }
+ }
+
+ @Test
+ fun listOnRelativePathReturnsRelativePaths() {
+ // Make sure there's always at least one file so our assertion is useful.
+ if (fileSystem.isFakeFileSystem) {
+ val workingDirectory = "/directory".toPath()
+ fileSystem.createDirectory(workingDirectory)
+ fileSystem.workingDirectory = workingDirectory
+ fileSystem.write("a.txt".toPath()) {
+ writeUtf8("hello, world!")
+ }
+ } else if (isWrappingJimFileSystem || isWasiFileSystem) {
+ fileSystem.write("a.txt".toPath()) {
+ writeUtf8("hello, world!")
+ }
+ }
+
+ val entries = fileSystem.list(".".toPath())
+ assertTrue(entries.toString()) { entries.isNotEmpty() && entries.all { it.isRelative } }
+ }
+
+ @Test
+ fun listOnRelativePathWhichIsNotDotReturnsRelativePaths() {
+ if (isNodeJsFileSystem) return
+
+ // Make sure there's always at least one file so our assertion is useful. We copy the first 2
+ // entries of the real working directory of the JVM to validate the results on all environment.
+ if (
+ fileSystem.isFakeFileSystem ||
+ fileSystem is ForwardingFileSystem && fileSystem.delegate.isFakeFileSystem
+ ) {
+ val workingDirectory = "/directory".toPath()
+ fileSystem.createDirectory(workingDirectory)
+ fileSystem.workingDirectory = workingDirectory
+ val apiDir = "api".toPath()
+ fileSystem.createDirectory(apiDir)
+ fileSystem.write(apiDir / "okio.api".toPath()) {
+ writeUtf8("hello, world!")
+ }
+ } else if (isWrappingJimFileSystem || isWasiFileSystem) {
+ val apiDir = "api".toPath()
+ fileSystem.createDirectory(apiDir)
+ fileSystem.write(apiDir / "okio.api".toPath()) {
+ writeUtf8("hello, world!")
+ }
+ }
+
+ try {
+ assertEquals(
+ listOf("api".toPath() / "okio.api".toPath()),
+ fileSystem.list("api".toPath()),
+ // List some entries to help debugging.
+ fileSystem.listRecursively(".".toPath()).take(5).toList().joinToString(),
+ )
+ } catch (e: Throwable) {
+ if (e !is AssertionError && e !is FileNotFoundException) { throw e }
+
+ // Non JVM environments.
+ val firstChild = fileSystem.list("Library".toPath()).first()
+ assertTrue(
+ // List some entries to help debugging.
+ fileSystem.listRecursively(".".toPath()).take(5).toList().joinToString(),
+ ) {
+ // To avoid relying too much on the environment we check that the path contains its parent
+ // once and that it's relative.
+ firstChild.isRelative &&
+ firstChild.toString().startsWith("Library") &&
+ firstChild.toString().split("Library").size == 2
+ }
+ }
+ }
+
+ @Test
+ fun listOrNullOnRelativePathWhichIsNotDotReturnsRelativePaths() {
+ if (isNodeJsFileSystem) return
+
+ // Make sure there's always at least one file so our assertion is useful. We copy the first 2
+ // entries of the real working directory of the JVM to validate the results on all environment.
+ if (
+ fileSystem.isFakeFileSystem ||
+ fileSystem is ForwardingFileSystem && fileSystem.delegate.isFakeFileSystem
+ ) {
+ val workingDirectory = "/directory".toPath()
+ fileSystem.createDirectory(workingDirectory)
+ fileSystem.workingDirectory = workingDirectory
+ val apiDir = "api".toPath()
+ fileSystem.createDirectory(apiDir)
+ fileSystem.write(apiDir / "okio.api".toPath()) {
+ writeUtf8("hello, world!")
+ }
+ } else if (isWrappingJimFileSystem) {
+ val apiDir = "api".toPath()
+ fileSystem.createDirectory(apiDir)
+ fileSystem.write(apiDir / "okio.api".toPath()) {
+ writeUtf8("hello, world!")
+ }
+ }
+
+ try {
+ assertEquals(
+ listOf("api".toPath() / "okio.api".toPath()),
+ fileSystem.listOrNull("api".toPath()),
+ // List some entries to help debugging.
+ fileSystem.listRecursively(".".toPath()).take(5).toList().joinToString(),
+ )
+ } catch (e: Throwable) {
+ if (e !is AssertionError && e !is FileNotFoundException) { throw e }
+
+ // Non JVM environments.
+ val firstChild = fileSystem.list("Library".toPath()).first()
+ assertTrue(
+ // List some entries to help debugging.
+ fileSystem.listRecursively(".".toPath()).take(5).toList().joinToString(),
+ ) {
+ // To avoid relying too much on the environment we check that the path contains its parent
+ // once and that it's relative.
+ firstChild.isRelative &&
+ firstChild.toString().startsWith("Library") &&
+ firstChild.toString().split("Library").size == 2
+ }
+ }
+ }
+
+ @Test
+ fun listResultsAreSorted() {
+ val fileA = base / "a"
+ val fileB = base / "b"
+ val fileC = base / "c"
+ val fileD = base / "d"
+
+ // Create files in a different order than the sorted order, so a file system that returns files
+ // in creation-order or reverse-creation order won't pass by accident.
+ fileD.writeUtf8("fileD")
+ fileB.writeUtf8("fileB")
+ fileC.writeUtf8("fileC")
+ fileA.writeUtf8("fileA")
+
+ val entries = fileSystem.list(base)
+ assertEquals(entries, listOf(fileA, fileB, fileC, fileD))
+ }
+
+ @Test
+ fun listNoSuchDirectory() {
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.list(base / "no-such-directory")
+ }
+ }
+
+ @Test
+ fun listFile() {
+ val target = base / "list"
+ target.writeUtf8("hello, world!")
+ val exception = assertFailsWith<IOException> {
+ fileSystem.list(target)
+ }
+ assertTrue(exception !is FileNotFoundException)
+ }
+
+ @Test
+ fun listOrNull() {
+ val target = base / "list"
+ target.writeUtf8("hello, world!")
+ val entries = fileSystem.listOrNull(base)!!
+ assertTrue(entries.toString()) { target in entries }
+ }
+
+ @Test
+ fun listOrNullOnRelativePathReturnsRelativePaths() {
+ // Make sure there's always at least one file so our assertion is useful.
+ if (fileSystem.isFakeFileSystem) {
+ val workingDirectory = "/directory".toPath()
+ fileSystem.createDirectory(workingDirectory)
+ fileSystem.workingDirectory = workingDirectory
+ fileSystem.write("a.txt".toPath()) {
+ writeUtf8("hello, world!")
+ }
+ } else if (isWrappingJimFileSystem) {
+ fileSystem.write("a.txt".toPath()) {
+ writeUtf8("hello, world!")
+ }
+ }
+
+ val entries = fileSystem.listOrNull(".".toPath())
+ assertTrue(entries.toString()) { entries!!.isNotEmpty() && entries.all { it.isRelative } }
+ }
+
+ @Test
+ fun listOrNullResultsAreSorted() {
+ val fileA = base / "a"
+ val fileB = base / "b"
+ val fileC = base / "c"
+ val fileD = base / "d"
+
+ // Create files in a different order than the sorted order, so a file system that returns files
+ // in creation-order or reverse-creation order won't pass by accident.
+ fileD.writeUtf8("fileD")
+ fileB.writeUtf8("fileB")
+ fileC.writeUtf8("fileC")
+ fileA.writeUtf8("fileA")
+
+ val entries = fileSystem.listOrNull(base)
+ assertEquals(entries, listOf(fileA, fileB, fileC, fileD))
+ }
+
+ @Test
+ fun listOrNullNoSuchDirectory() {
+ assertNull(fileSystem.listOrNull(base / "no-such-directory"))
+ }
+
+ @Test
+ fun listOrNullFile() {
+ val target = base / "list"
+ target.writeUtf8("hello, world!")
+ assertNull(fileSystem.listOrNull(target))
+ }
+
+ @Test
+ fun listRecursivelyReturnsEmpty() {
+ val entries = fileSystem.listRecursively(base)
+ assertEquals(entries.toList(), listOf())
+ }
+
+ @Test
+ fun listRecursivelyReturnsSingleFile() {
+ val baseA = base / "a"
+ baseA.writeUtf8("a")
+ val entries = fileSystem.listRecursively(base)
+ assertEquals(entries.toList(), listOf(baseA))
+ }
+
+ @Test
+ fun listRecursivelyRecurses() {
+ val baseA = base / "a"
+ val baseAB = baseA / "b"
+ baseA.createDirectory()
+ baseAB.writeUtf8("ab")
+ val entries = fileSystem.listRecursively(base)
+ assertEquals(listOf(baseA, baseAB), entries.toList())
+ }
+
+ @Test
+ fun listRecursivelyNoSuchFile() {
+ val baseA = base / "a"
+ val sequence = fileSystem.listRecursively(baseA)
+ assertFailsWith<FileNotFoundException> {
+ sequence.first()
+ }
+ }
+
+ /**
+ * Note that this is different from `Files.walk` in java.nio which returns the argument even if
+ * it is not a directory.
+ */
+ @Test
+ fun listRecursivelyNotADirectory() {
+ val baseA = base / "a"
+ baseA.writeUtf8("a")
+ val sequence = fileSystem.listRecursively(baseA)
+ val exception = assertFailsWith<IOException> {
+ sequence.first()
+ }
+ assertTrue(exception !is FileNotFoundException)
+ }
+
+ @Test
+ fun listRecursivelyIsDepthFirst() {
+ val baseA = base / "a"
+ val baseB = base / "b"
+ val baseA1 = baseA / "1"
+ val baseA2 = baseA / "2"
+ val baseB1 = baseB / "1"
+ val baseB2 = baseB / "2"
+ baseA.createDirectory()
+ baseB.createDirectory()
+ baseA1.writeUtf8("a1")
+ baseA2.writeUtf8("a2")
+ baseB1.writeUtf8("b1")
+ baseB2.writeUtf8("b2")
+ val entries = fileSystem.listRecursively(base)
+ assertEquals(listOf(baseA, baseA1, baseA2, baseB, baseB1, baseB2), entries.toList())
+ }
+
+ @Test
+ fun listRecursivelyIsLazy() {
+ val baseA = base / "a"
+ val baseB = base / "b"
+ baseA.createDirectory()
+ baseB.createDirectory()
+ val entries = fileSystem.listRecursively(base).iterator()
+
+ // This call will enqueue up the children of base, baseA and baseB.
+ assertEquals(baseA, entries.next())
+ val baseA1 = baseA / "1"
+ val baseA2 = baseA / "2"
+ baseA1.writeUtf8("a1")
+ baseA2.writeUtf8("a2")
+
+ // This call will enqueue the children of baseA, baseA1 and baseA2.
+ assertEquals(baseA1, entries.next())
+ assertEquals(baseA2, entries.next())
+ assertEquals(baseB, entries.next())
+
+ // This call will enqueue the children of baseB, baseB1 and baseB2.
+ val baseB1 = baseB / "1"
+ val baseB2 = baseB / "2"
+ baseB1.writeUtf8("b1")
+ baseB2.writeUtf8("b2")
+ assertEquals(baseB1, entries.next())
+ assertEquals(baseB2, entries.next())
+ assertFalse(entries.hasNext())
+ }
+
+ /**
+ * This test creates directories that should be listed lazily, and then deletes them! The test
+ * wants to confirm that the sequence is resilient to such changes.
+ */
+ @Test
+ fun listRecursivelySilentlyIgnoresListFailures() {
+ val baseA = base / "a"
+ val baseB = base / "b"
+ baseA.createDirectory()
+ baseB.createDirectory()
+ val entries = fileSystem.listRecursively(base).iterator()
+ assertEquals(baseA, entries.next())
+ assertEquals(baseB, entries.next())
+ fileSystem.delete(baseA)
+ fileSystem.delete(baseB)
+ assertFalse(entries.hasNext())
+ }
+
+ @Test
+ fun listRecursivelySequenceIterationsAreIndependent() {
+ val sequence = fileSystem.listRecursively(base)
+ val iterator1 = sequence.iterator()
+ assertFalse(iterator1.hasNext())
+ val baseA = base / "a"
+ baseA.writeUtf8("a")
+ val iterator2 = sequence.iterator()
+ assertEquals(baseA, iterator2.next())
+ assertFalse(iterator2.hasNext())
+ }
+
+ @Test
+ fun listRecursivelyFollowsSymlinks() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseAA = baseA / "a"
+ val baseB = base / "b"
+ val baseBA = baseB / "a"
+ baseA.createDirectory()
+ baseAA.writeUtf8("aa")
+ fileSystem.createSymlink(baseB, baseA)
+
+ val sequence = fileSystem.listRecursively(base, followSymlinks = true)
+ assertEquals(listOf(baseA, baseAA, baseB, baseBA), sequence.toList())
+ }
+
+ @Test
+ fun listRecursivelyDoesNotFollowSymlinks() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseAA = baseA / "a"
+ val baseB = base / "b"
+ baseA.createDirectory()
+ baseAA.writeUtf8("aa")
+ fileSystem.createSymlink(baseB, baseA)
+
+ val sequence = fileSystem.listRecursively(base, followSymlinks = false)
+ assertEquals(listOf(baseA, baseAA, baseB), sequence.toList())
+ }
+
+ @Test
+ fun listRecursivelyOnSymlink() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseAA = baseA / "a"
+ val baseB = base / "b"
+ val baseBA = baseB / "a"
+
+ baseA.createDirectory()
+ baseAA.writeUtf8("aa")
+ fileSystem.createSymlink(baseB, baseA)
+
+ val sequence = fileSystem.listRecursively(baseB, followSymlinks = false)
+ assertEquals(listOf(baseBA), sequence.toList())
+ }
+
+ @Test
+ fun listRecursiveWithSpecialCharacterNamedFiles() {
+ val baseA = base / "ä"
+ val baseASuperSaiyan = baseA / "超サイヤ人"
+ val baseB = base / "ß"
+ val baseBIliad = baseB / "Ἰλιάς"
+
+ baseA.createDirectory()
+ baseASuperSaiyan.writeUtf8("カカロットよ!")
+ baseB.createDirectory()
+ baseBIliad.writeUtf8("μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος")
+
+ val sequence = fileSystem.listRecursively(base)
+ assertEquals(listOf(baseB, baseBIliad, baseA, baseASuperSaiyan), sequence.toList())
+ }
+
+ @Test
+ fun listRecursiveOnSymlinkWithSpecialCharacterNamedFiles() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "ä"
+ val baseASuperSaiyan = baseA / "超サイヤ人"
+ val baseB = base / "ß"
+ val baseBSuperSaiyan = baseB / "超サイヤ人"
+
+ baseA.createDirectory()
+ baseASuperSaiyan.writeUtf8("aa")
+ fileSystem.createSymlink(baseB, baseA)
+
+ val sequence = fileSystem.listRecursively(baseB, followSymlinks = false)
+ assertEquals(listOf(baseBSuperSaiyan), sequence.toList())
+ }
+
+ @Test
+ fun listRecursivelyOnSymlinkCycleThrows() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseAB = baseA / "b"
+ val baseAC = baseA / "c"
+
+ baseA.createDirectory()
+ baseAB.writeUtf8("ab")
+ fileSystem.createSymlink(baseAC, baseA)
+
+ val iterator = fileSystem.listRecursively(base, followSymlinks = true).iterator()
+ assertEquals(baseA, iterator.next())
+ assertEquals(baseAB, iterator.next())
+ assertEquals(baseAC, iterator.next())
+ val exception = assertFailsWith<IOException> {
+ iterator.next() // This would fail because 'c' refers to a path we've already visited.
+ }
+ assertEquals("symlink cycle at $baseAC", exception.message)
+ }
+
+ @Test
+ fun listRecursivelyDoesNotFollowRelativeSymlink() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseAA = baseA / "a"
+ val baseB = base / "b"
+ baseA.createDirectory()
+ baseAA.writeUtf8("aa")
+ fileSystem.createSymlink(baseB, ".".toPath()) // Symlink to enclosing directory!
+
+ val iterator = fileSystem.listRecursively(base, followSymlinks = true).iterator()
+ assertEquals(baseA, iterator.next())
+ assertEquals(baseAA, iterator.next())
+ assertEquals(baseB, iterator.next())
+ val exception = assertFailsWith<IOException> {
+ iterator.next()
+ }
+ assertEquals("symlink cycle at $baseB", exception.message)
+ }
+
+ @Test
+ fun fileSourceNoSuchDirectory() {
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.source(base / "no-such-directory" / "file")
+ }
+ }
+
+ @Test
+ fun fileSource() {
+ val path = base / "file-source"
+ path.writeUtf8("hello, world!")
+
+ val source = fileSystem.source(path)
+ val buffer = Buffer()
+ assertTrue(source.read(buffer, 100L) == 13L)
+ assertEquals(-1L, source.read(buffer, 100L))
+ assertEquals("hello, world!", buffer.readUtf8())
+ source.close()
+ }
+
+ @Test
+ fun readPath() {
+ val path = base / "read-path"
+ val string = "hello, read with a Path"
+ path.writeUtf8(string)
+
+ val result = fileSystem.read(path) {
+ assertEquals("hello", readUtf8(5))
+ assertEquals(", read with ", readUtf8(12))
+ assertEquals("a Path", readUtf8())
+ return@read "success"
+ }
+ assertEquals("success", result)
+ }
+
+ @Test
+ fun fileSink() {
+ val path = base / "file-sink"
+ val sink = fileSystem.sink(path)
+ val buffer = Buffer().writeUtf8("hello, world!")
+ sink.write(buffer, buffer.size)
+ sink.close()
+ assertTrue(path in fileSystem.list(base))
+ assertEquals(0, buffer.size)
+ assertEquals("hello, world!", path.readUtf8())
+ }
+
+ @Test
+ fun fileSinkWithSpecialCharacterNamedFiles() {
+ val path = base / "Ἰλιάς"
+ val sink = fileSystem.sink(path)
+ val buffer = Buffer().writeUtf8("μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος")
+ sink.write(buffer, buffer.size)
+ sink.close()
+ assertTrue(path in fileSystem.list(base))
+ assertEquals(0, buffer.size)
+ assertEquals("μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος", path.readUtf8())
+ }
+
+ @Test
+ fun fileSinkMustCreate() {
+ val path = base / "file-sink"
+ val sink = fileSystem.sink(path, mustCreate = true)
+ val buffer = Buffer().writeUtf8("hello, world!")
+ sink.write(buffer, buffer.size)
+ sink.close()
+ assertTrue(path in fileSystem.list(base))
+ assertEquals(0, buffer.size)
+ assertEquals("hello, world!", path.readUtf8())
+ }
+
+ @Test
+ fun fileSinkMustCreateThrowsIfAlreadyExists() {
+ val path = base / "file-sink"
+ path.writeUtf8("First!")
+ assertFailsWith<IOException> {
+ fileSystem.sink(path, mustCreate = true)
+ }
+ }
+
+ /**
+ * Write a file by concatenating three mechanisms, then read it in its entirety using three other
+ * mechanisms. This is attempting to defend against unwanted use of Windows text mode.
+ *
+ * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-160
+ */
+ @Test
+ fun fileSinkSpecialCharacters() {
+ val path = base / "file-sink-special-characters"
+ val content = "[ctrl-z: \u001A][newline: \n][crlf: \r\n]".encodeUtf8()
+
+ fileSystem.write(path) {
+ writeUtf8("FileSystem.write()\n")
+ write(content)
+ }
+
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.sink(fileOffset = handle.size()).buffer().use { sink ->
+ sink.writeUtf8("FileSystem.openReadWrite()\n")
+ sink.write(content)
+ }
+ }
+
+ fileSystem.appendingSink(path).buffer().use { sink ->
+ sink.writeUtf8("FileSystem.appendingSink()\n")
+ sink.write(content)
+ }
+
+ fileSystem.read(path) {
+ assertEquals("FileSystem.write()", readUtf8LineStrict())
+ assertEquals(content, readByteString(content.size.toLong()))
+ assertEquals("FileSystem.openReadWrite()", readUtf8LineStrict())
+ assertEquals(content, readByteString(content.size.toLong()))
+ assertEquals("FileSystem.appendingSink()", readUtf8LineStrict())
+ assertEquals(content, readByteString(content.size.toLong()))
+ assertTrue(exhausted())
+ }
+
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.source().buffer().use { source ->
+ assertEquals("FileSystem.write()", source.readUtf8LineStrict())
+ assertEquals(content, source.readByteString(content.size.toLong()))
+ assertEquals("FileSystem.openReadWrite()", source.readUtf8LineStrict())
+ assertEquals(content, source.readByteString(content.size.toLong()))
+ assertEquals("FileSystem.appendingSink()", source.readUtf8LineStrict())
+ assertEquals(content, source.readByteString(content.size.toLong()))
+ assertTrue(source.exhausted())
+ }
+ }
+
+ fileSystem.openReadOnly(path).use { handle ->
+ handle.source().buffer().use { source ->
+ assertEquals("FileSystem.write()", source.readUtf8LineStrict())
+ assertEquals(content, source.readByteString(content.size.toLong()))
+ assertEquals("FileSystem.openReadWrite()", source.readUtf8LineStrict())
+ assertEquals(content, source.readByteString(content.size.toLong()))
+ assertEquals("FileSystem.appendingSink()", source.readUtf8LineStrict())
+ assertEquals(content, source.readByteString(content.size.toLong()))
+ assertTrue(source.exhausted())
+ }
+ }
+ }
+
+ @Test
+ fun writePath() {
+ val path = base / "write-path"
+ val content = fileSystem.write(path) {
+ val string = "hello, write with a Path"
+ writeUtf8(string)
+ return@write string
+ }
+ assertTrue(path in fileSystem.list(base))
+ assertEquals(content, path.readUtf8())
+ }
+
+ @Test
+ fun writePathMustCreate() {
+ val path = base / "write-path"
+ val content = fileSystem.write(path, mustCreate = true) {
+ val string = "hello, write with a Path"
+ writeUtf8(string)
+ return@write string
+ }
+ assertTrue(path in fileSystem.list(base))
+ assertEquals(content, path.readUtf8())
+ }
+
+ @Test
+ fun writePathMustCreateThrowsIfAlreadyExists() {
+ val path = base / "write-path"
+ path.writeUtf8("First!")
+ assertFailsWith<IOException> {
+ fileSystem.write(path, mustCreate = true) {}
+ }
+ }
+
+ @Test
+ fun appendingSinkAppendsToExistingFile() {
+ val path = base / "appending-sink-appends-to-existing-file"
+ path.writeUtf8("hello, world!\n")
+ val sink = fileSystem.appendingSink(path)
+ val buffer = Buffer().writeUtf8("this is added later!")
+ sink.write(buffer, buffer.size)
+ sink.close()
+ assertTrue(path in fileSystem.list(base))
+ assertEquals("hello, world!\nthis is added later!", path.readUtf8())
+ }
+
+ @Test
+ fun appendingSinkDoesNotImpactExistingFile() {
+ if (fileSystem.isFakeFileSystem && !fileSystem.allowReadsWhileWriting) return
+
+ val path = base / "appending-sink-does-not-impact-existing-file"
+ path.writeUtf8("hello, world!\n")
+ val sink = fileSystem.appendingSink(path)
+ assertEquals("hello, world!\n", path.readUtf8())
+ sink.close()
+ assertEquals("hello, world!\n", path.readUtf8())
+ }
+
+ @Test
+ fun appendingSinkCreatesNewFile() {
+ val path = base / "appending-sink-creates-new-file"
+ val sink = fileSystem.appendingSink(path)
+ val buffer = Buffer().writeUtf8("this is all there is!")
+ sink.write(buffer, buffer.size)
+ sink.close()
+ assertTrue(path in fileSystem.list(base))
+ assertEquals("this is all there is!", path.readUtf8())
+ }
+
+ @Test
+ fun appendingSinkExistingFileMustExist() {
+ val path = base / "appending-sink-creates-new-file"
+ path.writeUtf8("Hey, ")
+
+ val sink = fileSystem.appendingSink(path, mustExist = true)
+ val buffer = Buffer().writeUtf8("this is all there is!")
+ sink.write(buffer, buffer.size)
+ sink.close()
+ assertTrue(path in fileSystem.list(base))
+ assertEquals("Hey, this is all there is!", path.readUtf8())
+ }
+
+ @Test
+ fun appendingSinkMustExistThrowsIfAbsent() {
+ val path = base / "appending-sink-creates-new-file"
+ assertFailsWith<IOException> {
+ fileSystem.appendingSink(path, mustExist = true)
+ }
+ }
+
+ @Test
+ fun fileSinkFlush() {
+ if (fileSystem.isFakeFileSystem && !fileSystem.allowReadsWhileWriting) return
+
+ val path = base / "file-sink"
+ val sink = fileSystem.sink(path)
+
+ val buffer = Buffer().writeUtf8("hello,")
+ sink.write(buffer, buffer.size)
+ sink.flush()
+ assertEquals("hello,", path.readUtf8())
+
+ buffer.writeUtf8(" world!")
+ sink.write(buffer, buffer.size)
+ sink.close()
+ assertEquals("hello, world!", path.readUtf8())
+ }
+
+ @Test
+ fun fileSinkNoSuchDirectory() {
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.sink(base / "no-such-directory" / "file")
+ }
+ }
+
+ @Test
+ fun createDirectory() {
+ val path = base / "create-directory"
+ fileSystem.createDirectory(path)
+ assertTrue(path in fileSystem.list(base))
+ }
+
+ @Test
+ fun createDirectoryMustCreate() {
+ val path = base / "create-directory"
+ fileSystem.createDirectory(path, mustCreate = true)
+ assertTrue(path in fileSystem.list(base))
+ }
+
+ @Test
+ fun createDirectoryAlreadyExists() {
+ val path = base / "already-exists"
+ fileSystem.createDirectory(path)
+ fileSystem.createDirectory(path)
+ }
+
+ @Test
+ fun createDirectoryAlreadyExistsMustCreateThrows() {
+ val path = base / "already-exists"
+ fileSystem.createDirectory(path)
+ val exception = assertFailsWith<IOException> {
+ fileSystem.createDirectory(path, mustCreate = true)
+ }
+ assertTrue(exception !is FileNotFoundException)
+ }
+
+ @Test
+ fun createDirectoryParentDirectoryDoesNotExist() {
+ val path = base / "no-such-directory" / "created"
+ assertFailsWith<IOException> {
+ fileSystem.createDirectory(path)
+ }
+ }
+
+ @Test
+ fun createDirectoriesSingle() {
+ val path = base / "create-directories-single"
+ fileSystem.createDirectories(path)
+ assertTrue(path in fileSystem.list(base))
+ assertTrue(fileSystem.metadata(path).isDirectory)
+ }
+
+ @Test
+ fun createDirectoriesAlreadyExists() {
+ val path = base / "already-exists"
+ fileSystem.createDirectory(path)
+ fileSystem.createDirectories(path)
+ assertTrue(fileSystem.metadata(path).isDirectory)
+ }
+
+ @Test
+ fun createDirectoriesAlreadyExistsMustCreateThrows() {
+ val path = base / "already-exists"
+ fileSystem.createDirectory(path)
+ val exception = assertFailsWith<IOException> {
+ fileSystem.createDirectories(path, mustCreate = true)
+ }
+ assertTrue(exception !is FileNotFoundException)
+ assertTrue(fileSystem.metadata(path).isDirectory)
+ }
+
+ @Test
+ fun createDirectoriesParentDirectoryDoesNotExist() {
+ fileSystem.createDirectories(base / "a" / "b" / "c")
+ assertTrue(base / "a" in fileSystem.list(base))
+ assertTrue(base / "a" / "b" in fileSystem.list(base / "a"))
+ assertTrue(base / "a" / "b" / "c" in fileSystem.list(base / "a" / "b"))
+ assertTrue(fileSystem.metadata(base / "a" / "b" / "c").isDirectory)
+ }
+
+ @Test
+ fun createDirectoriesParentIsFile() {
+ val file = base / "simple-file"
+ file.writeUtf8("just a file")
+ assertFailsWith<IOException> {
+ fileSystem.createDirectories(file / "child")
+ }
+ }
+
+ @Test
+ fun atomicMoveFile() {
+ val source = base / "source"
+ source.writeUtf8("hello, world!")
+ val target = base / "target"
+ fileSystem.atomicMove(source, target)
+ assertEquals("hello, world!", target.readUtf8())
+ assertTrue(source !in fileSystem.list(base))
+ assertTrue(target in fileSystem.list(base))
+ }
+
+ @Test
+ fun atomicMoveDirectory() {
+ val source = base / "source"
+ fileSystem.createDirectory(source)
+ val target = base / "target"
+ fileSystem.atomicMove(source, target)
+ assertTrue(source !in fileSystem.list(base))
+ assertTrue(target in fileSystem.list(base))
+ }
+
+ @Test
+ fun atomicMoveSourceIsTarget() {
+ val source = base / "source"
+ source.writeUtf8("hello, world!")
+ fileSystem.atomicMove(source, source)
+ assertEquals("hello, world!", source.readUtf8())
+ assertTrue(source in fileSystem.list(base))
+ }
+
+ @Test
+ fun atomicMoveClobberExistingFile() {
+ // `java.io` on Windows doesn't allow file renaming if the target already exists.
+ if (isJvmFileSystemOnWindows()) return
+
+ val source = base / "source"
+ source.writeUtf8("hello, world!")
+ val target = base / "target"
+ target.writeUtf8("this file will be clobbered!")
+ fileSystem.atomicMove(source, target)
+ assertEquals("hello, world!", target.readUtf8())
+ assertTrue(source !in fileSystem.list(base))
+ assertTrue(target in fileSystem.list(base))
+ }
+
+ @Test
+ fun atomicMoveSourceDoesNotExist() {
+ val source = base / "source"
+ val target = base / "target"
+ if (fileSystem::class.simpleName == "JvmSystemFileSystem") {
+ assertFailsWith<IOException> {
+ fileSystem.atomicMove(source, target)
+ }
+ } else {
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.atomicMove(source, target)
+ }
+ }
+ }
+
+ @Test
+ fun atomicMoveSourceIsFileAndTargetIsDirectory() {
+ val source = base / "source"
+ source.writeUtf8("hello, world!")
+ val target = base / "target"
+ fileSystem.createDirectory(target)
+
+ if (allowAtomicMoveFromFileToDirectory) {
+ fileSystem.atomicMove(source, target)
+ assertEquals("hello, world!", target.readUtf8())
+ } else {
+ val exception = assertFailsWith<IOException> {
+ fileSystem.atomicMove(source, target)
+ }
+ assertTrue(exception !is FileNotFoundException)
+ }
+ }
+
+ @Test
+ fun atomicMoveSourceIsDirectoryAndTargetIsFile() {
+ // `java.io` on Windows doesn't allow file renaming if the target already exists.
+ if (isJvmFileSystemOnWindows()) return
+
+ val source = base / "source"
+ fileSystem.createDirectory(source)
+ val target = base / "target"
+ target.writeUtf8("hello, world!")
+ try {
+ fileSystem.atomicMove(source, target)
+ assertTrue(allowClobberingEmptyDirectories)
+ } catch (e: IOException) {
+ assertFalse(allowClobberingEmptyDirectories)
+ }
+ }
+
+ @Test
+ fun copyFile() {
+ val source = base / "source"
+ source.writeUtf8("hello, world!")
+ val target = base / "target"
+ fileSystem.copy(source, target)
+ assertTrue(target in fileSystem.list(base))
+ assertEquals("hello, world!", source.readUtf8())
+ assertEquals("hello, world!", target.readUtf8())
+ }
+
+ @Test
+ fun copySourceDoesNotExist() {
+ val source = base / "source"
+ val target = base / "target"
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.copy(source, target)
+ }
+ assertFalse(target in fileSystem.list(base))
+ }
+
+ @Test
+ fun copyTargetIsClobbered() {
+ val source = base / "source"
+ source.writeUtf8("hello, world!")
+ val target = base / "target"
+ target.writeUtf8("this file will be clobbered!")
+ fileSystem.copy(source, target)
+ assertTrue(target in fileSystem.list(base))
+ assertEquals("hello, world!", target.readUtf8())
+ }
+
+ @Test
+ fun deleteFile() {
+ val path = base / "delete-file"
+ path.writeUtf8("delete me")
+ fileSystem.delete(path)
+ assertTrue(path !in fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteFileMustExist() {
+ val path = base / "delete-file"
+ path.writeUtf8("delete me")
+ fileSystem.delete(path, mustExist = true)
+ assertTrue(path !in fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteEmptyDirectory() {
+ val path = base / "delete-empty-directory"
+ fileSystem.createDirectory(path)
+ fileSystem.delete(path)
+ assertTrue(path !in fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteEmptyDirectoryMustExist() {
+ val path = base / "delete-empty-directory"
+ fileSystem.createDirectory(path)
+ fileSystem.delete(path, mustExist = true)
+ assertTrue(path !in fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteDoesNotExist() {
+ val path = base / "no-such-file"
+ fileSystem.delete(path)
+ }
+
+ @Test
+ fun deleteFailsOnNoSuchFileIfMustExist() {
+ val path = base / "no-such-file"
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.delete(path, mustExist = true)
+ }
+ }
+
+ @Test
+ fun deleteFailsOnNonEmptyDirectory() {
+ val path = base / "non-empty-directory"
+ fileSystem.createDirectory(path)
+ (path / "file.txt").writeUtf8("inside directory")
+ val exception = assertFailsWith<IOException> {
+ fileSystem.delete(path)
+ }
+ assertTrue(exception !is FileNotFoundException)
+ }
+
+ @Test
+ fun deleteFailsOnNonEmptyDirectoryMustExist() {
+ val path = base / "non-empty-directory"
+ fileSystem.createDirectory(path)
+ (path / "file.txt").writeUtf8("inside directory")
+ val exception = assertFailsWith<IOException> {
+ fileSystem.delete(path, mustExist = true)
+ }
+ assertTrue(exception !is FileNotFoundException)
+ }
+
+ @Test
+ fun deleteRecursivelyFile() {
+ val path = base / "delete-recursively-file"
+ path.writeUtf8("delete me")
+ fileSystem.deleteRecursively(path)
+ assertTrue(path !in fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteRecursivelyFileMustExist() {
+ val path = base / "delete-recursively-file"
+ path.writeUtf8("delete me")
+ fileSystem.deleteRecursively(path, mustExist = true)
+ assertTrue(path !in fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteRecursivelyEmptyDirectory() {
+ val path = base / "delete-recursively-empty-directory"
+ fileSystem.createDirectory(path)
+ fileSystem.deleteRecursively(path)
+ assertTrue(path !in fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteRecursivelyEmptyDirectoryMustExist() {
+ val path = base / "delete-recursively-empty-directory"
+ fileSystem.createDirectory(path)
+ fileSystem.deleteRecursively(path, mustExist = true)
+ assertTrue(path !in fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteRecursivelyNoSuchFile() {
+ val path = base / "no-such-file"
+ fileSystem.deleteRecursively(path)
+ }
+
+ @Test
+ fun deleteRecursivelyMustExistFailsOnNoSuchFile() {
+ val path = base / "no-such-file"
+ assertFailsWith<IOException> {
+ fileSystem.deleteRecursively(path, mustExist = true)
+ }
+ }
+
+ @Test
+ fun deleteRecursivelyNonEmptyDirectory() {
+ val path = base / "delete-recursively-non-empty-directory"
+ fileSystem.createDirectory(path)
+ (path / "file.txt").writeUtf8("inside directory")
+ fileSystem.deleteRecursively(path)
+ assertTrue(path !in fileSystem.list(base))
+ assertTrue((path / "file.txt") !in fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteRecursivelyNonEmptyDirectoryMustExist() {
+ val path = base / "delete-recursively-non-empty-directory"
+ fileSystem.createDirectory(path)
+ (path / "file.txt").writeUtf8("inside directory")
+ fileSystem.deleteRecursively(path, mustExist = true)
+ assertTrue(path !in fileSystem.list(base))
+ assertTrue((path / "file.txt") !in fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteRecursivelyDeepHierarchy() {
+ fileSystem.createDirectory(base / "a")
+ fileSystem.createDirectory(base / "a" / "b")
+ fileSystem.createDirectory(base / "a" / "b" / "c")
+ (base / "a" / "b" / "c" / "d.txt").writeUtf8("inside deep hierarchy")
+ fileSystem.deleteRecursively(base / "a")
+ assertEquals(listOf(), fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteRecursivelyDeepHierarchyMustExist() {
+ fileSystem.createDirectory(base / "a")
+ fileSystem.createDirectory(base / "a" / "b")
+ fileSystem.createDirectory(base / "a" / "b" / "c")
+ (base / "a" / "b" / "c" / "d.txt").writeUtf8("inside deep hierarchy")
+ fileSystem.deleteRecursively(base / "a", mustExist = true)
+ assertEquals(listOf(), fileSystem.list(base))
+ }
+
+ @Test
+ fun deleteRecursivelyOnSymlinkToFileDeletesOnlyThatSymlink() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseB = base / "b"
+ baseB.writeUtf8("b")
+ fileSystem.createSymlink(baseA, baseB)
+ fileSystem.deleteRecursively(baseA)
+ assertEquals("b", baseB.readUtf8())
+ }
+
+ @Test
+ fun deleteRecursivelyOnSymlinkToFileDeletesOnlyThatSymlinkMustExist() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseB = base / "b"
+ baseB.writeUtf8("b")
+ fileSystem.createSymlink(baseA, baseB)
+ fileSystem.deleteRecursively(baseA, mustExist = true)
+ assertEquals("b", baseB.readUtf8())
+ }
+
+ @Test
+ fun deleteRecursivelyOnSymlinkToDirectoryDeletesOnlyThatSymlink() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseB = base / "b"
+ val baseBC = base / "b" / "c"
+ fileSystem.createDirectory(baseB)
+ baseBC.writeUtf8("c")
+ fileSystem.createSymlink(baseA, baseB)
+ fileSystem.deleteRecursively(baseA)
+ assertEquals("c", baseBC.readUtf8())
+ }
+
+ @Test
+ fun deleteRecursivelyOnSymlinkToDirectoryDeletesOnlyThatSymlinkMustExist() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseB = base / "b"
+ val baseBC = base / "b" / "c"
+ fileSystem.createDirectory(baseB)
+ baseBC.writeUtf8("c")
+ fileSystem.createSymlink(baseA, baseB)
+ fileSystem.deleteRecursively(baseA, mustExist = true)
+ assertEquals("c", baseBC.readUtf8())
+ }
+
+ @Test
+ fun deleteRecursivelyOnSymlinkCycleSucceeds() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseAB = baseA / "b"
+ val baseAC = baseA / "c"
+
+ baseA.createDirectory()
+ baseAB.writeUtf8("ab")
+ fileSystem.createSymlink(baseAC, baseA)
+
+ fileSystem.deleteRecursively(base)
+ assertFalse(fileSystem.exists(base))
+ }
+
+ @Test
+ fun deleteRecursivelyOnSymlinkCycleSucceedsMustExist() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseAB = baseA / "b"
+ val baseAC = baseA / "c"
+
+ baseA.createDirectory()
+ baseAB.writeUtf8("ab")
+ fileSystem.createSymlink(baseAC, baseA)
+
+ fileSystem.deleteRecursively(base, mustExist = true)
+ assertFalse(fileSystem.exists(base))
+ }
+
+ @Test
+ fun deleteRecursivelyOnSymlinkToEnclosingDirectorySucceeds() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ fileSystem.createSymlink(baseA, ".".toPath())
+
+ fileSystem.deleteRecursively(baseA)
+ assertFalse(fileSystem.exists(baseA))
+ assertTrue(fileSystem.exists(base))
+ }
+
+ @Test
+ fun deleteRecursivelyOnSymlinkToEnclosingDirectorySucceedsMustExist() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ fileSystem.createSymlink(baseA, ".".toPath())
+
+ fileSystem.deleteRecursively(baseA, mustExist = true)
+ assertFalse(fileSystem.exists(baseA))
+ assertTrue(fileSystem.exists(base))
+ }
+
+ @Test
+ fun fileMetadata() {
+ val minTime = clock.now()
+ val path = base / "file-metadata"
+ path.writeUtf8("hello, world!")
+ val maxTime = clock.now()
+
+ val metadata = fileSystem.metadata(path)
+ assertTrue(metadata.isRegularFile)
+ assertFalse(metadata.isDirectory)
+ assertEquals(13, metadata.size)
+ assertInRange(metadata.createdAt, minTime, maxTime)
+ assertInRange(metadata.lastModifiedAt, minTime, maxTime)
+ assertInRange(metadata.lastAccessedAt, minTime, maxTime)
+ }
+
+ @Test
+ fun directoryMetadata() {
+ val minTime = clock.now()
+ val path = base / "directory-metadata"
+ fileSystem.createDirectory(path)
+ val maxTime = clock.now()
+
+ val metadata = fileSystem.metadata(path)
+ assertFalse(metadata.isRegularFile)
+ assertTrue(metadata.isDirectory)
+ // Note that the size check is omitted; we'd expect null but the JVM returns values like 64.
+ assertInRange(metadata.createdAt, minTime, maxTime)
+ assertInRange(metadata.lastModifiedAt, minTime, maxTime)
+ assertInRange(metadata.lastAccessedAt, minTime, maxTime)
+ }
+
+ @Test
+ fun fileMetadataWithSpecialCharacterNamedFiles() {
+ val minTime = clock.now()
+ val path = base / "超サイヤ人"
+ path.writeUtf8("カカロットよ!")
+ val maxTime = clock.now()
+
+ val metadata = fileSystem.metadata(path)
+ assertTrue(metadata.isRegularFile)
+ assertFalse(metadata.isDirectory)
+ assertEquals(21, metadata.size)
+ assertInRange(metadata.createdAt, minTime, maxTime)
+ assertInRange(metadata.lastModifiedAt, minTime, maxTime)
+ assertInRange(metadata.lastAccessedAt, minTime, maxTime)
+ }
+
+ @Test
+ fun directoryMetadataWithSpecialCharacterNamedFiles() {
+ val minTime = clock.now()
+ val path = base / "Ἰλιάς"
+ fileSystem.createDirectory(path)
+ val maxTime = clock.now()
+
+ val metadata = fileSystem.metadata(path)
+ assertFalse(metadata.isRegularFile)
+ assertTrue(metadata.isDirectory)
+ // Note that the size check is omitted; we'd expect null but the JVM returns values like 64.
+ assertInRange(metadata.createdAt, minTime, maxTime)
+ assertInRange(metadata.lastModifiedAt, minTime, maxTime)
+ assertInRange(metadata.lastAccessedAt, minTime, maxTime)
+ }
+
+ @Test
+ fun absentMetadataOrNull() {
+ val path = base / "no-such-file"
+ assertNull(fileSystem.metadataOrNull(path))
+ }
+
+ @Test
+ fun absentParentDirectoryMetadataOrNull() {
+ val path = base / "no-such-directory" / "no-such-file"
+ assertNull(fileSystem.metadataOrNull(path))
+ }
+
+ @Test
+ fun parentDirectoryIsFileMetadataOrNull() {
+ val parent = base / "regular-file"
+ val path = parent / "no-such-file"
+ parent.writeUtf8("just a regular file")
+ // This returns null on Windows and throws IOException on other platforms.
+ try {
+ assertNull(fileSystem.metadataOrNull(path))
+ } catch (e: IOException) {
+ // Also okay.
+ }
+ }
+
+ @Test
+ @Ignore
+ fun inaccessibleMetadata() {
+ // TODO(swankjesse): configure a test directory in CI that exists, but that this process doesn't
+ // have permission to read metadata of. Perhaps a file in another user's /home directory?
+ }
+
+ @Test
+ fun absentMetadata() {
+ val path = base / "no-such-file"
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.metadata(path)
+ }
+ }
+
+ @Test
+ fun fileExists() {
+ val path = base / "file-exists"
+ assertFalse(fileSystem.exists(path))
+ path.writeUtf8("hello, world!")
+ assertTrue(fileSystem.exists(path))
+ }
+
+ @Test
+ fun directoryExists() {
+ val path = base / "directory-exists"
+ assertFalse(fileSystem.exists(path))
+ fileSystem.createDirectory(path)
+ assertTrue(fileSystem.exists(path))
+ }
+
+ @Test
+ fun deleteOpenForWritingFailsOnWindows() {
+ val file = base / "file.txt"
+ expectIOExceptionOnWindows(exceptJs = true) {
+ fileSystem.sink(file).use {
+ fileSystem.delete(file)
+ }
+ }
+ }
+
+ @Test
+ fun deleteOpenForReadingFailsOnWindows() {
+ val file = base / "file.txt"
+ file.writeUtf8("abc")
+ expectIOExceptionOnWindows(exceptJs = true) {
+ fileSystem.source(file).use {
+ fileSystem.delete(file)
+ }
+ }
+ }
+
+ @Test
+ fun renameSourceIsOpenFailsOnWindows() {
+ val from = base / "from.txt"
+ val to = base / "to.txt"
+ from.writeUtf8("source file")
+ to.writeUtf8("target file")
+ expectIOExceptionOnWindows(exceptJs = true) {
+ fileSystem.source(from).use {
+ fileSystem.atomicMove(from, to)
+ }
+ }
+ }
+
+ @Test
+ fun renameTargetIsOpenFailsOnWindows() {
+ val from = base / "from.txt"
+ val to = base / "to.txt"
+ from.writeUtf8("source file")
+ to.writeUtf8("target file")
+
+ val expectCrash = !allowRenameWhenTargetIsOpen
+ try {
+ fileSystem.source(to).use {
+ fileSystem.atomicMove(from, to)
+ }
+ assertFalse(expectCrash)
+ } catch (_: IOException) {
+ assertTrue(expectCrash)
+ }
+ }
+
+ @Test
+ fun deleteContentsOfParentOfFileOpenForReadingFailsOnWindows() {
+ val parentA = (base / "a")
+ fileSystem.createDirectory(parentA)
+ val parentAB = parentA / "b"
+ fileSystem.createDirectory(parentAB)
+ val parentABC = parentAB / "c"
+ fileSystem.createDirectory(parentABC)
+ val file = parentABC / "file.txt"
+ file.writeUtf8("child file")
+ expectIOExceptionOnWindows {
+ fileSystem.source(file).use {
+ fileSystem.delete(file)
+ fileSystem.delete(parentABC)
+ fileSystem.delete(parentAB)
+ fileSystem.delete(parentA)
+ }
+ }
+ }
+
+ @Test fun fileHandleWriteAndRead() {
+ val path = base / "file-handle-write-and-read"
+ fileSystem.openReadWrite(path).use { handle ->
+
+ handle.sink().buffer().use { sink ->
+ sink.writeUtf8("abcdefghijklmnop")
+ }
+
+ handle.source().buffer().use { source ->
+ assertEquals("abcde", source.readUtf8(5))
+ assertEquals("fghijklmnop", source.readUtf8())
+ }
+ }
+ }
+
+ @Test fun fileHandleWriteAndOverwrite() {
+ val path = base / "file-handle-write-and-overwrite"
+ fileSystem.openReadWrite(path).use { handle ->
+
+ handle.sink().buffer().use { sink ->
+ sink.writeUtf8("abcdefghij")
+ }
+
+ handle.sink(fileOffset = handle.size() - 3).buffer().use { sink ->
+ sink.writeUtf8("HIJKLMNOP")
+ }
+
+ handle.source().buffer().use { source ->
+ assertEquals("abcdefgHIJKLMNOP", source.readUtf8())
+ }
+ }
+ }
+
+ @Test fun fileHandleWriteBeyondEnd() {
+ val path = base / "file-handle-write-beyond-end"
+ fileSystem.openReadWrite(path).use { handle ->
+
+ handle.sink(fileOffset = 10).buffer().use { sink ->
+ sink.writeUtf8("klmnop")
+ }
+
+ handle.source().buffer().use { source ->
+ assertEquals("00000000000000000000", source.readByteString(10).hex())
+ assertEquals("klmnop", source.readUtf8())
+ }
+ }
+ }
+
+ @Test fun fileHandleResizeSmaller() {
+ val path = base / "file-handle-resize-smaller"
+ fileSystem.openReadWrite(path).use { handle ->
+
+ handle.sink().buffer().use { sink ->
+ sink.writeUtf8("abcdefghijklmnop")
+ }
+
+ handle.resize(10)
+
+ handle.source().buffer().use { source ->
+ assertEquals("abcdefghij", source.readUtf8())
+ }
+ }
+ }
+
+ @Test fun fileHandleResizeLarger() {
+ val path = base / "file-handle-resize-larger"
+ fileSystem.openReadWrite(path).use { handle ->
+
+ handle.sink().buffer().use { sink ->
+ sink.writeUtf8("abcde")
+ }
+
+ handle.resize(15)
+
+ handle.source().buffer().use { source ->
+ assertEquals("abcde", source.readUtf8(5))
+ assertEquals("00000000000000000000", source.readByteString().hex())
+ }
+ }
+ }
+
+ @Test fun fileHandleFlush() {
+ if (windowsLimitations) return // Open for reading and writing simultaneously.
+
+ val path = base / "file-handle-flush"
+ fileSystem.openReadWrite(path).use { handleA ->
+ handleA.sink().buffer().use { sink ->
+ sink.writeUtf8("abcde")
+ }
+ handleA.flush()
+
+ fileSystem.openReadWrite(path).use { handleB ->
+ handleB.source().buffer().use { source ->
+ assertEquals("abcde", source.readUtf8())
+ }
+ }
+ }
+ }
+
+ @Test fun fileHandleLargeBufferedWriteAndRead() {
+ if (isBrowser()) return // This test errors on browsers in CI.
+
+ val data = randomBytes(1024 * 1024 * 8)
+
+ val path = base / "file-handle-large-buffered-write-and-read"
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.sink().buffer().use { sink ->
+ sink.write(data)
+ }
+ }
+
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.source().buffer().use { source ->
+ assertEquals(data, source.readByteString())
+ }
+ }
+ }
+
+ @Test fun fileHandleLargeArrayWriteAndRead() {
+ if (isBrowser()) return // This test errors on browsers in CI.
+
+ val path = base / "file-handle-large-array-write-and-read"
+
+ val writtenBytes = randomBytes(1024 * 1024 * 8)
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.write(0, writtenBytes.toByteArray(), 0, writtenBytes.size)
+ }
+
+ val readBytes = fileSystem.openReadWrite(path).use { handle ->
+ val byteArray = ByteArray(writtenBytes.size)
+ handle.read(0, byteArray, 0, byteArray.size)
+ return@use byteArray.toByteString(0, byteArray.size) // Parameters necessary for issue 910.
+ }
+
+ assertEquals(writtenBytes, readBytes)
+ }
+
+ @Test fun fileHandleEmptyArrayWriteAndRead() {
+ val path = base / "file-handle-empty-array-write-and-read"
+
+ val writtenBytes = ByteArray(0)
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.write(0, writtenBytes, 0, writtenBytes.size)
+ }
+
+ val readBytes = fileSystem.openReadWrite(path).use { handle ->
+ val byteArray = ByteArray(writtenBytes.size)
+ handle.read(0, byteArray, 0, byteArray.size)
+ return@use byteArray
+ }
+
+ assertContentEquals(writtenBytes, readBytes)
+ }
+
+ @Test fun fileHandleSinkPosition() {
+ val path = base / "file-handle-sink-position"
+
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.sink().use { sink ->
+ sink.write(Buffer().writeUtf8("abcde"), 5)
+ assertEquals(5, handle.position(sink))
+ sink.write(Buffer().writeUtf8("fghijklmno"), 10)
+ assertEquals(15, handle.position(sink))
+ }
+
+ handle.sink(200).use { sink ->
+ sink.write(Buffer().writeUtf8("abcde"), 5)
+ assertEquals(205, handle.position(sink))
+ sink.write(Buffer().writeUtf8("fghijklmno"), 10)
+ assertEquals(215, handle.position(sink))
+ }
+ }
+ }
+
+ @Test fun fileHandleBufferedSinkPosition() {
+ val path = base / "file-handle-buffered-sink-position"
+
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.sink().buffer().use { sink ->
+ sink.writeUtf8("abcde")
+ assertEquals(5, handle.position(sink))
+ sink.writeUtf8("fghijklmno")
+ assertEquals(15, handle.position(sink))
+ }
+
+ handle.sink(200).buffer().use { sink ->
+ sink.writeUtf8("abcde")
+ assertEquals(205, handle.position(sink))
+ sink.writeUtf8("fghijklmno")
+ assertEquals(215, handle.position(sink))
+ }
+ }
+ }
+
+ @Test fun fileHandleSinkReposition() {
+ val path = base / "file-handle-sink-reposition"
+
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.sink().use { sink ->
+ sink.write(Buffer().writeUtf8("abcdefghij"), 10)
+ handle.reposition(sink, 5)
+ assertEquals(5, handle.position(sink))
+ sink.write(Buffer().writeUtf8("KLM"), 3)
+ assertEquals(8, handle.position(sink))
+
+ handle.reposition(sink, 200)
+ sink.write(Buffer().writeUtf8("ABCDEFGHIJ"), 10)
+ handle.reposition(sink, 205)
+ assertEquals(205, handle.position(sink))
+ sink.write(Buffer().writeUtf8("klm"), 3)
+ assertEquals(208, handle.position(sink))
+ }
+
+ Buffer().also {
+ handle.read(fileOffset = 0, sink = it, byteCount = 10)
+ assertEquals("abcdeKLMij", it.readUtf8())
+ }
+
+ Buffer().also {
+ handle.read(fileOffset = 200, sink = it, byteCount = 15)
+ assertEquals("ABCDEklmIJ", it.readUtf8())
+ }
+ }
+ }
+
+ @Test fun fileHandleBufferedSinkReposition() {
+ val path = base / "file-handle-buffered-sink-reposition"
+
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.sink().buffer().use { sink ->
+ sink.write(Buffer().writeUtf8("abcdefghij"), 10)
+ handle.reposition(sink, 5)
+ assertEquals(5, handle.position(sink))
+ sink.write(Buffer().writeUtf8("KLM"), 3)
+ assertEquals(8, handle.position(sink))
+
+ handle.reposition(sink, 200)
+ sink.write(Buffer().writeUtf8("ABCDEFGHIJ"), 10)
+ handle.reposition(sink, 205)
+ assertEquals(205, handle.position(sink))
+ sink.write(Buffer().writeUtf8("klm"), 3)
+ assertEquals(208, handle.position(sink))
+ }
+
+ Buffer().also {
+ handle.read(fileOffset = 0, sink = it, byteCount = 10)
+ assertEquals("abcdeKLMij", it.readUtf8())
+ }
+
+ Buffer().also {
+ handle.read(fileOffset = 200, sink = it, byteCount = 15)
+ assertEquals("ABCDEklmIJ", it.readUtf8())
+ }
+ }
+ }
+
+ @Test fun fileHandleSourceHappyPath() {
+ val path = base / "file-handle-source"
+ fileSystem.write(path) {
+ writeUtf8("abcdefghijklmnop")
+ }
+
+ fileSystem.openReadOnly(path).use { handle ->
+ assertEquals(16L, handle.size())
+ val buffer = Buffer()
+
+ handle.source().use { source ->
+ assertEquals(0L, handle.position(source))
+ assertEquals(4L, source.read(buffer, 4L))
+ assertEquals("abcd", buffer.readUtf8())
+ assertEquals(4L, handle.position(source))
+ }
+
+ handle.source(fileOffset = 8L).use { source ->
+ assertEquals(8L, handle.position(source))
+ assertEquals(4L, source.read(buffer, 4L))
+ assertEquals("ijkl", buffer.readUtf8())
+ assertEquals(12L, handle.position(source))
+ }
+
+ handle.source(fileOffset = 16L).use { source ->
+ assertEquals(16L, handle.position(source))
+ assertEquals(-1L, source.read(buffer, 4L))
+ assertEquals("", buffer.readUtf8())
+ assertEquals(16L, handle.position(source))
+ }
+ }
+ }
+
+ @Test fun fileHandleSourceReposition() {
+ val path = base / "file-handle-source-reposition"
+ fileSystem.write(path) {
+ writeUtf8("abcdefghijklmnop")
+ }
+
+ fileSystem.openReadOnly(path).use { handle ->
+ assertEquals(16L, handle.size())
+ val buffer = Buffer()
+
+ handle.source().use { source ->
+ handle.reposition(source, 12L)
+ assertEquals(12L, handle.position(source))
+ assertEquals(4L, source.read(buffer, 4L))
+ assertEquals("mnop", buffer.readUtf8())
+ assertEquals(-1L, source.read(buffer, 4L))
+ assertEquals("", buffer.readUtf8())
+ assertEquals(16L, handle.position(source))
+
+ handle.reposition(source, 0L)
+ assertEquals(0L, handle.position(source))
+ assertEquals(4L, source.read(buffer, 4L))
+ assertEquals("abcd", buffer.readUtf8())
+ assertEquals(4L, handle.position(source))
+
+ handle.reposition(source, 8L)
+ assertEquals(8L, handle.position(source))
+ assertEquals(4L, source.read(buffer, 4L))
+ assertEquals("ijkl", buffer.readUtf8())
+ assertEquals(12L, handle.position(source))
+
+ handle.reposition(source, 16L)
+ assertEquals(16L, handle.position(source))
+ assertEquals(-1L, source.read(buffer, 4L))
+ assertEquals("", buffer.readUtf8())
+ assertEquals(16L, handle.position(source))
+ }
+ }
+ }
+
+ @Test fun fileHandleBufferedSourceReposition() {
+ val path = base / "file-handle-buffered-source-reposition"
+ fileSystem.write(path) {
+ writeUtf8("abcdefghijklmnop")
+ }
+
+ fileSystem.openReadOnly(path).use { handle ->
+ assertEquals(16L, handle.size())
+ val buffer = Buffer()
+
+ handle.source().buffer().use { source ->
+ handle.reposition(source, 12L)
+ assertEquals(0L, source.buffer.size)
+ assertEquals(12L, handle.position(source))
+ assertEquals(4L, source.read(buffer, 4L))
+ assertEquals(0L, source.buffer.size)
+ assertEquals("mnop", buffer.readUtf8())
+ assertEquals(-1L, source.read(buffer, 4L))
+ assertEquals("", buffer.readUtf8())
+ assertEquals(16L, handle.position(source))
+
+ handle.reposition(source, 0L)
+ assertEquals(0L, source.buffer.size)
+ assertEquals(0L, handle.position(source))
+ assertEquals(4L, source.read(buffer, 4L))
+ assertEquals(12L, source.buffer.size) // Buffered bytes accumulated.
+ assertEquals("abcd", buffer.readUtf8())
+ assertEquals(4L, handle.position(source))
+
+ handle.reposition(source, 8L)
+ assertEquals(8L, source.buffer.size) // Buffered bytes preserved.
+ assertEquals(8L, handle.position(source))
+ assertEquals(4L, source.read(buffer, 4L))
+ assertEquals(4L, source.buffer.size)
+ assertEquals("ijkl", buffer.readUtf8())
+ assertEquals(12L, handle.position(source))
+
+ handle.reposition(source, 16L)
+ assertEquals(0L, source.buffer.size)
+ assertEquals(16L, handle.position(source))
+ assertEquals(-1L, source.read(buffer, 4L))
+ assertEquals(0L, source.buffer.size)
+ assertEquals("", buffer.readUtf8())
+ assertEquals(16L, handle.position(source))
+ }
+ }
+ }
+
+ @Test fun fileHandleSourceSeekBackwards() {
+ val path = base / "file-handle-source-backwards"
+ fileSystem.write(path) {
+ writeUtf8("abcdefghijklmnop")
+ }
+ fileSystem.openReadOnly(path).use { handle ->
+ assertEquals(16L, handle.size())
+ val buffer = Buffer()
+
+ handle.source().use { source ->
+ assertEquals(0L, handle.position(source))
+ assertEquals(16L, source.read(buffer, 16L))
+ assertEquals("abcdefghijklmnop", buffer.readUtf8())
+ assertEquals(16L, handle.position(source))
+ }
+
+ handle.source(0L).use { source ->
+ assertEquals(0L, handle.position(source))
+ assertEquals(16L, source.read(buffer, 16L))
+ assertEquals("abcdefghijklmnop", buffer.readUtf8())
+ assertEquals(16L, handle.position(source))
+ }
+ }
+ }
+
+ @Test fun bufferedFileHandleSourceHappyPath() {
+ val path = base / "file-handle-source"
+ fileSystem.write(path) {
+ writeUtf8("abcdefghijklmnop")
+ }
+
+ fileSystem.openReadOnly(path).use { handle ->
+ assertEquals(16L, handle.size())
+ val buffer = Buffer()
+
+ handle.source().buffer().use { source ->
+ assertEquals(0L, handle.position(source))
+ assertEquals(4L, source.read(buffer, 4L))
+ assertEquals("abcd", buffer.readUtf8())
+ assertEquals(4L, handle.position(source))
+ }
+
+ handle.source(fileOffset = 8L).buffer().use { source ->
+ assertEquals(8L, handle.position(source))
+ assertEquals(4L, source.read(buffer, 4L))
+ assertEquals("ijkl", buffer.readUtf8())
+ assertEquals(12L, handle.position(source))
+ }
+
+ handle.source(fileOffset = 16L).buffer().use { source ->
+ assertEquals(16L, handle.position(source))
+ assertEquals(-1L, source.read(buffer, 4L))
+ assertEquals("", buffer.readUtf8())
+ assertEquals(16L, handle.position(source))
+ }
+ }
+ }
+
+ @Test fun bufferedFileHandleSourceSeekBackwards() {
+ val path = base / "file-handle-source-backwards"
+ fileSystem.write(path) {
+ writeUtf8("abcdefghijklmnop")
+ }
+ fileSystem.openReadOnly(path).use { handle ->
+ assertEquals(16L, handle.size())
+ val buffer = Buffer()
+
+ handle.source().buffer().use { source ->
+ assertEquals(0L, handle.position(source))
+ assertEquals(16L, source.read(buffer, 16L))
+ assertEquals("abcdefghijklmnop", buffer.readUtf8())
+ assertEquals(16L, handle.position(source))
+ }
+
+ handle.source(0L).buffer().use { source ->
+ assertEquals(0L, handle.position(source))
+ assertEquals(16L, source.read(buffer, 16L))
+ assertEquals("abcdefghijklmnop", buffer.readUtf8())
+ assertEquals(16L, handle.position(source))
+ }
+ }
+ }
+
+ @Test fun openReadOnlyThrowsOnAttemptToWrite() {
+ val path = base / "file-handle-source"
+ fileSystem.write(path) {
+ writeUtf8("abcdefghijklmnop")
+ }
+
+ fileSystem.openReadOnly(path).use { handle ->
+ try {
+ handle.sink()
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+
+ try {
+ handle.flush()
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+
+ try {
+ handle.resize(0L)
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+
+ try {
+ handle.write(0L, Buffer().writeUtf8("hello"), 5L)
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+ }
+ }
+
+ @Test fun openReadOnlyFailsOnAbsentFile() {
+ val path = base / "file-handle-source"
+
+ try {
+ fileSystem.openReadOnly(path)
+ fail()
+ } catch (_: IOException) {
+ }
+ }
+
+ @Test fun openReadWriteCreatesAbsentFile() {
+ val path = base / "file-handle-source"
+
+ fileSystem.openReadWrite(path).use {
+ }
+
+ assertEquals("", path.readUtf8())
+ }
+
+ @Test fun openReadWriteCreatesAbsentFileMustCreate() {
+ val path = base / "file-handle-source"
+
+ fileSystem.openReadWrite(path, mustCreate = true).use {
+ }
+
+ assertEquals("", path.readUtf8())
+ }
+
+ @Test fun openReadWriteMustCreateThrowsIfAlreadyExists() {
+ val path = base / "file-handle-source"
+ path.writeUtf8("First!")
+
+ assertFailsWith<IOException> {
+ fileSystem.openReadWrite(path, mustCreate = true).use {}
+ }
+ }
+
+ @Test fun openReadWriteMustExist() {
+ val path = base / "file-handle-source"
+ path.writeUtf8("one")
+
+ fileSystem.openReadWrite(path, mustExist = true).use { handle ->
+ handle.write(3L, Buffer().writeUtf8(" two"), 4L)
+ }
+
+ assertEquals("one two", path.readUtf8())
+ }
+
+ @Test fun openReadWriteMustExistThrowsIfAbsent() {
+ val path = base / "file-handle-source"
+
+ assertFailsWith<IOException> {
+ fileSystem.openReadWrite(path, mustExist = true).use {}
+ }
+ }
+
+ @Test fun openReadWriteThrowsIfBothMustCreateAndMustExist() {
+ val path = base / "file-handle-source"
+
+ assertFailsWith<IllegalArgumentException> {
+ fileSystem.openReadWrite(path, mustCreate = true, mustExist = true).use {}
+ }
+ }
+
+ @Test fun sinkPositionFailsAfterClose() {
+ val path = base / "sink-position-fails-after-close"
+
+ fileSystem.openReadWrite(path).use { handle ->
+ val sink = handle.sink()
+ sink.close()
+ try {
+ handle.position(sink)
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+ try {
+ handle.position(sink.buffer())
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+ }
+ }
+
+ @Test fun sinkRepositionFailsAfterClose() {
+ val path = base / "sink-reposition-fails-after-close"
+
+ fileSystem.openReadWrite(path).use { handle ->
+ val sink = handle.sink()
+ sink.close()
+ try {
+ handle.reposition(sink, 1L)
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+ try {
+ handle.reposition(sink.buffer(), 1L)
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+ }
+ }
+
+ @Test fun sourcePositionFailsAfterClose() {
+ val path = base / "source-position-fails-after-close"
+
+ fileSystem.openReadWrite(path).use { handle ->
+ val source = handle.source()
+ source.close()
+ try {
+ handle.position(source)
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+ try {
+ handle.position(source.buffer())
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+ }
+ }
+
+ @Test fun sourceRepositionFailsAfterClose() {
+ val path = base / "source-reposition-fails-after-close"
+
+ fileSystem.openReadWrite(path).use { handle ->
+ val source = handle.source()
+ source.close()
+ try {
+ handle.reposition(source, 1L)
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+ try {
+ handle.reposition(source.buffer(), 1L)
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+ }
+ }
+
+ @Test fun sizeFailsAfterClose() {
+ val path = base / "size-fails-after-close"
+
+ val handle = fileSystem.openReadWrite(path)
+ handle.close()
+ try {
+ handle.size()
+ fail()
+ } catch (_: IllegalStateException) {
+ }
+ }
+
+ @Test
+ fun absoluteSymlinkMetadata() {
+ if (!supportsSymlink()) return
+
+ val target = base / "symlink-target"
+ val source = base / "symlink-source"
+
+ val minTime = clock.now()
+ fileSystem.createSymlink(source, target)
+ val maxTime = clock.now()
+
+ val sourceMetadata = fileSystem.metadata(source)
+ // Okio's WasiFileSystem only creates relative symlinks.
+ assertEquals(
+ when {
+ isWasiFileSystem -> target.relativeTo(source.parent!!)
+ else -> target
+ },
+ sourceMetadata.symlinkTarget,
+ )
+ assertInRange(sourceMetadata.createdAt, minTime, maxTime)
+ }
+
+ @Test
+ fun relativeSymlinkMetadata() {
+ if (!supportsSymlink()) return
+
+ val target = "symlink-target".toPath()
+ val source = base / "symlink-source"
+
+ val minTime = clock.now()
+ fileSystem.createSymlink(source, target)
+ val maxTime = clock.now()
+
+ val sourceMetadata = fileSystem.metadata(source)
+ assertEquals(target, sourceMetadata.symlinkTarget)
+ assertInRange(sourceMetadata.createdAt, minTime, maxTime)
+ }
+
+ @Test
+ fun createSymlinkSourceAlreadyExists() {
+ if (!supportsSymlink()) return
+
+ val target = base / "symlink-target"
+ val source = base / "symlink-source"
+ source.writeUtf8("hello")
+ val exception = assertFailsWith<IOException> {
+ fileSystem.createSymlink(source, target)
+ }
+ assertTrue(exception !is FileNotFoundException)
+ }
+
+ @Test
+ fun createSymlinkParentDirectoryDoesNotExist() {
+ if (!supportsSymlink()) return
+
+ val source = base / "no-such-directory" / "source"
+ val target = base / "target"
+ val e = assertFailsWith<IOException> {
+ fileSystem.createSymlink(source, target)
+ }
+ assertTrue(e !is FileNotFoundException)
+ }
+
+ @Test
+ fun openSymlinkSource() {
+ if (!supportsSymlink()) return
+
+ val target = base / "symlink-target"
+ val source = base / "symlink-source"
+ fileSystem.createSymlink(source, target)
+ target.writeUtf8("I am the target file")
+ val sourceContent = fileSystem.source(source).buffer().use { it.readUtf8() }
+ assertEquals("I am the target file", sourceContent)
+ }
+
+ @Test
+ fun openSymlinkSink() {
+ if (!supportsSymlink()) return
+ if (isJimFileSystem()) return
+
+ val target = base / "symlink-target"
+ val source = base / "symlink-source"
+ fileSystem.createSymlink(source, target)
+ fileSystem.sink(source).buffer().use {
+ it.writeUtf8("This writes to the the source file")
+ }
+ assertEquals("This writes to the the source file", target.readUtf8())
+ }
+
+ @Test
+ fun openFileWithDirectorySymlink() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseAA = base / "a" / "a"
+ val baseB = base / "b"
+ val baseBA = base / "b" / "a"
+ fileSystem.createDirectory(baseA)
+ baseAA.writeUtf8("aa")
+ fileSystem.createSymlink(baseB, baseA)
+ assertEquals("aa", baseAA.readUtf8())
+ assertEquals("aa", baseBA.readUtf8())
+ }
+
+ @Test
+ fun openSymlinkFileHandle() {
+ if (!supportsSymlink()) return
+
+ val target = base / "symlink-target"
+ val source = base / "symlink-source"
+ fileSystem.createSymlink(source, target)
+ target.writeUtf8("I am the target file")
+ val sourceContent = fileSystem.openReadOnly(source).use { fileHandle ->
+ fileHandle.source().buffer().use { it.readUtf8() }
+ }
+ assertEquals("I am the target file", sourceContent)
+ }
+
+ @Test
+ fun listSymlinkDirectory() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseAA = base / "a" / "a"
+ val baseAB = base / "a" / "b"
+ val baseB = base / "b"
+ val baseBA = base / "b" / "a"
+ val baseBB = base / "b" / "b"
+ fileSystem.createDirectory(baseA)
+ baseAA.writeUtf8("aa")
+ baseAB.writeUtf8("ab")
+ fileSystem.createSymlink(baseB, baseA)
+ assertEquals(listOf(baseBA, baseBB), fileSystem.list(baseB))
+ }
+
+ @Test
+ fun symlinkFileLastAccessedAt() {
+ if (!supportsSymlink()) return
+
+ val target = base / "symlink-target"
+ val source = base / "symlink-source"
+ target.writeUtf8("a")
+ fileSystem.createSymlink(source, target)
+ tryAdvanceTime()
+ val minTime = clock.now()
+ assertEquals("a", source.readUtf8())
+ val maxTime = clock.now()
+ assertInRange(fileSystem.metadata(source).lastAccessedAt, minTime, maxTime)
+ }
+
+ @Test
+ fun symlinkDirectoryLastAccessedAt() {
+ if (!supportsSymlink()) return
+
+ val baseA = base / "a"
+ val baseAA = base / "a" / "a"
+ val baseB = base / "b"
+ val baseBA = base / "b" / "a"
+ fileSystem.createDirectory(baseA)
+ baseAA.writeUtf8("aa")
+ fileSystem.createSymlink(baseB, baseA)
+ tryAdvanceTime()
+ val minTime = clock.now()
+ assertEquals("aa", baseBA.readUtf8())
+ val maxTime = clock.now()
+ assertInRange(fileSystem.metadata(baseB).lastAccessedAt, minTime, maxTime)
+ }
+
+ @Test
+ fun deleteSymlinkDoesntDeleteTargetFile() {
+ if (!supportsSymlink()) return
+
+ val target = base / "symlink-target"
+ val source = base / "symlink-source"
+ target.writeUtf8("I am the target file")
+ fileSystem.createSymlink(source, target)
+ fileSystem.delete(source)
+ assertEquals("I am the target file", target.readUtf8())
+ }
+
+ @Test
+ fun moveSymlinkDoesntMoveTargetFile() {
+ if (!supportsSymlink()) return
+
+ val target = base / "symlink-target"
+ val source1 = base / "symlink-source-1"
+ val source2 = base / "symlink-source-2"
+ target.writeUtf8("I am the target file")
+ fileSystem.createSymlink(source1, target)
+ fileSystem.atomicMove(source1, source2)
+ assertEquals("I am the target file", target.readUtf8())
+ assertEquals("I am the target file", source2.readUtf8())
+ // Okio's WasiFileSystem only creates relative symlinks.
+ assertEquals(
+ when {
+ isWasiFileSystem -> target.relativeTo(source1.parent!!)
+ else -> target
+ },
+ fileSystem.metadata(source2).symlinkTarget,
+ )
+ }
+
+ @Test
+ fun symlinkCanBeRelative() {
+ if (!supportsSymlink()) return
+
+ val relativeTarget = "symlink-target".toPath()
+ val absoluteTarget = base / relativeTarget
+ val source = base / "symlink-source"
+ absoluteTarget.writeUtf8("I am the target file")
+ fileSystem.createSymlink(source, relativeTarget)
+ assertEquals("I am the target file", source.readUtf8())
+ }
+
+ @Test
+ fun symlinkCanBeRelativeWithDotDots() {
+ if (!supportsSymlink()) return
+
+ val relativeTarget = "../b/symlink-target".toPath()
+ val absoluteTarget = base / "b" / "symlink-target"
+ val absoluteSource = base / "a" / "symlink-source"
+ fileSystem.createDirectory(absoluteSource.parent!!)
+ fileSystem.createDirectory(absoluteTarget.parent!!)
+ absoluteTarget.writeUtf8("I am the target file")
+ fileSystem.createSymlink(absoluteSource, relativeTarget)
+ assertEquals("I am the target file", absoluteSource.readUtf8())
+ }
+
+ @Test
+ fun followingRecursiveSymlinksIsOkay() {
+ if (!supportsSymlink()) return
+
+ val pathA = base / "symlink-a"
+ val pathB = base / "symlink-b"
+ val pathC = base / "symlink-c"
+ val target = base / "symlink-target"
+ fileSystem.createSymlink(pathA, pathB)
+ fileSystem.createSymlink(pathB, pathC)
+ fileSystem.createSymlink(pathC, target)
+ target.writeUtf8("I am the target file")
+ assertEquals("I am the target file", pathC.readUtf8())
+ assertEquals("I am the target file", pathB.readUtf8())
+ assertEquals("I am the target file", pathA.readUtf8())
+ }
+
+ @Test
+ fun symlinkCycle() {
+ if (!supportsSymlink()) return
+
+ val pathA = base / "symlink-a"
+ val pathB = base / "symlink-b"
+ fileSystem.createSymlink(pathA, pathB)
+ fileSystem.createSymlink(pathB, pathA)
+ assertFailsWith<IOException> {
+ pathB.writeUtf8("This should not work")
+ }
+ assertFailsWith<IOException> {
+ pathB.readUtf8()
+ }
+ }
+
+ protected fun supportsSymlink(): Boolean {
+ if (fileSystem.isFakeFileSystem) return fileSystem.allowSymlinks
+ if (windowsLimitations) return false
+ return when (fileSystem::class.simpleName) {
+ "JvmSystemFileSystem",
+ -> false
+ else -> true
+ }
+ }
+
+ private fun expectIOExceptionOnWindows(
+ exceptJs: Boolean = false,
+ block: () -> Unit,
+ ) {
+ val expectCrash = windowsLimitations && (!isNodeJsFileSystem || !exceptJs)
+ try {
+ block()
+ assertFalse(expectCrash)
+ } catch (_: IOException) {
+ assertTrue(expectCrash)
+ }
+ }
+
+ fun Path.readUtf8(): String {
+ return fileSystem.source(this).buffer().use {
+ it.readUtf8()
+ }
+ }
+
+ fun Path.writeUtf8(string: String) {
+ fileSystem.sink(this).buffer().use {
+ it.writeUtf8(string)
+ }
+ }
+
+ fun Path.createDirectory() {
+ fileSystem.createDirectory(this)
+ }
+
+ /**
+ * Returns the earliest file system time that could be recorded for an event occurring at this
+ * instant. This truncates fractional seconds because most host file systems do not use precise
+ * timestamps for file metadata.
+ *
+ * It also pads the result by 200 milliseconds because the host and file system may use different
+ * clocks, allowing the time on the CPU to drift ahead of the time on the file system.
+ */
+ private fun Instant.minFileSystemTime(): Instant {
+ val paddedInstant = minus(200.milliseconds)
+ return fromEpochSeconds(paddedInstant.epochSeconds)
+ }
+
+ /**
+ * Returns the latest file system time that could be recorded for an event occurring at this
+ * instant. This adds 2 seconds and truncates fractional seconds because file systems may defer
+ * assigning the timestamp.
+ *
+ * It also pads the result by 200 milliseconds because the host and file system may use different
+ * clocks, allowing the time on the CPU to drift behind the time on the file system.
+ *
+ * https://docs.microsoft.com/en-us/windows/win32/sysinfo/file-times
+ */
+ private fun Instant.maxFileSystemTime(): Instant {
+ val paddedInstant = plus(200.milliseconds)
+ return fromEpochSeconds(paddedInstant.plus(2.seconds).epochSeconds)
+ }
+
+ /**
+ * Attempt to advance the clock so that any already-issued timestamps will not collide with
+ * timestamps yet to be issued.
+ */
+ private fun tryAdvanceTime() {
+ if (clock is FakeClock) clock.sleep(1.minutes)
+ }
+
+ private fun assertInRange(sampled: Instant?, minTime: Instant, maxTime: Instant) {
+ if (sampled == null) return
+ val minFsTime = minTime.minFileSystemTime()
+ val maxFsTime = maxTime.maxFileSystemTime()
+ assertTrue("expected $sampled in $minFsTime..$maxFsTime (relaxed from $minTime..$maxTime)") {
+ sampled in minFsTime..maxFsTime
+ }
+ }
+
+ private fun isJvmFileSystemOnWindows(): Boolean {
+ return windowsLimitations && fileSystem::class.simpleName == "JvmSystemFileSystem"
+ }
+
+ private fun isJimFileSystem(): Boolean {
+ return "JimfsFileSystem" in fileSystem.toString()
+ }
+
+ private fun isNodeJsFileSystemOnWindows(): Boolean {
+ return windowsLimitations && fileSystem::class.simpleName == "NodeJsFileSystem"
+ }
+}
diff --git a/okio/src/commonTest/kotlin/okio/FakeClock.kt b/okio-testing-support/src/commonMain/kotlin/okio/FakeClock.kt
index 31cf5503..9667090d 100644
--- a/okio/src/commonTest/kotlin/okio/FakeClock.kt
+++ b/okio-testing-support/src/commonMain/kotlin/okio/FakeClock.kt
@@ -15,14 +15,10 @@
*/
package okio
-import kotlinx.datetime.Clock
-import kotlinx.datetime.Instant
import kotlin.time.Duration
-import kotlin.time.ExperimentalTime
-@ExperimentalTime
-internal class FakeClock : Clock {
- var time = Instant.parse("2021-01-01T00:00:00Z")
+class FakeClock : Clock {
+ var time = fromEpochSeconds(1609459200L) // 2021-01-01T00:00:00Z
override fun now() = time
diff --git a/okio-testing-support/src/commonMain/kotlin/okio/TestingCommon.kt b/okio-testing-support/src/commonMain/kotlin/okio/TestingCommon.kt
new file mode 100644
index 00000000..d84642ff
--- /dev/null
+++ b/okio-testing-support/src/commonMain/kotlin/okio/TestingCommon.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.random.Random
+import kotlin.test.assertEquals
+import kotlin.time.Duration
+import okio.ByteString.Companion.toByteString
+
+fun Char.repeat(count: Int): String {
+ return toString().repeat(count)
+}
+
+fun assertArrayEquals(a: ByteArray, b: ByteArray) {
+ assertEquals(a.contentToString(), b.contentToString())
+}
+
+fun randomBytes(length: Int): ByteString {
+ val random = Random(0)
+ val randomBytes = ByteArray(length)
+ random.nextBytes(randomBytes)
+ return ByteString.of(*randomBytes)
+}
+
+fun randomToken(length: Int) = Random.nextBytes(length).toByteString(0, length).hex()
+
+expect fun isBrowser(): Boolean
+
+expect fun isWasm(): Boolean
+
+val FileMetadata.createdAt: Instant?
+ get() {
+ val createdAt = createdAtMillis ?: return null
+ return fromEpochMilliseconds(createdAt)
+ }
+
+val FileMetadata.lastModifiedAt: Instant?
+ get() {
+ val lastModifiedAt = lastModifiedAtMillis ?: return null
+ return fromEpochMilliseconds(lastModifiedAt)
+ }
+
+val FileMetadata.lastAccessedAt: Instant?
+ get() {
+ val lastAccessedAt = lastAccessedAtMillis ?: return null
+ return fromEpochMilliseconds(lastAccessedAt)
+ }
+
+/*
+ * This file contains some declarations from kotlinx.datetime used by [AbstractFileSystemTest], but
+ * that we can't use because that library isn't yet available for WASM. We should delete these when
+ * WASM is supported in kotlinx.datetime.
+ */
+
+expect interface Clock {
+ fun now(): Instant
+}
+
+expect class Instant : Comparable<Instant> {
+ val epochSeconds: Long
+
+ operator fun plus(duration: Duration): Instant
+
+ operator fun minus(duration: Duration): Instant
+}
+
+expect fun fromEpochSeconds(
+ epochSeconds: Long,
+): Instant
+
+expect fun fromEpochMilliseconds(epochMilliseconds: Long): Instant
+
+expect val FileSystem.isFakeFileSystem: Boolean
+
+expect val FileSystem.allowSymlinks: Boolean
+
+expect val FileSystem.allowReadsWhileWriting: Boolean
+
+expect var FileSystem.workingDirectory: Path
diff --git a/okio-testing-support/src/jsMain/kotlin/okio/TestingJs.kt b/okio-testing-support/src/jsMain/kotlin/okio/TestingJs.kt
new file mode 100644
index 00000000..21442a5c
--- /dev/null
+++ b/okio-testing-support/src/jsMain/kotlin/okio/TestingJs.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+actual fun isBrowser(): Boolean {
+ return js("""(globalThis.window || null)""") != null
+}
+
+actual fun isWasm() = false
diff --git a/okio-testing-support/src/jvmMain/kotlin/okio/TestingExecutors.kt b/okio-testing-support/src/jvmMain/kotlin/okio/TestingExecutors.kt
new file mode 100644
index 00000000..4561240f
--- /dev/null
+++ b/okio-testing-support/src/jvmMain/kotlin/okio/TestingExecutors.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 Block, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.ThreadFactory
+
+object TestingExecutors {
+ val isLoom = System.getProperty("loomEnabled").toBoolean()
+
+ fun newScheduledExecutorService(corePoolSize: Int = 0): ScheduledExecutorService = if (isLoom) {
+ Executors.newScheduledThreadPool(corePoolSize, newVirtualThreadFactory())
+ } else {
+ Executors.newScheduledThreadPool(corePoolSize)
+ }
+
+ fun newExecutorService(corePoolSize: Int = 0): ExecutorService = if (isLoom) {
+ Executors.newScheduledThreadPool(corePoolSize, newVirtualThreadFactory())
+ } else {
+ Executors.newScheduledThreadPool(corePoolSize)
+ }
+
+ fun newVirtualThreadFactory(): ThreadFactory {
+ val threadBuilder = Thread::class.java.getMethod("ofVirtual").invoke(null)
+ return Class.forName("java.lang.Thread\$Builder").getMethod("factory").invoke(threadBuilder) as ThreadFactory
+ }
+
+ fun newVirtualThreadPerTaskExecutor(): ExecutorService {
+ return Executors::class.java.getMethod("newVirtualThreadPerTaskExecutor").invoke(null) as ExecutorService
+ }
+}
diff --git a/okio-testing-support/src/jvmMain/kotlin/okio/TestingJvm.kt b/okio-testing-support/src/jvmMain/kotlin/okio/TestingJvm.kt
new file mode 100644
index 00000000..d6e274db
--- /dev/null
+++ b/okio-testing-support/src/jvmMain/kotlin/okio/TestingJvm.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+actual fun isBrowser() = false
+
+actual fun isWasm() = false
diff --git a/okio-testing-support/src/nativeMain/kotlin/okio/TestingNative.kt b/okio-testing-support/src/nativeMain/kotlin/okio/TestingNative.kt
new file mode 100644
index 00000000..d6e274db
--- /dev/null
+++ b/okio-testing-support/src/nativeMain/kotlin/okio/TestingNative.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+actual fun isBrowser() = false
+
+actual fun isWasm() = false
diff --git a/okio-testing-support/src/nonWasmMain/kotlin/okio/TestingNonWasm.kt b/okio-testing-support/src/nonWasmMain/kotlin/okio/TestingNonWasm.kt
new file mode 100644
index 00000000..a331ce7d
--- /dev/null
+++ b/okio-testing-support/src/nonWasmMain/kotlin/okio/TestingNonWasm.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import okio.fakefilesystem.FakeFileSystem
+
+actual typealias Clock = kotlinx.datetime.Clock
+
+actual typealias Instant = kotlinx.datetime.Instant
+
+actual fun fromEpochSeconds(
+ epochSeconds: Long,
+) = Instant.fromEpochSeconds(epochSeconds)
+
+actual fun fromEpochMilliseconds(
+ epochMilliseconds: Long,
+) = Instant.fromEpochMilliseconds(epochMilliseconds)
+
+actual val FileSystem.isFakeFileSystem: Boolean
+ get() = this is FakeFileSystem
+
+actual val FileSystem.allowSymlinks: Boolean
+ get() = (this as? FakeFileSystem)?.allowSymlinks == true
+
+actual val FileSystem.allowReadsWhileWriting: Boolean
+ get() = (this as? FakeFileSystem)?.allowReadsWhileWriting == true
+
+actual var FileSystem.workingDirectory: Path
+ get() {
+ return when (this) {
+ is FakeFileSystem -> workingDirectory
+ is ForwardingFileSystem -> delegate.workingDirectory
+ else -> error("cannot get working directory: $this")
+ }
+ }
+ set(value) {
+ when (this) {
+ is FakeFileSystem -> workingDirectory = value
+ is ForwardingFileSystem -> delegate.workingDirectory = value
+ else -> error("cannot set working directory: $this")
+ }
+ }
diff --git a/okio-testing-support/src/wasmMain/kotlin/okio/TestingWasm.kt b/okio-testing-support/src/wasmMain/kotlin/okio/TestingWasm.kt
new file mode 100644
index 00000000..cb841783
--- /dev/null
+++ b/okio-testing-support/src/wasmMain/kotlin/okio/TestingWasm.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.time.Duration
+
+actual fun isBrowser() = false
+
+actual fun isWasm() = true
+
+actual interface Clock {
+ actual fun now(): Instant
+}
+
+actual class Instant(
+ private val epochMilliseconds: Long,
+) : Comparable<Instant> {
+ actual val epochSeconds: Long
+ get() = epochMilliseconds / 1_000L
+
+ actual operator fun plus(duration: Duration) =
+ Instant(epochMilliseconds + duration.inWholeMilliseconds)
+
+ actual operator fun minus(duration: Duration) =
+ Instant(epochMilliseconds - duration.inWholeMilliseconds)
+
+ override fun compareTo(other: Instant) =
+ epochMilliseconds.compareTo(other.epochMilliseconds)
+}
+
+actual fun fromEpochSeconds(epochSeconds: Long) =
+ Instant(epochSeconds * 1_000L)
+
+actual fun fromEpochMilliseconds(epochMilliseconds: Long) =
+ Instant(epochMilliseconds)
+
+actual val FileSystem.isFakeFileSystem: Boolean
+ get() = false
+
+actual val FileSystem.allowSymlinks: Boolean
+ get() = error("unexpected call")
+
+actual val FileSystem.allowReadsWhileWriting: Boolean
+ get() = error("unexpected call")
+
+actual var FileSystem.workingDirectory: Path
+ get() = error("unexpected call")
+ set(_) = error("unexpected call")
diff --git a/okio-testing-support/src/wasmWasiMain/kotlin/okio/WasiClock.kt b/okio-testing-support/src/wasmWasiMain/kotlin/okio/WasiClock.kt
new file mode 100644
index 00000000..101a075b
--- /dev/null
+++ b/okio-testing-support/src/wasmWasiMain/kotlin/okio/WasiClock.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.wasm.unsafe.UnsafeWasmMemoryApi
+import kotlin.wasm.unsafe.withScopedMemoryAllocator
+import okio.internal.preview1.clock_time_get
+import okio.internal.preview1.clockid_realtime
+
+object WasiClock : Clock {
+ @OptIn(UnsafeWasmMemoryApi::class)
+ override fun now(): Instant {
+ withScopedMemoryAllocator { allocator ->
+ val returnPointer = allocator.allocate(8) // timestamp is u64.
+ val errno = clock_time_get(
+ id = clockid_realtime,
+ precision = 1_000_000_000L, // 1-second precision.
+ returnPointer.address.toInt(),
+ )
+ if (errno != 0) throw IllegalStateException("failed to get now: $errno")
+
+ val nanos = returnPointer.loadLong()
+ return Instant(epochMilliseconds = nanos / 1_000_000L)
+ }
+ }
+}
diff --git a/okio-testing-support/src/wasmWasiMain/kotlin/okio/internal/preview1/Clockid.kt b/okio-testing-support/src/wasmWasiMain/kotlin/okio/internal/preview1/Clockid.kt
new file mode 100644
index 00000000..e6e9148f
--- /dev/null
+++ b/okio-testing-support/src/wasmWasiMain/kotlin/okio/internal/preview1/Clockid.kt
@@ -0,0 +1,30 @@
+// Copyright 2019-2023 the Contributors to the WASI Specification
+// This file is adapted from the WASI preview1 spec here:
+// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
+package okio.internal.preview1
+
+internal typealias clockid = Int
+
+/**
+ * The clock measuring real time. Time value zero corresponds with
+ * 1970-01-01T00:00:00Z.
+ */
+internal val clockid_realtime = 0
+
+/**
+ * The store-wide monotonic clock, which is defined as a clock measuring
+ * real time, whose value cannot be adjusted and which cannot have negative
+ * clock jumps. The epoch of this clock is undefined. The absolute time
+ * value of this clock therefore has no meaning.
+ */
+internal val clockid_monotonic = 1
+
+/**
+ * The CPU-time clock associated with the current process.
+ */
+internal val clockid_process_cputime_id = 2
+
+/**
+ * The CPU-time clock associated with the current thread.
+ */
+internal val clockid_thread_cputime_id = 3
diff --git a/okio-testing-support/src/wasmWasiMain/kotlin/okio/internal/preview1/Preview1Clock.kt b/okio-testing-support/src/wasmWasiMain/kotlin/okio/internal/preview1/Preview1Clock.kt
new file mode 100644
index 00000000..50726af1
--- /dev/null
+++ b/okio-testing-support/src/wasmWasiMain/kotlin/okio/internal/preview1/Preview1Clock.kt
@@ -0,0 +1,19 @@
+// Copyright 2019-2023 the Contributors to the WASI Specification
+// This file is adapted from the WASI preview1 spec here:
+// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
+package okio.internal.preview1
+
+import kotlin.wasm.WasmImport
+
+/**
+ * clock_time_get(id: clockid, precision: timestamp) -> Result<timestamp, errno>
+ *
+ * Return the time value of a clock.
+ * Note: This is similar to `clock_gettime` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "clock_time_get")
+internal external fun clock_time_get(
+ id: clockid,
+ precision: Long,
+ returnPointer: Int,
+): Int // should be Short??
diff --git a/okio-wasifilesystem/README.md b/okio-wasifilesystem/README.md
new file mode 100644
index 00000000..5ff87d4c
--- /dev/null
+++ b/okio-wasifilesystem/README.md
@@ -0,0 +1,12 @@
+WASI FileSystem
+===============
+
+⚠️ This is a work in progress ⚠️
+
+This module implements Okio's FileSystem API using the [WebAssembly System Interface (WASI)][wasi].
+
+It currently uses the WASI [preview1] APIs and is tested on NodeJS with the
+`--experimental-wasi-unstable-preview1` option.
+
+[wasi]: https://wasi.dev/
+[preview1]: https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
diff --git a/okio-wasifilesystem/build.gradle.kts b/okio-wasifilesystem/build.gradle.kts
new file mode 100644
index 00000000..aaf43faa
--- /dev/null
+++ b/okio-wasifilesystem/build.gradle.kts
@@ -0,0 +1,111 @@
+import com.vanniktech.maven.publish.JavadocJar.Empty
+import com.vanniktech.maven.publish.KotlinMultiplatform
+import com.vanniktech.maven.publish.MavenPublishBaseExtension
+
+plugins {
+ kotlin("multiplatform")
+ // TODO: Restore Dokka once this issue is resolved.
+ // https://github.com/Kotlin/dokka/issues/3038
+ // id("org.jetbrains.dokka")
+ id("com.vanniktech.maven.publish.base")
+ id("build-support")
+ id("binary-compatibility-validator")
+}
+
+kotlin {
+ configureOrCreateWasmPlatform(
+ js = false,
+ wasi = true,
+ )
+ sourceSets {
+ all {
+ languageSettings.optIn("kotlin.wasm.unsafe.UnsafeWasmMemoryApi")
+ }
+ val wasmWasiMain by getting {
+ dependencies {
+ implementation(projects.okio)
+ }
+ }
+ val wasmWasiTest by getting {
+ dependencies {
+ implementation(projects.okioTestingSupport)
+ implementation(libs.kotlin.test)
+ }
+ }
+ }
+}
+
+configure<MavenPublishBaseExtension> {
+ // TODO: switch from 'Empty' to 'Dokka' once this issue is resolved.
+ // https://github.com/Kotlin/dokka/issues/3038
+ configure(
+ KotlinMultiplatform(javadocJar = Empty()),
+ )
+}
+
+/**
+ * Inspired by runner.mjs in kowasm, this rewrites the JavaScript bootstrap script to set up WASI.
+ *
+ * See also:
+ * * https://github.com/kowasm/kowasm
+ * * https://github.com/nodejs/node/blob/main/doc/api/wasi.md
+ *
+ * This task overwrites the output of `compileTestDevelopmentExecutableKotlinWasmWasi` and must run
+ * after that task. It must also run before the WASM test execution tasks that read this script.
+ *
+ * Note that this includes which file paths are exposed to the WASI sandbox.
+ */
+val injectWasiInit by tasks.creating {
+ dependsOn("compileTestDevelopmentExecutableKotlinWasmWasi")
+ val moduleName = "${rootProject.name}-${project.name}-wasm-wasi-test"
+
+ val entryPointMjs = File(
+ buildDir,
+ "compileSync/wasmWasi/test/testDevelopmentExecutable/kotlin/$moduleName.mjs"
+ )
+
+ outputs.file(entryPointMjs)
+
+ doLast {
+ val base = File(System.getProperty("java.io.tmpdir"), "okio-wasifilesystem-test")
+ val baseA = File(base, "a")
+ val baseB = File(base, "b")
+ base.mkdirs()
+ baseA.mkdirs()
+ baseB.mkdirs()
+
+ entryPointMjs.writeText(
+ """
+ import { WASI } from 'wasi';
+ import { argv, env } from 'node:process';
+
+ export const wasi = new WASI({
+ version: 'preview1',
+ preopens: {
+ '/tmp': '$base',
+ '/a': '$baseA',
+ '/b': '$baseB'
+ }
+ });
+
+ const module = await import(/* webpackIgnore: true */'node:module');
+ const require = module.default.createRequire(import.meta.url);
+ const fs = require('fs');
+ const path = require('path');
+ const url = require('url');
+ const filepath = url.fileURLToPath(import.meta.url);
+ const dirpath = path.dirname(filepath);
+ const wasmBuffer = fs.readFileSync(path.resolve(dirpath, './$moduleName.wasm'));
+ const wasmModule = new WebAssembly.Module(wasmBuffer);
+ const wasmInstance = new WebAssembly.Instance(wasmModule, wasi.getImportObject());
+
+ wasi.initialize(wasmInstance);
+
+ export default wasmInstance.exports;
+ """.trimIndent()
+ )
+ }
+}
+tasks.named("wasmWasiNodeTest").configure {
+ dependsOn(injectWasiInit)
+}
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/FileSink.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/FileSink.kt
new file mode 100644
index 00000000..f3b69a11
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/FileSink.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.wasm.unsafe.withScopedMemoryAllocator
+import okio.internal.ErrnoException
+import okio.internal.fdClose
+import okio.internal.preview1.fd
+import okio.internal.preview1.fd_sync
+import okio.internal.preview1.fd_write
+import okio.internal.preview1.size
+import okio.internal.write
+
+internal class FileSink(
+ private val fd: fd,
+) : Sink {
+ private var closed = false
+ private val cursor = Buffer.UnsafeCursor()
+
+ override fun write(source: Buffer, byteCount: Long) {
+ require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
+ require(source.size >= byteCount) { "source.size=${source.size} < byteCount=$byteCount" }
+ check(!closed) { "closed" }
+
+ var bytesRemaining = byteCount
+ source.readAndWriteUnsafe(cursor)
+ try {
+ while (bytesRemaining > 0L) {
+ check(cursor.next() != -1)
+
+ val count = minOf(bytesRemaining, cursor.end.toLong() - cursor.start).toInt()
+ if (fdWrite(cursor.data!!, cursor.start, count) != count) {
+ throw IOException("write failed")
+ }
+ bytesRemaining -= count
+ }
+ } finally {
+ cursor.close()
+ source.skip(byteCount)
+ }
+ }
+
+ private fun fdWrite(data: ByteArray, offset: Int, count: Int): size {
+ withScopedMemoryAllocator { allocator ->
+ val dataPointer = allocator.write(data, offset, count)
+
+ val iovec = allocator.allocate(8)
+ iovec.storeInt(dataPointer.address.toInt())
+ (iovec + 4).storeInt(count)
+
+ val returnPointer = allocator.allocate(4) // `size` is u32, 4 bytes.
+ val errno = fd_write(
+ fd = fd,
+ iovs = iovec.address.toInt(),
+ iovsSize = 1,
+ returnPointer = returnPointer.address.toInt(),
+ )
+ if (errno != 0) throw ErrnoException(errno.toShort())
+
+ return returnPointer.loadInt()
+ }
+ }
+
+ override fun flush() {
+ val errno = fd_sync(fd)
+ if (errno != 0) throw ErrnoException(errno.toShort())
+ }
+
+ override fun timeout() = Timeout.NONE
+
+ override fun close() {
+ if (closed) return
+ closed = true
+ fdClose(fd)
+ }
+}
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/FileSource.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/FileSource.kt
new file mode 100644
index 00000000..d9d98666
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/FileSource.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.wasm.unsafe.withScopedMemoryAllocator
+import okio.internal.ErrnoException
+import okio.internal.fdClose
+import okio.internal.preview1.fd
+import okio.internal.preview1.fd_read
+import okio.internal.preview1.size
+import okio.internal.read
+
+internal class FileSource(
+ private val fd: fd,
+) : Source {
+ private val unsafeCursor = Buffer.UnsafeCursor()
+ private var closed = false
+
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
+ check(!closed) { "closed" }
+ val sinkInitialSize = sink.size
+
+ // Request a writable segment in `sink`. We request at least 1024 bytes, unless the request is
+ // for smaller than that, in which case we request only that many bytes.
+ val cursor = sink.readAndWriteUnsafe(unsafeCursor)
+ val addedCapacityCount = cursor.expandBuffer(minByteCount = minOf(byteCount, 1024L).toInt())
+
+ // Now that we have a writable segment, figure out how many bytes to read. This is the smaller
+ // of the user's requested byte count, and the segment's writable capacity.
+ val attemptCount = minOf(byteCount, addedCapacityCount).toInt()
+
+ // Copy bytes from the file to the segment.
+ val bytesRead = fdRead(cursor.data!!, cursor.start, attemptCount)
+
+ // Remove new capacity that was added but not used.
+ cursor.resizeBuffer(sinkInitialSize + bytesRead)
+ cursor.close()
+
+ return when {
+ bytesRead == attemptCount -> bytesRead.toLong()
+ else -> if (bytesRead == 0) -1L else bytesRead.toLong()
+ }
+ }
+
+ private fun fdRead(data: ByteArray, offset: Int, count: Int): size {
+ withScopedMemoryAllocator { allocator ->
+ val dataPointer = allocator.allocate(count)
+
+ val iovec = allocator.allocate(8)
+ iovec.storeInt(dataPointer.address.toInt())
+ (iovec + 4).storeInt(count)
+
+ val returnPointer = allocator.allocate(4) // `size` is u32, 4 bytes.
+ val errno = fd_read(
+ fd = fd,
+ iovs = iovec.address.toInt(),
+ iovsSize = 1,
+ returnPointer = returnPointer.address.toInt(),
+ )
+ if (errno != 0) throw ErrnoException(errno.toShort())
+
+ val byteCount = returnPointer.loadInt()
+ if (byteCount != -1) {
+ dataPointer.read(data, offset, byteCount)
+ }
+
+ return byteCount
+ }
+ }
+
+ override fun timeout(): Timeout = Timeout.NONE
+
+ override fun close() {
+ if (closed) return
+ closed = true
+ fdClose(fd)
+ }
+}
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/WasiFileHandle.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/WasiFileHandle.kt
new file mode 100644
index 00000000..48271943
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/WasiFileHandle.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.wasm.unsafe.Pointer
+import kotlin.wasm.unsafe.withScopedMemoryAllocator
+import okio.internal.ErrnoException
+import okio.internal.fdClose
+import okio.internal.preview1.fd
+import okio.internal.preview1.fd_filestat_get
+import okio.internal.preview1.fd_filestat_set_size
+import okio.internal.preview1.fd_pread
+import okio.internal.preview1.fd_pwrite
+import okio.internal.preview1.fd_sync
+import okio.internal.read
+import okio.internal.write
+
+internal class WasiFileHandle(
+ private val fd: fd,
+ readWrite: Boolean,
+) : FileHandle(readWrite) {
+ override fun protectedSize(): Long {
+ withScopedMemoryAllocator { allocator ->
+ val returnPointer: Pointer = allocator.allocate(64) // filestat is 64 bytes.
+ val errno = fd_filestat_get(fd, returnPointer.address.toInt())
+ if (errno != 0) throw ErrnoException(errno.toShort())
+
+ return (returnPointer + 32).loadLong()
+ }
+ }
+
+ override fun protectedRead(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ): Int {
+ withScopedMemoryAllocator { allocator ->
+ val dataPointer = allocator.allocate(byteCount)
+
+ val iovec = allocator.allocate(8)
+ iovec.storeInt(dataPointer.address.toInt())
+ (iovec + 4).storeInt(byteCount)
+
+ val returnPointer = allocator.allocate(4) // `size` is u32, 4 bytes.
+ val errno = fd_pread(
+ fd = fd,
+ iovs = iovec.address.toInt(),
+ iovsSize = 1,
+ offset = fileOffset,
+ returnPointer = returnPointer.address.toInt(),
+ )
+ if (errno != 0) throw ErrnoException(errno.toShort())
+
+ val readByteCount = returnPointer.loadInt()
+ if (byteCount != -1) {
+ dataPointer.read(array, arrayOffset, readByteCount)
+ }
+
+ if (readByteCount == 0) return -1
+ return readByteCount
+ }
+ }
+
+ override fun protectedWrite(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ) {
+ withScopedMemoryAllocator { allocator ->
+ val dataPointer = allocator.write(array, arrayOffset, byteCount)
+
+ val iovec = allocator.allocate(8)
+ iovec.storeInt(dataPointer.address.toInt())
+ (iovec + 4).storeInt(byteCount)
+
+ val returnPointer = allocator.allocate(4) // `size` is u32, 4 bytes.
+ val errno = fd_pwrite(
+ fd = fd,
+ iovs = iovec.address.toInt(),
+ iovsSize = 1,
+ offset = fileOffset,
+ returnPointer = returnPointer.address.toInt(),
+ )
+ if (errno != 0) throw ErrnoException(errno.toShort())
+
+ val writtenByteCount = returnPointer.loadInt()
+ if (writtenByteCount != byteCount) {
+ throw IOException("expected $byteCount but was $writtenByteCount")
+ }
+ }
+ }
+
+ override fun protectedFlush() {
+ val errno = fd_sync(fd)
+ if (errno != 0) throw ErrnoException(errno.toShort())
+ }
+
+ override fun protectedResize(size: Long) {
+ val errno = fd_filestat_set_size(fd, size)
+ if (errno != 0) throw ErrnoException(errno.toShort())
+ }
+
+ override fun protectedClose() {
+ fdClose(fd)
+ }
+}
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/WasiFileSystem.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/WasiFileSystem.kt
new file mode 100644
index 00000000..9e820215
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/WasiFileSystem.kt
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.wasm.unsafe.Pointer
+import kotlin.wasm.unsafe.withScopedMemoryAllocator
+import okio.Path.Companion.toPath
+import okio.internal.ErrnoException
+import okio.internal.fdClose
+import okio.internal.preview1.Errno
+import okio.internal.preview1.dirnamelen
+import okio.internal.preview1.fd
+import okio.internal.preview1.fd_prestat_dir_name
+import okio.internal.preview1.fd_prestat_get
+import okio.internal.preview1.fd_readdir
+import okio.internal.preview1.fdflags
+import okio.internal.preview1.fdflags_append
+import okio.internal.preview1.filetype
+import okio.internal.preview1.filetype_directory
+import okio.internal.preview1.filetype_regular_file
+import okio.internal.preview1.filetype_symbolic_link
+import okio.internal.preview1.oflag_creat
+import okio.internal.preview1.oflag_directory
+import okio.internal.preview1.oflag_excl
+import okio.internal.preview1.oflag_trunc
+import okio.internal.preview1.oflags
+import okio.internal.preview1.path_create_directory
+import okio.internal.preview1.path_filestat_get
+import okio.internal.preview1.path_open
+import okio.internal.preview1.path_readlink
+import okio.internal.preview1.path_remove_directory
+import okio.internal.preview1.path_rename
+import okio.internal.preview1.path_symlink
+import okio.internal.preview1.path_unlink_file
+import okio.internal.preview1.right_fd_filestat_get
+import okio.internal.preview1.right_fd_filestat_set_size
+import okio.internal.preview1.right_fd_read
+import okio.internal.preview1.right_fd_readdir
+import okio.internal.preview1.right_fd_seek
+import okio.internal.preview1.right_fd_sync
+import okio.internal.preview1.right_fd_write
+import okio.internal.preview1.rights
+import okio.internal.readString
+import okio.internal.write
+
+/**
+ * Use [WASI] to implement the Okio file system interface.
+ *
+ * [WASI]: https://wasi.dev/
+ */
+object WasiFileSystem : FileSystem() {
+ private val preopens: List<Preopen> = buildList {
+ // File descriptor of the first preopen in the `WASI` instance's configured `preopens` property.
+ // This is 3 by default, assuming `stdin` is 0, `stdout` is 1, and `stderr` is 2. Other preopens
+ // are assigned sequentially starting at this value.
+ val firstPreopen = 3
+
+ withScopedMemoryAllocator { allocator ->
+ val bufSize = 2048
+ val bufPointer = allocator.allocate(bufSize)
+
+ for (fd in firstPreopen..Int.MAX_VALUE) {
+ val getReturnPointer = allocator.allocate(12)
+
+ val getErrno = fd_prestat_get(fd, getReturnPointer.address.toInt())
+ if (getErrno == Errno.badf.ordinal) break // No more preopens.
+ if (getErrno != 0) throw ErrnoException(getErrno.toShort())
+
+ val size = (getReturnPointer + 4).loadInt()
+ require(size + 1 < bufSize) { "unexpected preopen size: $size" }
+ val dirNameErrno = fd_prestat_dir_name(fd, bufPointer.address.toInt(), size + 1)
+ if (dirNameErrno != 0) throw ErrnoException(dirNameErrno.toShort())
+ val dirName = bufPointer.readString(size)
+ val dirNamePath = dirName.toPath()
+ add(Preopen(dirNamePath, dirNamePath.segmentsBytes, fd))
+ }
+ }
+ }
+
+ private val relativePathPreopen: Preopen = preopens.firstOrNull()
+ ?: throw IllegalStateException("no preopens")
+
+ override fun canonicalize(path: Path): Path {
+ val absolutePath = when {
+ path.isAbsolute -> path
+ else -> relativePathPreopen.path.resolve(path, normalize = true)
+ }
+
+ // There's no APIs in preview1 to canonicalize a path. We give it a best effort by resolving
+ // all symlinks, but this could result in a relative path.
+ val result = resolveSymlinks(absolutePath, 0).normalized()
+
+ check(result.isAbsolute) {
+ "Canonicalize $path returned non-absolute path: $result"
+ }
+
+ return result
+ }
+
+ private fun resolveSymlinks(
+ path: Path,
+ recurseCount: Int = 0,
+ ): Path {
+ // 40 is chosen for consistency with the Linux kernel (which previously used 8).
+ if (recurseCount > 40) throw IOException("symlink cycle?")
+
+ val parent = path.parent
+ val resolvedParent = when {
+ parent != null -> resolveSymlinks(parent, recurseCount + 1)
+ else -> null
+ }
+ val pathWithResolvedParent = when {
+ resolvedParent != null -> resolvedParent.resolve(path.name)
+ else -> path
+ }
+
+ val symlinkTarget = metadata(pathWithResolvedParent).symlinkTarget
+ ?: return pathWithResolvedParent
+
+ val resolvedSymlinkTarget = when {
+ symlinkTarget.isAbsolute -> symlinkTarget
+ resolvedParent != null -> resolvedParent.resolve(symlinkTarget)
+ else -> symlinkTarget
+ }
+
+ return resolveSymlinks(resolvedSymlinkTarget, recurseCount + 1)
+ }
+
+ override fun metadataOrNull(path: Path): FileMetadata? {
+ withScopedMemoryAllocator { allocator ->
+ val returnPointer = allocator.allocate(64)
+ val preopen = preopenForPath(path) ?: return null
+ val (pathAddress, pathSize) = allocator.write(path.toString())
+
+ val errno = path_filestat_get(
+ fd = preopen.fd,
+ flags = 0,
+ path = pathAddress.address.toInt(),
+ pathSize = pathSize,
+ returnPointer = returnPointer.address.toInt(),
+ )
+
+ when (errno) {
+ // 'notcapable' means our preopens don't cover this path. This will happen for paths
+ // like '/' that are an ancestor of our preopens.
+ Errno.notcapable.ordinal -> return FileMetadata(isDirectory = true)
+ Errno.noent.ordinal -> return null
+ }
+
+ if (errno != 0) throw ErrnoException(errno.toShort())
+
+ // Skip device, offset 0.
+ // Skip ino, offset 8.
+ val filetype: filetype = (returnPointer + 16).loadByte()
+ // Skip nlink, offset 24.
+ val filesize: Long = (returnPointer + 32).loadLong()
+ val atim: Long = (returnPointer + 40).loadLong() // Access time, Nanoseconds.
+ val mtim: Long = (returnPointer + 48).loadLong() // Modification time, Nanoseconds.
+ val ctim: Long = (returnPointer + 56).loadLong() // Status change time, Nanoseconds.
+
+ val symlinkTarget: Path? = when (filetype) {
+ filetype_symbolic_link -> {
+ val bufLen = filesize.toInt() + 1
+ val bufPointer = allocator.allocate(bufLen)
+ val readlinkReturnPointer = allocator.allocate(4) // `size` is u32, 4 bytes.
+ val readlinkErrno = path_readlink(
+ fd = preopen.fd,
+ path = pathAddress.address.toInt(),
+ pathSize = pathSize,
+ buf = bufPointer.address.toInt(),
+ buf_len = bufLen,
+ returnPointer = readlinkReturnPointer.address.toInt(),
+ )
+ if (readlinkErrno != 0) throw ErrnoException(readlinkErrno.toShort())
+ val symlinkSize = readlinkReturnPointer.loadInt()
+ val symlink = bufPointer.readString(symlinkSize)
+ symlink.toPath()
+ }
+
+ else -> null
+ }
+
+ return FileMetadata(
+ isRegularFile = filetype == filetype_regular_file,
+ isDirectory = filetype == filetype_directory,
+ symlinkTarget = symlinkTarget,
+ size = filesize,
+ createdAtMillis = ctim / 1_000_000L, // Nanos to millis.
+ lastModifiedAtMillis = mtim / 1_000_000L, // Nanos to millis.
+ lastAccessedAtMillis = atim / 1_000_000L, // Nanos to millis.
+ )
+ }
+ }
+
+ override fun list(dir: Path): List<Path> {
+ val fd = pathOpen(
+ path = dir,
+ oflags = oflag_directory,
+ rightsBase = right_fd_readdir,
+ )
+ try {
+ return list(dir, fd)
+ } finally {
+ fdClose(fd)
+ }
+ }
+
+ override fun listOrNull(dir: Path): List<Path>? {
+ // TODO: stop using exceptions for flow control.
+ try {
+ return list(dir)
+ } catch (e: FileNotFoundException) {
+ return null
+ } catch (e: ErrnoException) {
+ if (e.errno == Errno.notdir) return null
+ throw e
+ }
+ }
+
+ private fun list(dir: Path, fd: fd): List<Path> {
+ withScopedMemoryAllocator { allocator ->
+ // In theory, fd_readdir uses a 'cookie' field to page through results. In practice the
+ // NodeJS implementation doesn't honor the cookie and directories with large file names
+ // don't progress. Instead, just grow the buffer until the entire directory fits.
+ var bufSize = 2048
+ var bufPointer = allocator.allocate(bufSize)
+ val returnPointer = allocator.allocate(4) // `size` is u32, 4 bytes.
+ var pageSize: Int
+ while (true) {
+ val errno = fd_readdir(
+ fd = fd,
+ buf = bufPointer.address.toInt(),
+ buf_len = bufSize,
+ cookie = 0L, // Don't bother with dircookie, it doesn't work for large file names.
+ returnPointer = returnPointer.address.toInt(),
+ )
+
+ if (errno != 0) throw ErrnoException(errno.toShort())
+ pageSize = returnPointer.loadInt()
+
+ if (pageSize < bufSize) break
+
+ bufSize *= 4
+ bufPointer = allocator.allocate(bufSize)
+ }
+
+ // Parse dirent records from the buffer.
+ var pos = bufPointer
+ val limit = bufPointer + pageSize
+ val result = mutableListOf<Path>()
+ while (pos.address < limit.address) {
+ pos += 8 // Skip dircookie.
+ pos += 8 // Skip inode.
+ val d_namelen: dirnamelen = pos.loadInt()
+ pos += 4 // Consume d_namelen.
+ pos += 4 // Skip d_type.
+
+ val name = pos.readString(d_namelen)
+ pos += d_namelen
+
+ result += dir / name
+ }
+
+ result.sort()
+ return result
+ }
+ }
+
+ override fun openReadOnly(file: Path): FileHandle {
+ val rightsBase = right_fd_filestat_get or
+ right_fd_read or
+ right_fd_seek or
+ right_fd_sync
+ val fd = pathOpen(
+ path = file,
+ oflags = 0,
+ rightsBase = rightsBase,
+ )
+ return WasiFileHandle(fd, readWrite = false)
+ }
+
+ override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
+ val oflags = when {
+ mustCreate && mustExist -> {
+ throw IllegalArgumentException("Cannot require mustCreate and mustExist at the same time.")
+ }
+ mustCreate -> oflag_creat or oflag_excl
+ mustExist -> 0
+ else -> oflag_creat
+ }
+ val rightsBase = right_fd_filestat_get or
+ right_fd_filestat_set_size or
+ right_fd_read or
+ right_fd_seek or
+ right_fd_sync or
+ right_fd_write
+ val fd = pathOpen(
+ path = file,
+ oflags = oflags,
+ rightsBase = rightsBase,
+ )
+ return WasiFileHandle(fd, readWrite = true)
+ }
+
+ override fun source(file: Path): Source {
+ return FileSource(
+ fd = pathOpen(
+ path = file,
+ oflags = 0,
+ rightsBase = right_fd_read,
+ ),
+ )
+ }
+
+ override fun sink(file: Path, mustCreate: Boolean): Sink {
+ val oflags = when {
+ mustCreate -> oflag_creat or oflag_excl or oflag_trunc
+ else -> oflag_creat or oflag_trunc
+ }
+
+ return FileSink(
+ fd = pathOpen(
+ path = file,
+ oflags = oflags,
+ rightsBase = right_fd_write or right_fd_sync,
+ ),
+ )
+ }
+
+ override fun appendingSink(file: Path, mustExist: Boolean): Sink {
+ val oflags = when {
+ mustExist -> 0
+ else -> oflag_creat
+ }
+
+ return FileSink(
+ fd = pathOpen(
+ path = file,
+ oflags = oflags,
+ rightsBase = right_fd_write,
+ fdflags = fdflags_append,
+ ),
+ )
+ }
+
+ override fun createDirectory(dir: Path, mustCreate: Boolean) {
+ withScopedMemoryAllocator { allocator ->
+ val (pathAddress, pathSize) = allocator.write(dir.toString())
+
+ val errno = path_create_directory(
+ fd = preopenForPath(dir)?.fd ?: throw FileNotFoundException("no preopen: $dir"),
+ path = pathAddress.address.toInt(),
+ pathSize = pathSize,
+ )
+ if (errno == Errno.exist.ordinal) {
+ if (mustCreate) throw IOException("already exists: $dir")
+ return
+ }
+
+ if (errno != 0) throw ErrnoException(errno.toShort())
+ }
+ }
+
+ override fun atomicMove(source: Path, target: Path) {
+ withScopedMemoryAllocator { allocator ->
+ val (sourcePathAddress, sourcePathSize) = allocator.write(source.toString())
+ val (targetPathAddress, targetPathSize) = allocator.write(target.toString())
+
+ val errno = path_rename(
+ fd = preopenForPath(source)?.fd ?: throw FileNotFoundException("no preopen: $source"),
+ old_path = sourcePathAddress.address.toInt(),
+ old_pathSize = sourcePathSize,
+ new_fd = preopenForPath(target)?.fd ?: throw FileNotFoundException("no preopen: $target"),
+ new_path = targetPathAddress.address.toInt(),
+ new_pathSize = targetPathSize,
+ )
+ if (errno == Errno.noent.ordinal) {
+ throw FileNotFoundException("no such file: $source")
+ }
+
+ if (errno != 0) throw ErrnoException(errno.toShort())
+ }
+ }
+
+ override fun delete(path: Path, mustExist: Boolean) {
+ withScopedMemoryAllocator { allocator ->
+ val (pathAddress, pathSize) = allocator.write(path.toString())
+ val preopenFd = preopenForPath(path) ?: throw FileNotFoundException("no preopen: $path")
+
+ var errno = path_unlink_file(
+ fd = preopenFd.fd,
+ path = pathAddress.address.toInt(),
+ pathSize = pathSize,
+ )
+ // If unlink failed, try remove_directory.
+ when (errno) {
+ Errno.noent.ordinal -> {
+ if (mustExist) throw FileNotFoundException("no such file: $path")
+ return // Nothing to delete.
+ }
+
+ Errno.perm.ordinal,
+ Errno.isdir.ordinal,
+ -> {
+ errno = path_remove_directory(
+ fd = preopenFd.fd,
+ path = pathAddress.address.toInt(),
+ pathSize = pathSize,
+ )
+ }
+ }
+ if (errno != 0) throw ErrnoException(errno.toShort())
+ }
+ }
+
+ override fun createSymlink(source: Path, target: Path) {
+ withScopedMemoryAllocator { allocator ->
+ val sourcePreopen = preopenForPath(source)
+ ?: throw FileNotFoundException("no preopen: $source")
+
+ // Always create symlinks relative to their source. Absolute symlinks are trouble because the
+ // absolute paths used by WASI are different from the absolute paths on the host file system.
+ val sourceParent = source.parent
+ ?: throw IOException("unexpected symlink source: $source")
+ val targetRelative = when {
+ target.isRelative -> target
+ else -> target.relativeTo(sourceParent)
+ }
+
+ val (sourcePathAddress, sourcePathSize) = allocator.write(source.toString())
+ val (targetPathAddress, targetPathSize) = allocator.write(targetRelative.toString())
+
+ val errno = path_symlink(
+ old_path = targetPathAddress.address.toInt(),
+ old_pathSize = targetPathSize,
+ fd = sourcePreopen.fd,
+ new_path = sourcePathAddress.address.toInt(),
+ new_pathSize = sourcePathSize,
+ )
+ if (errno != 0) throw ErrnoException(errno.toShort())
+ }
+ }
+
+ private fun pathOpen(
+ path: Path,
+ oflags: oflags,
+ rightsBase: rights,
+ fdflags: fdflags = 0,
+ ): fd {
+ withScopedMemoryAllocator { allocator ->
+ val preopenFd = preopenForPath(path) ?: throw FileNotFoundException("no preopen: $path")
+ val (pathAddress, pathSize) = allocator.write(path.toString())
+
+ val returnPointer: Pointer = allocator.allocate(4) // fd is u32.
+ val errno = path_open(
+ fd = preopenFd.fd,
+ dirflags = 0,
+ path = pathAddress.address.toInt(),
+ pathSize = pathSize,
+ oflags = oflags,
+ fs_rights_base = rightsBase,
+ fs_rights_inheriting = 0,
+ fdflags = fdflags,
+ returnPointer = returnPointer.address.toInt(),
+ )
+ if (errno == Errno.noent.ordinal) {
+ throw FileNotFoundException("no such file: $path")
+ }
+ if (errno != 0) throw ErrnoException(errno.toShort())
+ return returnPointer.loadInt()
+ }
+ }
+
+ /**
+ * Returns the preopen whose path is either an ancestor of [path], or whose path [path] is an
+ * ancestor of.
+ *
+ * If [path] is an ancestor of our preopen, then operating on the path will ultimately fail with a
+ * `notcapable` errno.
+ */
+ private fun preopenForPath(path: Path): Preopen? {
+ if (path.isRelative) return relativePathPreopen
+
+ val pathSegmentsBytes = path.segmentsBytes
+
+ return preopens.firstOrNull { preopen ->
+ val commonSize = minOf(pathSegmentsBytes.size, preopen.segmentsBytes.size)
+ preopen.segmentsBytes.subList(0, commonSize) == pathSegmentsBytes.subList(0, commonSize)
+ }
+ }
+
+ override fun toString() = "okio.WasiFileSystem"
+
+ private class Preopen(
+ val path: Path,
+ val segmentsBytes: List<ByteString>,
+ val fd: fd,
+ )
+}
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/ErrnoException.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/ErrnoException.kt
new file mode 100644
index 00000000..fb3aa2ac
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/ErrnoException.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.internal
+
+import okio.IOException
+import okio.internal.preview1.Errno
+import okio.internal.preview1.errno
+
+class ErrnoException(
+ val errno: Errno,
+) : IOException(errno.name) {
+ constructor(errno: errno) : this(Errno.entries[errno.toInt()])
+}
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/Wasi.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/Wasi.kt
new file mode 100644
index 00000000..7775f616
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/Wasi.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.internal
+
+import kotlin.wasm.unsafe.MemoryAllocator
+import kotlin.wasm.unsafe.Pointer
+import okio.internal.preview1.fd
+import okio.internal.preview1.fd_close
+import okio.internal.preview1.size
+
+internal fun fdClose(fd: fd) {
+ val errno = fd_close(fd = fd)
+ if (errno != 0) throw ErrnoException(errno.toShort())
+}
+
+internal fun Pointer.readString(byteCount: Int): String {
+ if (byteCount == 0) return ""
+
+ // Drop the last byte if it's 0. At least in NodeJS' implementation, strings are returned with
+ // a trailing NUL byte.
+ val lastByte = (this + byteCount - 1).loadByte()
+ val byteArray = when {
+ lastByte.toInt() == 0 -> readByteArray(byteCount - 1)
+ else -> readByteArray(byteCount)
+ }
+
+ return byteArray.decodeToString()
+}
+
+private fun Pointer.readByteArray(byteCount: Int): ByteArray {
+ val result = ByteArray(byteCount)
+ read(result, 0, byteCount)
+ return result
+}
+
+internal fun Pointer.read(
+ sink: ByteArray,
+ offset: Int,
+ count: Int,
+): ByteArray {
+ for (i in 0 until count) {
+ sink[offset + i] = (this + i).loadByte()
+ }
+ return sink
+}
+
+internal fun MemoryAllocator.write(
+ string: String,
+): Pair<Pointer, size> {
+ val bytes = string.encodeToByteArray()
+
+ // Append a trailing NUL byte. This shouldn't be necessary, but it reduces crashes in practice,
+ // at least on NodeJS 20.0. https://github.com/WebAssembly/WASI/issues/492
+ val result = allocate(bytes.size + 1)
+ var pos = result
+ for (element in bytes) {
+ pos.storeByte(element)
+ pos += 1
+ }
+ pos.storeByte(0)
+ return result to bytes.size
+}
+
+internal fun MemoryAllocator.write(
+ byteArray: ByteArray,
+ offset: Int = 0,
+ count: Int = byteArray.size - offset,
+): Pointer {
+ val result = allocate(count)
+ var pos = result
+ for (b in offset until (offset + count)) {
+ pos.storeByte(byteArray[b])
+ pos += 1
+ }
+ return result
+}
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Errno.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Errno.kt
new file mode 100644
index 00000000..bc5a4555
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Errno.kt
@@ -0,0 +1,238 @@
+// Copyright 2019-2023 the Contributors to the WASI Specification
+// This file is adapted from the WASI preview1 spec here:
+// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
+package okio.internal.preview1
+
+@Suppress("ktlint:enum-entry-name-case")
+enum class Errno {
+ /** `success`: No error occurred. System call completed successfully. */
+ success,
+
+ /** `2big`: Argument list too long. */
+ toobig,
+
+ /** `acces`: Permission denied. */
+ acces,
+
+ /** `addrinuse`: Address in use. */
+ addrinuse,
+
+ /** `addrnotavail`: Address not available. */
+ addrnotavail,
+
+ /** `afnosupport`: Address family not supported. */
+ afnosupport,
+
+ /** `again`: Resource unavailable, or operation would block. */
+ again,
+
+ /** `already`: Connection already in progress. */
+ already,
+
+ /** `badf`: Bad file descriptor. */
+ badf,
+
+ /** `badmsg`: Bad message. */
+ badmsg,
+
+ /** `busy`: Device or resource busy. */
+ busy,
+
+ /** `canceled`: Operation canceled. */
+ canceled,
+
+ /** `child`: No child processes. */
+ child,
+
+ /** `connaborted`: Connection aborted. */
+ connaborted,
+
+ /** `connrefused`: Connection refused. */
+ connrefused,
+
+ /** `connreset`: Connection reset. */
+ connreset,
+
+ /** `deadlk`: Resource deadlock would occur. */
+ deadlk,
+
+ /** `destaddrreq`: Destination address required. */
+ destaddrreq,
+
+ /** `dom`: Mathematics argument out of domain of function. */
+ dom,
+
+ /** `dquot`: Reserved. */
+ dquot,
+
+ /** `exist`: File exists. */
+ exist,
+
+ /** `fault`: Bad address. */
+ fault,
+
+ /** `fbig`: File too large. */
+ fbig,
+
+ /** `hostunreach`: Host is unreachable. */
+ hostunreach,
+
+ /** `idrm`: Identifier removed. */
+ idrm,
+
+ /** `ilseq`: Illegal byte sequence. */
+ ilseq,
+
+ /** `inprogress`: Operation in progress. */
+ inprogress,
+
+ /** `intr`: Interrupted function. */
+ intr,
+
+ /** `inval`: Invalid argument. */
+ inval,
+
+ /** `io`: I/O error. */
+ io,
+
+ /** `isconn`: Socket is connected. */
+ isconn,
+
+ /** `isdir`: Is a directory. */
+ isdir,
+
+ /** `loop`: Too many levels of symbolic links. */
+ loop,
+
+ /** `mfile`: File descriptor value too large. */
+ mfile,
+
+ /** `mlink`: Too many links. */
+ mlink,
+
+ /** `msgsize`: Message too large. */
+ msgsize,
+
+ /** `multihop`: Reserved. */
+ multihop,
+
+ /** `nametoolong`: Filename too long. */
+ nametoolong,
+
+ /** `netdown`: Network is down. */
+ netdown,
+
+ /** `netreset`: Connection aborted by network. */
+ netreset,
+
+ /** `netunreach`: Network unreachable. */
+ netunreach,
+
+ /** `nfile`: Too many files open in system. */
+ nfile,
+
+ /** `nobufs`: No buffer space available. */
+ nobufs,
+
+ /** `nodev`: No such device. */
+ nodev,
+
+ /** `noent`: No such file or directory. */
+ noent,
+
+ /** `noexec`: Executable file format error. */
+ noexec,
+
+ /** `nolck`: No locks available. */
+ nolck,
+
+ /** `nolink`: Reserved. */
+ nolink,
+
+ /** `nomem`: Not enough space. */
+ nomem,
+
+ /** `nomsg`: No message of the desired type. */
+ nomsg,
+
+ /** `noprotoopt`: Protocol not available. */
+ noprotoopt,
+
+ /** `nospc`: No space left on device. */
+ nospc,
+
+ /** `nosys`: Function not supported. */
+ nosys,
+
+ /** `notconn`: The socket is not connected. */
+ notconn,
+
+ /** `notdir`: Not a directory or a symbolic link to a directory. */
+ notdir,
+
+ /** `notempty`: Directory not empty. */
+ notempty,
+
+ /** `notrecoverable`: State not recoverable. */
+ notrecoverable,
+
+ /** `notsock`: Not a socket. */
+ notsock,
+
+ /** `notsup`: Not supported, or operation not supported on socket. */
+ notsup,
+
+ /** `notty`: Inappropriate I/O control operation. */
+ notty,
+
+ /** `nxio`: No such device or address. */
+ nxio,
+
+ /** `overflow`: Value too large to be stored in data type. */
+ overflow,
+
+ /** `ownerdead`: Previous owner died. */
+ ownerdead,
+
+ /** `perm`: Operation not permitted. */
+ perm,
+
+ /** `pipe`: Broken pipe. */
+ pipe,
+
+ /** `proto`: Protocol error. */
+ proto,
+
+ /** `protonosupport`: Protocol not supported. */
+ protonosupport,
+
+ /** `prototype`: Protocol wrong type for socket. */
+ prototype_,
+
+ /** `range`: Result too large. */
+ range,
+
+ /** `rofs`: Read-only file system. */
+ rofs,
+
+ /** `spipe`: Invalid seek. */
+ spipe,
+
+ /** `srch`: No such process. */
+ srch,
+
+ /** `stale`: Reserved. */
+ stale,
+
+ /** `timedout`: Connection timed out. */
+ timedout,
+
+ /** `txtbsy`: Text file busy. */
+ txtbsy,
+
+ /** `xdev`: Cross-device link. */
+ xdev,
+
+ /** `notcapable`: Extension: Capabilities insufficient. */
+ notcapable,
+}
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Fdflags.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Fdflags.kt
new file mode 100644
index 00000000..5c3bf217
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Fdflags.kt
@@ -0,0 +1,26 @@
+// Copyright 2019-2023 the Contributors to the WASI Specification
+// This file is adapted from the WASI preview1 spec here:
+// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
+package okio.internal.preview1
+
+/**
+ * `fdflags: Record`.
+ *
+ * File descriptor flags.
+ */
+typealias fdflags = Short
+
+/** Data written to the file is always appended to the file's end. */
+val fdflags_append: Short = (1 shl 0).toShort()
+
+/** Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. */
+val fdflags_dsync: Short = (1 shl 1).toShort()
+
+/** Non-blocking mode. */
+val fdflags_nonblock: Short = (1 shl 2).toShort()
+
+/** Synchronized read I/O operations. */
+val fdflags_rsync: Short = (1 shl 3).toShort()
+
+/** Write according to synchronized I/O file integrity completion. In addition to synchronizing the data stored in the file, the implementation may also synchronously update the file's metadata. */
+val fdflags_sync: Short = (1 shl 4).toShort()
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Filetype.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Filetype.kt
new file mode 100644
index 00000000..f4788b36
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Filetype.kt
@@ -0,0 +1,35 @@
+// Copyright 2019-2023 the Contributors to the WASI Specification
+// This file is adapted from the WASI preview1 spec here:
+// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
+package okio.internal.preview1
+
+/**
+ * `Variant`.
+ *
+ * The type of a file descriptor or file.
+ */
+typealias filetype = Byte
+
+/** The type of the file descriptor or file is unknown or is different from any of the other types specified. */
+val filetype_unknown: filetype = 0
+
+/** The file descriptor or file refers to a block device inode. */
+val filetype_block_device: filetype = 1
+
+/** The file descriptor or file refers to a character device inode. */
+val filetype_character_device: filetype = 2
+
+/** The file descriptor or file refers to a directory inode. */
+val filetype_directory: filetype = 3
+
+/** The file descriptor or file refers to a regular file inode. */
+val filetype_regular_file: filetype = 4
+
+/** The file descriptor or file refers to a datagram socket. */
+val filetype_socket_dgram: filetype = 5
+
+/** The file descriptor or file refers to a byte-stream socket. */
+val filetype_socket_stream: filetype = 6
+
+/** The file refers to a symbolic link inode. */
+val filetype_symbolic_link: filetype = 7
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/LookupFlags.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/LookupFlags.kt
new file mode 100644
index 00000000..5608f445
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/LookupFlags.kt
@@ -0,0 +1,17 @@
+// Copyright 2019-2023 the Contributors to the WASI Specification
+// This file is adapted from the WASI preview1 spec here:
+// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
+package okio.internal.preview1
+
+/**
+ * `lookupflags: Record`.
+ *
+ * Flags determining the method of how paths are resolved.
+ *
+ * Bit0:
+ * symlink_follow:
+ */
+typealias lookupflags = Int
+
+/** As long as the resolved path corresponds to a symbolic link, it is expanded. */
+val lookupflags_symlink_follow = 1 shl 0
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/OFlags.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/OFlags.kt
new file mode 100644
index 00000000..3efeb764
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/OFlags.kt
@@ -0,0 +1,23 @@
+// Copyright 2019-2023 the Contributors to the WASI Specification
+// This file is adapted from the WASI preview1 spec here:
+// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
+package okio.internal.preview1
+
+/**
+ * `oflags: `Record`.
+ *
+ * Open flags used by path_open.
+ */
+typealias oflags = Int
+
+/** Create file if it does not exist. */
+val oflag_creat = 1 shl 0
+
+/** Fail if not a directory. */
+val oflag_directory = 1 shl 1
+
+/** Fail if file already exists. */
+val oflag_excl = 1 shl 2
+
+/** Truncate file to size 0. */
+val oflag_trunc = 1 shl 3
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Preview1.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Preview1.kt
new file mode 100644
index 00000000..4441d5ec
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Preview1.kt
@@ -0,0 +1,336 @@
+// Copyright 2019-2023 the Contributors to the WASI Specification
+// This file is adapted from the WASI preview1 spec here:
+// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
+package okio.internal.preview1
+
+import kotlin.wasm.WasmImport
+
+/** `u32`. */
+typealias size = Int
+
+/**
+ * `Handle`.
+ *
+ * A file descriptor handle.
+ */
+typealias fd = Int
+
+/**
+ * `u64`.
+ *
+ * A reference to the offset of a directory entry.
+ *
+ * The value 0 signifies the start of the directory.
+ */
+typealias dircookie = Long
+
+/**
+ * `Variant`.
+ *
+ * Error codes returned by functions. Not all of these error codes are returned by the functions
+ * provided by this API; some are used in higher-level library layers, and others are provided
+ * merely for alignment with POSIX.
+ */
+typealias errno = Short
+
+/**
+ * `u64`.
+ *
+ * File serial number that is unique within its file system.
+ */
+typealias inode = Long
+
+/**
+ * `u32`.
+ *
+ * The type for the [`dirent::d_namlen`](#dirent.d_namlen) field of [`dirent`](#dirent) struct.
+ */
+typealias dirnamelen = Int
+
+/**
+ * `Pointer<u8>`.
+ */
+typealias PointerU8 = Int
+
+/**
+ * path_create_directory(fd: fd, path: string) -> Result<(), errno>
+ *
+ * Create a directory.
+ * Note: This is similar to `mkdirat` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "path_create_directory")
+internal external fun path_create_directory(
+ fd: fd,
+ path: PointerU8,
+ pathSize: size,
+): Int // should be Short??
+
+/**
+ * path_filestat_get(fd: fd, flags: lookupflags, path: string) -> Result<filestat, errno>
+ *
+ * Return the attributes of a file or directory.
+ * Note: This is similar to `stat` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "path_filestat_get")
+internal external fun path_filestat_get(
+ fd: fd,
+ flags: lookupflags,
+ path: PointerU8,
+ pathSize: size,
+ returnPointer: PointerU8,
+): Int // should be Short??
+
+/**
+ * path_open(fd: fd, dirflags: lookupflags, path: string, oflags: oflags, fs_rights_base: rights, fs_rights_inheriting: rights, fdflags: fdflags) -> Result<fd, errno>
+ *
+ * Open a file or directory.
+ * The returned file descriptor is not guaranteed to be the lowest-numbered
+ * file descriptor not currently open; it is randomized to prevent
+ * applications from depending on making assumptions about indexes, since this
+ * is error-prone in multi-threaded contexts. The returned file descriptor is
+ * guaranteed to be less than 2**31.
+ * Note: This is similar to `openat` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "path_open")
+internal external fun path_open(
+ fd: fd,
+ dirflags: lookupflags,
+ path: PointerU8,
+ pathSize: size,
+ oflags: oflags,
+ fs_rights_base: rights,
+ fs_rights_inheriting: rights,
+ fdflags: fdflags,
+ returnPointer: PointerU8,
+): Int // should be Short??
+
+/**
+ * path_readlink(fd: fd, path: string, buf: Pointer<u8>, buf_len: size) -> Result<size, errno>
+ *
+ * Read the contents of a symbolic link.
+ * Note: This is similar to `readlinkat` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "path_readlink")
+internal external fun path_readlink(
+ fd: fd,
+ path: PointerU8,
+ pathSize: size,
+ buf: PointerU8,
+ buf_len: size,
+ returnPointer: PointerU8,
+): Int // should be Short??
+
+/**
+ * path_remove_directory(fd: fd, path: string) -> Result<(), errno>
+ *
+ * Remove a directory.
+ * Return [`errno::notempty`](#errno.notempty) if the directory is not empty.
+ * Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "path_remove_directory")
+internal external fun path_remove_directory(
+ fd: fd,
+ path: PointerU8,
+ pathSize: size,
+): Int // should be Short??
+
+/**
+ * path_rename(fd: fd, old_path: string, new_fd: fd, new_path: string) -> Result<(), errno>
+ *
+ * Rename a file or directory.
+ * Note: This is similar to `renameat` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "path_rename")
+internal external fun path_rename(
+ fd: fd,
+ old_path: PointerU8,
+ old_pathSize: size,
+ new_fd: fd,
+ new_path: PointerU8,
+ new_pathSize: size,
+): Int // should be Short??
+
+/**
+ * path_symlink(old_path: string, fd: fd, new_path: string) -> Result<(), errno>
+ *
+ * Create a symbolic link.
+ * Note: This is similar to `symlinkat` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "path_symlink")
+internal external fun path_symlink(
+ old_path: PointerU8,
+ old_pathSize: size,
+ fd: fd,
+ new_path: PointerU8,
+ new_pathSize: size,
+): Int // should be Short??
+
+/**
+ * path_unlink_file(fd: fd, path: string) -> Result<(), errno>
+ *
+ * Unlink a file.
+ * Return [`errno::isdir`](#errno.isdir) if the path refers to a directory.
+ * Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "path_unlink_file")
+internal external fun path_unlink_file(
+ fd: fd,
+ path: PointerU8,
+ pathSize: size,
+): Int // should be Short??
+
+/**
+ * fd_close(fd: fd) -> Result<(), errno>
+ *
+ * Close a file descriptor.
+ * Note: This is similar to `close` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "fd_close")
+internal external fun fd_close(
+ fd: fd,
+): Int // should be Short??
+
+/**
+ * fd_filestat_get(fd: fd) -> Result<filestat, errno>
+ *
+ * Return the attributes of an open file.
+ */
+@WasmImport("wasi_snapshot_preview1", "fd_filestat_get")
+internal external fun fd_filestat_get(
+ fd: fd,
+ returnPointer: PointerU8,
+): Int // should be Short??
+
+/**
+ * fd_pread(fd: fd, iovs: iovec_array, offset: filesize) -> Result<size, errno>
+ *
+ * Read from a file descriptor.
+ * Note: This is similar to `readv` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "fd_pread")
+internal external fun fd_pread(
+ fd: fd,
+ iovs: PointerU8,
+ iovsSize: size,
+ offset: Long,
+ returnPointer: PointerU8,
+): Int // should be Short??
+
+/**
+ * fd_prestat_dir_name(fd: fd, path: Pointer<u8>, path_len: size) -> Result<(), errno>
+ *
+ * Return a description of the given preopened file descriptor.
+ */
+@WasmImport("wasi_snapshot_preview1", "fd_prestat_dir_name")
+internal external fun fd_prestat_dir_name(
+ fd: fd,
+ path: PointerU8,
+ pathSize: size,
+): Int // should be Short??
+
+/**
+ * fd_prestat_get(fd: fd) -> Result<prestat, errno>
+ *
+ * Return a description of the given preopened file descriptor.
+ */
+@WasmImport("wasi_snapshot_preview1", "fd_prestat_get")
+internal external fun fd_prestat_get(
+ fd: fd,
+ returnPointer: PointerU8,
+): Int // should be Short??
+
+/**
+ * fd_pwrite(fd: fd, iovs: ciovec_array, offset: filesize) -> Result<size, errno>`
+ *
+ * Write to a file descriptor, without using and updating the file descriptor's offset.
+ * Note: This is similar to `pwritev` in Linux (and other Unix-es).
+ *
+ * Like Linux (and other Unix-es), any calls of `pwrite` (and other
+ * functions to read or write) for a regular file by other threads in the
+ * WASI process should not be interleaved while `pwrite` is executed.
+ */
+@WasmImport("wasi_snapshot_preview1", "fd_pwrite")
+internal external fun fd_pwrite(
+ fd: fd,
+ iovs: PointerU8,
+ iovsSize: size,
+ offset: Long,
+ returnPointer: PointerU8,
+): Int // should be Short??
+
+/**
+ * fd_read(fd: fd, iovs: iovec_array) -> Result<size, errno>
+ *
+ * Read from a file descriptor.
+ * Note: This is similar to `readv` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "fd_read")
+internal external fun fd_read(
+ fd: fd,
+ iovs: PointerU8,
+ iovsSize: size,
+ returnPointer: PointerU8,
+): Int // should be Short??
+
+/**
+ * fd_readdir(fd: fd, buf: Pointer<u8>, buf_len: size, cookie: dircookie) -> Result<size, errno>
+ *
+ * Read directory entries from a directory.
+ * When successful, the contents of the output buffer consist of a sequence of
+ * directory entries. Each directory entry consists of a [`dirent`](#dirent) object,
+ * followed by [`dirent::d_namlen`](#dirent.d_namlen) bytes holding the name of the directory
+ * entry.
+ * This function fills the output buffer as much as possible, potentially
+ * truncating the last directory entry. This allows the caller to grow its
+ * read buffer size in case it's too small to fit a single large directory
+ * entry, or skip the oversized directory entry.
+ */
+@WasmImport("wasi_snapshot_preview1", "fd_readdir")
+internal external fun fd_readdir(
+ fd: fd,
+ buf: PointerU8,
+ buf_len: size,
+ cookie: dircookie,
+ returnPointer: PointerU8,
+): Int // should be Short??
+
+/**
+ * fd_filestat_set_size(fd: fd, size: filesize) -> Result<(), errno>
+ *
+ * Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros.
+ * Note: This is similar to `ftruncate` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "fd_filestat_set_size")
+internal external fun fd_filestat_set_size(
+ fd: fd,
+ size: Long,
+): Int // should be Short??
+
+/**
+ * fd_sync(fd: fd) -> Result<(), errno>
+ *
+ * Synchronize the data and metadata of a file to disk.
+ * Note: This is similar to `fsync` in POSIX.
+ */
+@WasmImport("wasi_snapshot_preview1", "fd_sync")
+internal external fun fd_sync(
+ fd: fd,
+): Int // should be Short??
+
+/**
+ * fd_write(fd: fd, iovs: ciovec_array) -> Result<size, errno>
+ *
+ * Write to a file descriptor.
+ * Note: This is similar to `writev` in POSIX.
+ *
+ * Like POSIX, any calls of `write` (and other functions to read or write)
+ * for a regular file by other threads in the WASI process should not be
+ * interleaved while `write` is executed.
+ */
+@WasmImport("wasi_snapshot_preview1", "fd_write")
+internal external fun fd_write(
+ fd: fd,
+ iovs: PointerU8,
+ iovsSize: size,
+ returnPointer: PointerU8,
+): Int // should be Short??
diff --git a/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Rights.kt b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Rights.kt
new file mode 100644
index 00000000..b72f26f4
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiMain/kotlin/okio/internal/preview1/Rights.kt
@@ -0,0 +1,130 @@
+// Copyright 2019-2023 the Contributors to the WASI Specification
+// This file is adapted from the WASI preview1 spec here:
+// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
+package okio.internal.preview1
+
+/** `rights: Record`. */
+typealias rights = Long
+
+/**
+ * The right to invoke [`fd_datasync`](#fd_datasync)0
+ * If [`path_open`](#path_open) is set, includes the right to invoke
+ * [`path_open`](#path_open) with [`fdflags::dsync`](#fdflags.dsync).
+ */
+val right_fd_datasync = 1L shl 0
+
+/**
+ * The right to invoke [`fd_read`](#fd_read) and [`sock_recv`](#sock_recv).
+ * If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke [`fd_pread`](#fd_pread).
+ */
+val right_fd_read = 1L shl 1
+
+/** The right to invoke [`fd_seek`](#fd_seek). This flag implies [`rights::fd_tell`](#rights.fd_tell). */
+val right_fd_seek = 1L shl 2
+
+/** The right to invoke [`fd_fdstat_set_flags`](#fd_fdstat_set_flags). */
+val right_fd_fdstat_set_flags = 1L shl 3
+
+/**
+ * The right to invoke [`fd_sync`](#fd_sync).
+ * If [`path_open`](#path_open) is set, includes the right to invoke
+ * [`path_open`](#path_open) with [`fdflags::rsync`](#fdflags.rsync) and [`fdflags::dsync`](#fdflags.dsync).
+ */
+val right_fd_sync = 1L shl 4
+
+/**
+ * The right to invoke [`fd_seek`](#fd_seek) in such a way that the file offset
+ * remains unaltered (i.e., [`whence::cur`](#whence.cur) with offset zero), or to
+ * invoke [`fd_tell`](#fd_tell).
+ */
+val right_fd_tell = 1L shl 5
+
+/**
+ * The right to invoke [`fd_write`](#fd_write) and [`sock_send`](#sock_send).
+ * If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke [`fd_pwrite`](#fd_pwrite).
+ */
+val right_fd_write = 1L shl 6
+
+/** The right to invoke [`fd_advise`](#fd_advise). */
+val right_fd_advise = 1L shl 7
+
+/** The right to invoke [`fd_allocate`](#fd_allocate). */
+val right_fd_allocate = 1L shl 8
+
+/** The right to invoke [`path_create_directory`](#path_create_directory). */
+val right_path_create_directory = 1L shl 9
+
+/** If [`path_open`](#path_open) is set, the right to invoke [`path_open`](#path_open) with [`oflags::creat`](#oflags.creat). */
+val right_path_create_file = 1L shl 10
+
+/**
+ * The right to invoke [`path_link`](#path_link) with the file descriptor as the
+ * source directory.
+ */
+val right_path_link_source = 1L shl 11
+
+/** The right to invoke [`path_link`](#path_link) with the file descriptor as the target directory.
+ */
+val right_path_link_target = 1L shl 12
+
+/** The right to invoke [`path_open`](#path_open). */
+val right_path_open = 1L shl 13
+
+/** The right to invoke [`fd_readdir`](#fd_readdir). */
+val right_fd_readdir = 1L shl 14
+
+/** The right to invoke [`path_readlink`](#path_readlink). */
+val right_path_readlink = 1L shl 15
+
+/** The right to invoke [`path_rename`](#path_rename) with the file descriptor as the source directory. */
+val right_path_rename_source = 1L shl 16
+
+/** The right to invoke [`path_rename`](#path_rename) with the file descriptor as the target directory. */
+val right_path_rename_target = 1L shl 17
+
+/** The right to invoke [`path_filestat_get`](#path_filestat_get). */
+val right_path_filestat_get = 1L shl 18
+
+/**
+ * The right to change a file's size.
+ * If [`path_open`](#path_open) is set, includes the right to invoke [`path_open`](#path_open) with [`oflags::trunc`](#oflags.trunc).
+ * Note: there is no function named `path_filestat_set_size`. This follows POSIX design,
+ * which only has `ftruncate` and does not provide `ftruncateat`.
+ * While such function would be desirable from the API design perspective, there are virtually
+ * no use cases for it since no code written for POSIX systems would use it.
+ * Moreover, implementing it would require multiple syscalls, leading to inferior performance.
+ */
+val right_path_filestat_set_size = 1L shl 19
+
+/** The right to invoke [`path_filestat_set_times`](#path_filestat_set_times). */
+val right_path_filestat_set_times = 1L shl 20
+
+/** The right to invoke [`fd_filestat_get`](#fd_filestat_get). */
+val right_fd_filestat_get = 1L shl 21
+
+/** The right to invoke [`fd_filestat_set_size`](#fd_filestat_set_size). */
+val right_fd_filestat_set_size = 1L shl 22
+
+/** The right to invoke [`fd_filestat_set_times`](#fd_filestat_set_times). */
+val right_fd_filestat_set_times = 1L shl 23
+
+/** The right to invoke [`path_symlink`](#path_symlink). */
+val right_path_symlink = 1L shl 24
+
+/** The right to invoke [`path_remove_directory`](#path_remove_directory). */
+val right_path_remove_directory = 1L shl 25
+
+/** The right to invoke [`path_unlink_file`](#path_unlink_file). */
+val right_path_unlink_file = 1L shl 26
+
+/**
+ * If [`rights::fd_read`](#rights.fd_read) is set, includes the right to invoke [`poll_oneoff`](#poll_oneoff) to subscribe to [`eventtype::fd_read`](#eventtype.fd_read).
+ * If [`rights::fd_write`](#rights.fd_write) is set, includes the right to invoke [`poll_oneoff`](#poll_oneoff) to subscribe to [`eventtype::fd_write`](#eventtype.fd_write).
+ */
+val right_poll_fd_readwrite = 1L shl 27
+
+/** The right to invoke [`sock_shutdown`](#sock_shutdown). */
+val right_sock_shutdown = 1L shl 28
+
+/** The right to invoke [`sock_accept`](#sock_accept). */
+val right_sock_accept = 1L shl 29
diff --git a/okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiFileSystemPreopensTest.kt b/okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiFileSystemPreopensTest.kt
new file mode 100644
index 00000000..ed844fa5
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiFileSystemPreopensTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertNull
+import okio.Path.Companion.toPath
+
+/**
+ * Confirm the [WasiFileSystem] can operate on different preopened directories independently.
+ *
+ * This tracks the `preopens` attribute in `.mjs` script in `okio-wasifilesystem/build.gradle.kts`.
+ */
+class WasiFileSystemPreopensTest {
+ private val fileSystem = WasiFileSystem
+ private val testId = "${this::class.simpleName}-${randomToken(16)}"
+ private val baseA: Path = "/a".toPath() / testId
+ private val baseB: Path = "/b".toPath() / testId
+
+ @BeforeTest
+ fun setUp() {
+ fileSystem.createDirectory(baseA)
+ fileSystem.createDirectory(baseB)
+ }
+
+ @Test
+ fun operateOnPreopens() {
+ fileSystem.write(baseA / "a.txt") {
+ writeUtf8("hello world a")
+ }
+ fileSystem.write(baseB / "b.txt") {
+ writeUtf8("bello burld")
+ }
+ assertEquals(
+ "hello world a".length.toLong(),
+ fileSystem.metadata(baseA / "a.txt").size,
+ )
+ assertEquals(
+ "bello burld".length.toLong(),
+ fileSystem.metadata(baseB / "b.txt").size,
+ )
+ }
+
+ @Test
+ fun operateAcrossPreopens() {
+ fileSystem.write(baseA / "a.txt") {
+ writeUtf8("hello world")
+ }
+
+ fileSystem.atomicMove(baseA / "a.txt", baseB / "b.txt")
+
+ assertEquals(
+ "hello world",
+ fileSystem.read(baseB / "b.txt") {
+ readUtf8()
+ },
+ )
+ }
+
+ @Test
+ fun cannotOperateOutsideOfPreopens() {
+ val noPreopen = "/c".toPath() / testId
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.createDirectory(noPreopen)
+ }
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.sink(noPreopen)
+ }
+ assertNull(fileSystem.metadataOrNull(noPreopen))
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.metadata(noPreopen)
+ }
+ assertNull(fileSystem.listOrNull(noPreopen))
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.list(noPreopen)
+ }
+ }
+}
diff --git a/okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiFileSystemTest.kt b/okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiFileSystemTest.kt
new file mode 100644
index 00000000..b6846b45
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiFileSystemTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import okio.Path.Companion.toPath
+
+class WasiFileSystemTest : AbstractFileSystemTest(
+ clock = WasiClock,
+ fileSystem = WasiFileSystem,
+ windowsLimitations = Path.DIRECTORY_SEPARATOR == "\\",
+ allowClobberingEmptyDirectories = Path.DIRECTORY_SEPARATOR == "\\",
+ allowAtomicMoveFromFileToDirectory = false,
+ temporaryDirectory = "/tmp".toPath(),
+)
diff --git a/okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiTest.kt b/okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiTest.kt
new file mode 100644
index 00000000..6992cb2c
--- /dev/null
+++ b/okio-wasifilesystem/src/wasmWasiTest/kotlin/okio/WasiTest.kt
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
+import okio.ByteString.Companion.encodeUtf8
+import okio.Path.Companion.toPath
+
+class WasiTest {
+ private val fileSystem = WasiFileSystem
+ private val base: Path = "/tmp".toPath() / "${this::class.simpleName}-${randomToken(16)}"
+
+ @BeforeTest
+ fun setUp() {
+ fileSystem.createDirectory(base)
+ }
+
+ @Test
+ fun createDirectory() {
+ fileSystem.createDirectory(base / "child")
+ }
+
+ @Test
+ fun canonicalizeAbsolutePathNoSymlinks() {
+ val path = base / "regular_file.txt"
+ fileSystem.write(path) {
+ writeUtf8("hello")
+ }
+ assertEquals(
+ path,
+ fileSystem.canonicalize(path),
+ )
+ }
+
+ @Test
+ fun canonicalizeAbsolutePathWithSymlinksInFiles() {
+ val target = base / "target"
+ val source = base / "source"
+ fileSystem.write(target) {
+ writeUtf8("hello")
+ }
+ fileSystem.createSymlink(source, "target".toPath())
+ assertEquals(
+ target,
+ fileSystem.canonicalize(source),
+ )
+ }
+
+ @Test
+ fun canonicalizeAbsolutePathWithSymlinksInDirectories() {
+ val target = base / "target"
+ val source = base / "source"
+ fileSystem.createDirectory(target)
+ fileSystem.write(target / "file.txt") {
+ writeUtf8("hello")
+ }
+ fileSystem.createSymlink(source, "target".toPath())
+ assertEquals(
+ target / "file.txt",
+ fileSystem.canonicalize(source / "file.txt"),
+ )
+ }
+
+ @Test
+ fun canonicalizeAbsolutePathWithSymlinkCycle() {
+ fileSystem.createSymlink(base / "rock", "scissors".toPath())
+ fileSystem.createSymlink(base / "scissors", "paper".toPath())
+ fileSystem.createSymlink(base / "paper", "rock".toPath())
+ val e = assertFailsWith<IOException> {
+ fileSystem.canonicalize(base / "rock")
+ }
+ assertEquals("symlink cycle?", e.message)
+ }
+
+ @Test
+ fun writeAndReadEmptyFile() {
+ writeAndReadFile(ByteString.EMPTY, base / "empty.txt")
+ }
+
+ @Test
+ fun writeAndReadShortFile() {
+ writeAndReadFile("hello\n".encodeUtf8(), base / "hello.txt")
+ }
+
+ private fun writeAndReadFile(content: ByteString, fileName: Path) {
+ fileSystem.write(fileName) {
+ write(content)
+ }
+ assertEquals(
+ content,
+ fileSystem.read(fileName) {
+ readByteString()
+ },
+ )
+ }
+
+ @Test
+ fun writeAndReadLongFile() {
+ val fileName = base / "5m_bytes.txt"
+ fileSystem.write(fileName) {
+ for (i in 0L until 1_000_000L) {
+ writeByte(i.toInt())
+ writeByte(0)
+ writeByte(0)
+ writeByte(0)
+ writeByte(0)
+ }
+ }
+ fileSystem.read(fileName) {
+ for (i in 0L until 1_000_000L) {
+ assertEquals(i.toByte(), readByte())
+ assertEquals(0, readByte())
+ assertEquals(0, readByte())
+ assertEquals(0, readByte())
+ assertEquals(0, readByte())
+ }
+ assertTrue(exhausted())
+ }
+ }
+
+ @Test
+ fun appendToFile() {
+ val fileName = base / "append.txt"
+ fileSystem.write(fileName) {
+ writeUtf8("hello")
+ }
+ fileSystem.appendingSink(fileName).buffer().use {
+ it.writeUtf8(" world")
+ }
+ assertEquals(
+ "hello world",
+ fileSystem.read(fileName) {
+ readUtf8()
+ },
+ )
+ }
+
+ @Test
+ fun listDirectory() {
+ fileSystem.write(base / "a") {
+ writeUtf8("this file has a 1-byte file name")
+ }
+ fileSystem.write(base / "a.txt") {
+ writeUtf8("this file has a 5-byte file name")
+ }
+
+ assertEquals(
+ listOf(
+ base / "a",
+ base / "a.txt",
+ ),
+ fileSystem.list(base).sorted(),
+ )
+ }
+
+ @Test
+ fun deleteFile() {
+ fileSystem.write(base / "a") {
+ }
+ fileSystem.write(base / "b") {
+ }
+ fileSystem.write(base / "c") {
+ }
+ fileSystem.delete(base / "b")
+
+ assertEquals(
+ listOf(
+ base / "a",
+ base / "c",
+ ),
+ fileSystem.list(base).sorted(),
+ )
+ }
+
+ @Test
+ fun deleteDirectory() {
+ fileSystem.createDirectory(base / "a")
+ fileSystem.createDirectory(base / "b")
+ fileSystem.createDirectory(base / "c")
+ fileSystem.delete(base / "b")
+
+ assertEquals(
+ listOf(
+ base / "a",
+ base / "c",
+ ),
+ fileSystem.list(base).sorted(),
+ )
+ }
+
+ @Test
+ fun createSymlink() {
+ val targetPath = base / "target"
+ val sourcePath = base / "source"
+ fileSystem.write(targetPath) {
+ writeUtf8("this is the target file's contents")
+ }
+ fileSystem.createSymlink(sourcePath, "target".toPath())
+
+ assertEquals(
+ "this is the target file's contents",
+ fileSystem.read(sourcePath) {
+ readUtf8()
+ },
+ )
+ }
+
+ @Test
+ fun rename() {
+ val targetPath = base / "target"
+ val sourcePath = base / "source"
+ fileSystem.write(sourcePath) {
+ writeUtf8("this is the file's contents")
+ }
+ fileSystem.atomicMove(sourcePath, targetPath)
+
+ assertEquals(
+ "this is the file's contents",
+ fileSystem.read(targetPath) {
+ readUtf8()
+ },
+ )
+ assertEquals(
+ listOf(targetPath),
+ fileSystem.list(base),
+ )
+ }
+
+ @Test
+ fun fileMetadata() {
+ val regularFile = base / "regularFile"
+ val directory = base / "directory"
+ val symlink = base / "symlink"
+ fileSystem.write(regularFile) {
+ writeUtf8("this is a regular file")
+ }
+ fileSystem.createDirectory(directory)
+ fileSystem.createSymlink(symlink, "regularFile".toPath())
+
+ val regularFileMetadata = fileSystem.metadata(regularFile)
+ assertEquals(true, regularFileMetadata.isRegularFile)
+ assertEquals(false, regularFileMetadata.isDirectory)
+ assertEquals(null, regularFileMetadata.symlinkTarget)
+ assertEquals(22L, regularFileMetadata.size)
+
+ val directoryMetadata = fileSystem.metadata(directory)
+ assertEquals(false, directoryMetadata.isRegularFile)
+ assertEquals(true, directoryMetadata.isDirectory)
+ assertEquals(null, directoryMetadata.symlinkTarget)
+ // Note: no assertions about directory size.
+
+ val symlinkMetadata = fileSystem.metadata(symlink)
+ assertEquals(false, symlinkMetadata.isRegularFile)
+ assertEquals(false, symlinkMetadata.isDirectory)
+ assertEquals("regularFile".toPath(), symlinkMetadata.symlinkTarget)
+ assertEquals("regularFile".length.toLong(), symlinkMetadata.size)
+ }
+
+ @Test
+ fun absentMetadata() {
+ assertEquals(null, fileSystem.metadataOrNull(base / "no-such-file"))
+ assertFailsWith<FileNotFoundException> {
+ fileSystem.metadata(base / "no-such-file")
+ }
+ }
+
+ @Test
+ fun fileHandleRead() {
+ val path = base / "file.txt"
+ fileSystem.write(path) {
+ writeUtf8("this is a file about dogs and cats")
+ }
+ fileSystem.openReadOnly(path).use { handle ->
+ val sink = Buffer()
+ handle.read(21L, sink, 4L)
+
+ assertEquals(
+ "dogs",
+ sink.readUtf8(),
+ )
+ }
+ }
+
+ @Test
+ fun fileHandleWrite() {
+ val path = base / "file.txt"
+ fileSystem.write(path) {
+ writeUtf8("this is a file about cats and cats")
+ }
+ fileSystem.openReadWrite(path).use { handle ->
+ val source = Buffer().writeUtf8("dogs")
+ handle.write(21L, source, 4L)
+
+ assertEquals(
+ "this is a file about dogs and cats",
+ fileSystem.read(path) {
+ readUtf8()
+ },
+ )
+ }
+ }
+
+ @Test
+ fun fileHandleGetSize() {
+ val path = base / "file.txt"
+ fileSystem.write(path) {
+ writeUtf8("this is a file about dogs and cats")
+ }
+ fileSystem.openReadOnly(path).use { handle ->
+ assertEquals(
+ 34L,
+ handle.size(),
+ )
+ }
+ }
+
+ @Test
+ fun fileHandleResize() {
+ val path = base / "file.txt"
+ fileSystem.write(path) {
+ writeUtf8("this is a file about dogs and cats")
+ }
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.resize(25L)
+
+ assertEquals(
+ "this is a file about dogs",
+ fileSystem.read(path) {
+ readUtf8()
+ },
+ )
+ }
+ }
+
+ @Test
+ fun fileHandleFlush() {
+ val path = base / "file.txt"
+ fileSystem.openReadWrite(path).use { handle ->
+ handle.sink().buffer().use {
+ it.writeUtf8("hello")
+ }
+ handle.flush()
+
+ assertEquals(
+ "hello",
+ fileSystem.read(path) {
+ readUtf8()
+ },
+ )
+ }
+ }
+
+ @Test
+ fun fileSinkFlush() {
+ val path = base / "file.txt"
+ fileSystem.write(path) {
+ writeUtf8("hello")
+ flush()
+
+ assertEquals(
+ "hello",
+ fileSystem.read(path) {
+ readUtf8()
+ },
+ )
+ }
+ }
+}
diff --git a/okio/api/okio.api b/okio/api/okio.api
new file mode 100644
index 00000000..b85e1870
--- /dev/null
+++ b/okio/api/okio.api
@@ -0,0 +1,809 @@
+public final class okio/-DeflaterSinkExtensions {
+ public static final fun deflate (Lokio/Sink;Ljava/util/zip/Deflater;)Lokio/DeflaterSink;
+ public static synthetic fun deflate$default (Lokio/Sink;Ljava/util/zip/Deflater;ILjava/lang/Object;)Lokio/DeflaterSink;
+}
+
+public final class okio/-DeprecatedOkio {
+ public static final field INSTANCE Lokio/-DeprecatedOkio;
+ public final fun appendingSink (Ljava/io/File;)Lokio/Sink;
+ public final fun blackhole ()Lokio/Sink;
+ public final fun buffer (Lokio/Sink;)Lokio/BufferedSink;
+ public final fun buffer (Lokio/Source;)Lokio/BufferedSource;
+ public final fun sink (Ljava/io/File;)Lokio/Sink;
+ public final fun sink (Ljava/io/OutputStream;)Lokio/Sink;
+ public final fun sink (Ljava/net/Socket;)Lokio/Sink;
+ public final fun sink (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Lokio/Sink;
+ public final fun source (Ljava/io/File;)Lokio/Source;
+ public final fun source (Ljava/io/InputStream;)Lokio/Source;
+ public final fun source (Ljava/net/Socket;)Lokio/Source;
+ public final fun source (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Lokio/Source;
+}
+
+public final class okio/-DeprecatedUpgrade {
+ public static final fun getOkio ()Lokio/-DeprecatedOkio;
+ public static final fun getUtf8 ()Lokio/-DeprecatedUtf8;
+}
+
+public final class okio/-DeprecatedUtf8 {
+ public static final field INSTANCE Lokio/-DeprecatedUtf8;
+ public final fun size (Ljava/lang/String;)J
+ public final fun size (Ljava/lang/String;II)J
+}
+
+public final class okio/-GzipSinkExtensions {
+ public static final fun gzip (Lokio/Sink;)Lokio/GzipSink;
+}
+
+public final class okio/-GzipSourceExtensions {
+ public static final fun gzip (Lokio/Source;)Lokio/GzipSource;
+}
+
+public final class okio/-InflaterSourceExtensions {
+ public static final fun inflate (Lokio/Source;Ljava/util/zip/Inflater;)Lokio/InflaterSource;
+ public static synthetic fun inflate$default (Lokio/Source;Ljava/util/zip/Inflater;ILjava/lang/Object;)Lokio/InflaterSource;
+}
+
+public class okio/AsyncTimeout : okio/Timeout {
+ public fun <init> ()V
+ public final fun access$newTimeoutException (Ljava/io/IOException;)Ljava/io/IOException;
+ public fun cancel ()V
+ public final fun enter ()V
+ public final fun exit ()Z
+ protected fun newTimeoutException (Ljava/io/IOException;)Ljava/io/IOException;
+ public final fun sink (Lokio/Sink;)Lokio/Sink;
+ public final fun source (Lokio/Source;)Lokio/Source;
+ protected fun timedOut ()V
+ public final fun withTimeout (Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
+}
+
+public final class okio/Buffer : java/lang/Cloneable, java/nio/channels/ByteChannel, okio/BufferedSink, okio/BufferedSource {
+ public final fun -deprecated_getByte (J)B
+ public final fun -deprecated_size ()J
+ public fun <init> ()V
+ public fun buffer ()Lokio/Buffer;
+ public final fun clear ()V
+ public synthetic fun clone ()Ljava/lang/Object;
+ public fun clone ()Lokio/Buffer;
+ public fun close ()V
+ public final fun completeSegmentByteCount ()J
+ public final fun copy ()Lokio/Buffer;
+ public final fun copyTo (Ljava/io/OutputStream;)Lokio/Buffer;
+ public final fun copyTo (Ljava/io/OutputStream;J)Lokio/Buffer;
+ public final fun copyTo (Ljava/io/OutputStream;JJ)Lokio/Buffer;
+ public final fun copyTo (Lokio/Buffer;J)Lokio/Buffer;
+ public final fun copyTo (Lokio/Buffer;JJ)Lokio/Buffer;
+ public static synthetic fun copyTo$default (Lokio/Buffer;Ljava/io/OutputStream;JJILjava/lang/Object;)Lokio/Buffer;
+ public static synthetic fun copyTo$default (Lokio/Buffer;Lokio/Buffer;JILjava/lang/Object;)Lokio/Buffer;
+ public static synthetic fun copyTo$default (Lokio/Buffer;Lokio/Buffer;JJILjava/lang/Object;)Lokio/Buffer;
+ public fun emit ()Lokio/Buffer;
+ public synthetic fun emit ()Lokio/BufferedSink;
+ public fun emitCompleteSegments ()Lokio/Buffer;
+ public synthetic fun emitCompleteSegments ()Lokio/BufferedSink;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun exhausted ()Z
+ public fun flush ()V
+ public fun getBuffer ()Lokio/Buffer;
+ public final fun getByte (J)B
+ public fun hashCode ()I
+ public final fun hmacSha1 (Lokio/ByteString;)Lokio/ByteString;
+ public final fun hmacSha256 (Lokio/ByteString;)Lokio/ByteString;
+ public final fun hmacSha512 (Lokio/ByteString;)Lokio/ByteString;
+ public fun indexOf (B)J
+ public fun indexOf (BJ)J
+ public fun indexOf (BJJ)J
+ public fun indexOf (Lokio/ByteString;)J
+ public fun indexOf (Lokio/ByteString;J)J
+ public fun indexOfElement (Lokio/ByteString;)J
+ public fun indexOfElement (Lokio/ByteString;J)J
+ public fun inputStream ()Ljava/io/InputStream;
+ public fun isOpen ()Z
+ public final fun md5 ()Lokio/ByteString;
+ public fun outputStream ()Ljava/io/OutputStream;
+ public fun peek ()Lokio/BufferedSource;
+ public fun rangeEquals (JLokio/ByteString;)Z
+ public fun rangeEquals (JLokio/ByteString;II)Z
+ public fun read (Ljava/nio/ByteBuffer;)I
+ public fun read (Lokio/Buffer;J)J
+ public fun read ([B)I
+ public fun read ([BII)I
+ public fun readAll (Lokio/Sink;)J
+ public final fun readAndWriteUnsafe ()Lokio/Buffer$UnsafeCursor;
+ public final fun readAndWriteUnsafe (Lokio/Buffer$UnsafeCursor;)Lokio/Buffer$UnsafeCursor;
+ public static synthetic fun readAndWriteUnsafe$default (Lokio/Buffer;Lokio/Buffer$UnsafeCursor;ILjava/lang/Object;)Lokio/Buffer$UnsafeCursor;
+ public fun readByte ()B
+ public fun readByteArray ()[B
+ public fun readByteArray (J)[B
+ public fun readByteString ()Lokio/ByteString;
+ public fun readByteString (J)Lokio/ByteString;
+ public fun readDecimalLong ()J
+ public final fun readFrom (Ljava/io/InputStream;)Lokio/Buffer;
+ public final fun readFrom (Ljava/io/InputStream;J)Lokio/Buffer;
+ public fun readFully (Lokio/Buffer;J)V
+ public fun readFully ([B)V
+ public fun readHexadecimalUnsignedLong ()J
+ public fun readInt ()I
+ public fun readIntLe ()I
+ public fun readLong ()J
+ public fun readLongLe ()J
+ public fun readShort ()S
+ public fun readShortLe ()S
+ public fun readString (JLjava/nio/charset/Charset;)Ljava/lang/String;
+ public fun readString (Ljava/nio/charset/Charset;)Ljava/lang/String;
+ public final fun readUnsafe ()Lokio/Buffer$UnsafeCursor;
+ public final fun readUnsafe (Lokio/Buffer$UnsafeCursor;)Lokio/Buffer$UnsafeCursor;
+ public static synthetic fun readUnsafe$default (Lokio/Buffer;Lokio/Buffer$UnsafeCursor;ILjava/lang/Object;)Lokio/Buffer$UnsafeCursor;
+ public fun readUtf8 ()Ljava/lang/String;
+ public fun readUtf8 (J)Ljava/lang/String;
+ public fun readUtf8CodePoint ()I
+ public fun readUtf8Line ()Ljava/lang/String;
+ public fun readUtf8LineStrict ()Ljava/lang/String;
+ public fun readUtf8LineStrict (J)Ljava/lang/String;
+ public fun request (J)Z
+ public fun require (J)V
+ public fun select (Lokio/Options;)I
+ public final fun sha1 ()Lokio/ByteString;
+ public final fun sha256 ()Lokio/ByteString;
+ public final fun sha512 ()Lokio/ByteString;
+ public final fun size ()J
+ public fun skip (J)V
+ public final fun snapshot ()Lokio/ByteString;
+ public final fun snapshot (I)Lokio/ByteString;
+ public fun timeout ()Lokio/Timeout;
+ public fun toString ()Ljava/lang/String;
+ public fun write (Ljava/nio/ByteBuffer;)I
+ public fun write (Lokio/Buffer;J)V
+ public fun write (Lokio/ByteString;)Lokio/Buffer;
+ public synthetic fun write (Lokio/ByteString;)Lokio/BufferedSink;
+ public fun write (Lokio/ByteString;II)Lokio/Buffer;
+ public synthetic fun write (Lokio/ByteString;II)Lokio/BufferedSink;
+ public fun write (Lokio/Source;J)Lokio/Buffer;
+ public synthetic fun write (Lokio/Source;J)Lokio/BufferedSink;
+ public fun write ([B)Lokio/Buffer;
+ public synthetic fun write ([B)Lokio/BufferedSink;
+ public fun write ([BII)Lokio/Buffer;
+ public synthetic fun write ([BII)Lokio/BufferedSink;
+ public fun writeAll (Lokio/Source;)J
+ public fun writeByte (I)Lokio/Buffer;
+ public synthetic fun writeByte (I)Lokio/BufferedSink;
+ public fun writeDecimalLong (J)Lokio/Buffer;
+ public synthetic fun writeDecimalLong (J)Lokio/BufferedSink;
+ public fun writeHexadecimalUnsignedLong (J)Lokio/Buffer;
+ public synthetic fun writeHexadecimalUnsignedLong (J)Lokio/BufferedSink;
+ public fun writeInt (I)Lokio/Buffer;
+ public synthetic fun writeInt (I)Lokio/BufferedSink;
+ public fun writeIntLe (I)Lokio/Buffer;
+ public synthetic fun writeIntLe (I)Lokio/BufferedSink;
+ public fun writeLong (J)Lokio/Buffer;
+ public synthetic fun writeLong (J)Lokio/BufferedSink;
+ public fun writeLongLe (J)Lokio/Buffer;
+ public synthetic fun writeLongLe (J)Lokio/BufferedSink;
+ public fun writeShort (I)Lokio/Buffer;
+ public synthetic fun writeShort (I)Lokio/BufferedSink;
+ public fun writeShortLe (I)Lokio/Buffer;
+ public synthetic fun writeShortLe (I)Lokio/BufferedSink;
+ public fun writeString (Ljava/lang/String;IILjava/nio/charset/Charset;)Lokio/Buffer;
+ public synthetic fun writeString (Ljava/lang/String;IILjava/nio/charset/Charset;)Lokio/BufferedSink;
+ public fun writeString (Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/Buffer;
+ public synthetic fun writeString (Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/BufferedSink;
+ public final fun writeTo (Ljava/io/OutputStream;)Lokio/Buffer;
+ public final fun writeTo (Ljava/io/OutputStream;J)Lokio/Buffer;
+ public static synthetic fun writeTo$default (Lokio/Buffer;Ljava/io/OutputStream;JILjava/lang/Object;)Lokio/Buffer;
+ public fun writeUtf8 (Ljava/lang/String;)Lokio/Buffer;
+ public synthetic fun writeUtf8 (Ljava/lang/String;)Lokio/BufferedSink;
+ public fun writeUtf8 (Ljava/lang/String;II)Lokio/Buffer;
+ public synthetic fun writeUtf8 (Ljava/lang/String;II)Lokio/BufferedSink;
+ public fun writeUtf8CodePoint (I)Lokio/Buffer;
+ public synthetic fun writeUtf8CodePoint (I)Lokio/BufferedSink;
+}
+
+public final class okio/Buffer$UnsafeCursor : java/io/Closeable {
+ public field buffer Lokio/Buffer;
+ public field data [B
+ public field end I
+ public field offset J
+ public field readWrite Z
+ public field start I
+ public fun <init> ()V
+ public fun close ()V
+ public final fun expandBuffer (I)J
+ public final fun next ()I
+ public final fun resizeBuffer (J)J
+ public final fun seek (J)I
+}
+
+public abstract interface class okio/BufferedSink : java/nio/channels/WritableByteChannel, okio/Sink {
+ public abstract fun buffer ()Lokio/Buffer;
+ public abstract fun emit ()Lokio/BufferedSink;
+ public abstract fun emitCompleteSegments ()Lokio/BufferedSink;
+ public abstract fun flush ()V
+ public abstract fun getBuffer ()Lokio/Buffer;
+ public abstract fun outputStream ()Ljava/io/OutputStream;
+ public abstract fun write (Lokio/ByteString;)Lokio/BufferedSink;
+ public abstract fun write (Lokio/ByteString;II)Lokio/BufferedSink;
+ public abstract fun write (Lokio/Source;J)Lokio/BufferedSink;
+ public abstract fun write ([B)Lokio/BufferedSink;
+ public abstract fun write ([BII)Lokio/BufferedSink;
+ public abstract fun writeAll (Lokio/Source;)J
+ public abstract fun writeByte (I)Lokio/BufferedSink;
+ public abstract fun writeDecimalLong (J)Lokio/BufferedSink;
+ public abstract fun writeHexadecimalUnsignedLong (J)Lokio/BufferedSink;
+ public abstract fun writeInt (I)Lokio/BufferedSink;
+ public abstract fun writeIntLe (I)Lokio/BufferedSink;
+ public abstract fun writeLong (J)Lokio/BufferedSink;
+ public abstract fun writeLongLe (J)Lokio/BufferedSink;
+ public abstract fun writeShort (I)Lokio/BufferedSink;
+ public abstract fun writeShortLe (I)Lokio/BufferedSink;
+ public abstract fun writeString (Ljava/lang/String;IILjava/nio/charset/Charset;)Lokio/BufferedSink;
+ public abstract fun writeString (Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/BufferedSink;
+ public abstract fun writeUtf8 (Ljava/lang/String;)Lokio/BufferedSink;
+ public abstract fun writeUtf8 (Ljava/lang/String;II)Lokio/BufferedSink;
+ public abstract fun writeUtf8CodePoint (I)Lokio/BufferedSink;
+}
+
+public abstract interface class okio/BufferedSource : java/nio/channels/ReadableByteChannel, okio/Source {
+ public abstract fun buffer ()Lokio/Buffer;
+ public abstract fun exhausted ()Z
+ public abstract fun getBuffer ()Lokio/Buffer;
+ public abstract fun indexOf (B)J
+ public abstract fun indexOf (BJ)J
+ public abstract fun indexOf (BJJ)J
+ public abstract fun indexOf (Lokio/ByteString;)J
+ public abstract fun indexOf (Lokio/ByteString;J)J
+ public abstract fun indexOfElement (Lokio/ByteString;)J
+ public abstract fun indexOfElement (Lokio/ByteString;J)J
+ public abstract fun inputStream ()Ljava/io/InputStream;
+ public abstract fun peek ()Lokio/BufferedSource;
+ public abstract fun rangeEquals (JLokio/ByteString;)Z
+ public abstract fun rangeEquals (JLokio/ByteString;II)Z
+ public abstract fun read ([B)I
+ public abstract fun read ([BII)I
+ public abstract fun readAll (Lokio/Sink;)J
+ public abstract fun readByte ()B
+ public abstract fun readByteArray ()[B
+ public abstract fun readByteArray (J)[B
+ public abstract fun readByteString ()Lokio/ByteString;
+ public abstract fun readByteString (J)Lokio/ByteString;
+ public abstract fun readDecimalLong ()J
+ public abstract fun readFully (Lokio/Buffer;J)V
+ public abstract fun readFully ([B)V
+ public abstract fun readHexadecimalUnsignedLong ()J
+ public abstract fun readInt ()I
+ public abstract fun readIntLe ()I
+ public abstract fun readLong ()J
+ public abstract fun readLongLe ()J
+ public abstract fun readShort ()S
+ public abstract fun readShortLe ()S
+ public abstract fun readString (JLjava/nio/charset/Charset;)Ljava/lang/String;
+ public abstract fun readString (Ljava/nio/charset/Charset;)Ljava/lang/String;
+ public abstract fun readUtf8 ()Ljava/lang/String;
+ public abstract fun readUtf8 (J)Ljava/lang/String;
+ public abstract fun readUtf8CodePoint ()I
+ public abstract fun readUtf8Line ()Ljava/lang/String;
+ public abstract fun readUtf8LineStrict ()Ljava/lang/String;
+ public abstract fun readUtf8LineStrict (J)Ljava/lang/String;
+ public abstract fun request (J)Z
+ public abstract fun require (J)V
+ public abstract fun select (Lokio/Options;)I
+ public abstract fun skip (J)V
+}
+
+public class okio/ByteString : java/io/Serializable, java/lang/Comparable {
+ public static final field Companion Lokio/ByteString$Companion;
+ public static final field EMPTY Lokio/ByteString;
+ public final fun -deprecated_getByte (I)B
+ public final fun -deprecated_size ()I
+ public fun asByteBuffer ()Ljava/nio/ByteBuffer;
+ public fun base64 ()Ljava/lang/String;
+ public fun base64Url ()Ljava/lang/String;
+ public synthetic fun compareTo (Ljava/lang/Object;)I
+ public fun compareTo (Lokio/ByteString;)I
+ public fun copyInto (I[BII)V
+ public static synthetic fun copyInto$default (Lokio/ByteString;I[BIIILjava/lang/Object;)V
+ public static final fun decodeBase64 (Ljava/lang/String;)Lokio/ByteString;
+ public static final fun decodeHex (Ljava/lang/String;)Lokio/ByteString;
+ public static final fun encodeString (Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/ByteString;
+ public static final fun encodeUtf8 (Ljava/lang/String;)Lokio/ByteString;
+ public final fun endsWith (Lokio/ByteString;)Z
+ public final fun endsWith ([B)Z
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getByte (I)B
+ public fun hashCode ()I
+ public fun hex ()Ljava/lang/String;
+ public fun hmacSha1 (Lokio/ByteString;)Lokio/ByteString;
+ public fun hmacSha256 (Lokio/ByteString;)Lokio/ByteString;
+ public fun hmacSha512 (Lokio/ByteString;)Lokio/ByteString;
+ public final fun indexOf (Lokio/ByteString;)I
+ public final fun indexOf (Lokio/ByteString;I)I
+ public final fun indexOf ([B)I
+ public fun indexOf ([BI)I
+ public static synthetic fun indexOf$default (Lokio/ByteString;Lokio/ByteString;IILjava/lang/Object;)I
+ public static synthetic fun indexOf$default (Lokio/ByteString;[BIILjava/lang/Object;)I
+ public final fun lastIndexOf (Lokio/ByteString;)I
+ public final fun lastIndexOf (Lokio/ByteString;I)I
+ public final fun lastIndexOf ([B)I
+ public fun lastIndexOf ([BI)I
+ public static synthetic fun lastIndexOf$default (Lokio/ByteString;Lokio/ByteString;IILjava/lang/Object;)I
+ public static synthetic fun lastIndexOf$default (Lokio/ByteString;[BIILjava/lang/Object;)I
+ public final fun md5 ()Lokio/ByteString;
+ public static final fun of (Ljava/nio/ByteBuffer;)Lokio/ByteString;
+ public static final fun of ([B)Lokio/ByteString;
+ public static final fun of ([BII)Lokio/ByteString;
+ public fun rangeEquals (ILokio/ByteString;II)Z
+ public fun rangeEquals (I[BII)Z
+ public static final fun read (Ljava/io/InputStream;I)Lokio/ByteString;
+ public final fun sha1 ()Lokio/ByteString;
+ public final fun sha256 ()Lokio/ByteString;
+ public final fun sha512 ()Lokio/ByteString;
+ public final fun size ()I
+ public final fun startsWith (Lokio/ByteString;)Z
+ public final fun startsWith ([B)Z
+ public fun string (Ljava/nio/charset/Charset;)Ljava/lang/String;
+ public final fun substring ()Lokio/ByteString;
+ public final fun substring (I)Lokio/ByteString;
+ public fun substring (II)Lokio/ByteString;
+ public static synthetic fun substring$default (Lokio/ByteString;IIILjava/lang/Object;)Lokio/ByteString;
+ public fun toAsciiLowercase ()Lokio/ByteString;
+ public fun toAsciiUppercase ()Lokio/ByteString;
+ public fun toByteArray ()[B
+ public fun toString ()Ljava/lang/String;
+ public fun utf8 ()Ljava/lang/String;
+ public fun write (Ljava/io/OutputStream;)V
+}
+
+public final class okio/ByteString$Companion {
+ public final fun -deprecated_decodeBase64 (Ljava/lang/String;)Lokio/ByteString;
+ public final fun -deprecated_decodeHex (Ljava/lang/String;)Lokio/ByteString;
+ public final fun -deprecated_encodeString (Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/ByteString;
+ public final fun -deprecated_encodeUtf8 (Ljava/lang/String;)Lokio/ByteString;
+ public final fun -deprecated_of (Ljava/nio/ByteBuffer;)Lokio/ByteString;
+ public final fun -deprecated_of ([BII)Lokio/ByteString;
+ public final fun -deprecated_read (Ljava/io/InputStream;I)Lokio/ByteString;
+ public final fun decodeBase64 (Ljava/lang/String;)Lokio/ByteString;
+ public final fun decodeHex (Ljava/lang/String;)Lokio/ByteString;
+ public final fun encodeString (Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/ByteString;
+ public static synthetic fun encodeString$default (Lokio/ByteString$Companion;Ljava/lang/String;Ljava/nio/charset/Charset;ILjava/lang/Object;)Lokio/ByteString;
+ public final fun encodeUtf8 (Ljava/lang/String;)Lokio/ByteString;
+ public final fun of (Ljava/nio/ByteBuffer;)Lokio/ByteString;
+ public final fun of ([B)Lokio/ByteString;
+ public final fun of ([BII)Lokio/ByteString;
+ public static synthetic fun of$default (Lokio/ByteString$Companion;[BIIILjava/lang/Object;)Lokio/ByteString;
+ public final fun read (Ljava/io/InputStream;I)Lokio/ByteString;
+}
+
+public final class okio/CipherSink : okio/Sink {
+ public fun <init> (Lokio/BufferedSink;Ljavax/crypto/Cipher;)V
+ public fun close ()V
+ public fun flush ()V
+ public final fun getCipher ()Ljavax/crypto/Cipher;
+ public fun timeout ()Lokio/Timeout;
+ public fun write (Lokio/Buffer;J)V
+}
+
+public final class okio/CipherSource : okio/Source {
+ public fun <init> (Lokio/BufferedSource;Ljavax/crypto/Cipher;)V
+ public fun close ()V
+ public final fun getCipher ()Ljavax/crypto/Cipher;
+ public fun read (Lokio/Buffer;J)J
+ public fun timeout ()Lokio/Timeout;
+}
+
+public final class okio/DeflaterSink : okio/Sink {
+ public fun <init> (Lokio/Sink;Ljava/util/zip/Deflater;)V
+ public fun close ()V
+ public fun flush ()V
+ public fun timeout ()Lokio/Timeout;
+ public fun toString ()Ljava/lang/String;
+ public fun write (Lokio/Buffer;J)V
+}
+
+public abstract interface annotation class okio/ExperimentalFileSystem : java/lang/annotation/Annotation {
+}
+
+public abstract class okio/FileHandle : java/io/Closeable {
+ public fun <init> (Z)V
+ public final fun appendingSink ()Lokio/Sink;
+ public final fun close ()V
+ public final fun flush ()V
+ public final fun getLock ()Ljava/util/concurrent/locks/ReentrantLock;
+ public final fun getReadWrite ()Z
+ public final fun position (Lokio/Sink;)J
+ public final fun position (Lokio/Source;)J
+ protected abstract fun protectedClose ()V
+ protected abstract fun protectedFlush ()V
+ protected abstract fun protectedRead (J[BII)I
+ protected abstract fun protectedResize (J)V
+ protected abstract fun protectedSize ()J
+ protected abstract fun protectedWrite (J[BII)V
+ public final fun read (JLokio/Buffer;J)J
+ public final fun read (J[BII)I
+ public final fun reposition (Lokio/Sink;J)V
+ public final fun reposition (Lokio/Source;J)V
+ public final fun resize (J)V
+ public final fun sink (J)Lokio/Sink;
+ public static synthetic fun sink$default (Lokio/FileHandle;JILjava/lang/Object;)Lokio/Sink;
+ public final fun size ()J
+ public final fun source (J)Lokio/Source;
+ public static synthetic fun source$default (Lokio/FileHandle;JILjava/lang/Object;)Lokio/Source;
+ public final fun write (JLokio/Buffer;J)V
+ public final fun write (J[BII)V
+}
+
+public final class okio/FileMetadata {
+ public fun <init> ()V
+ public fun <init> (ZZLokio/Path;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/util/Map;)V
+ public synthetic fun <init> (ZZLokio/Path;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun copy (ZZLokio/Path;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/util/Map;)Lokio/FileMetadata;
+ public static synthetic fun copy$default (Lokio/FileMetadata;ZZLokio/Path;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/util/Map;ILjava/lang/Object;)Lokio/FileMetadata;
+ public final fun extra (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+ public final fun getCreatedAtMillis ()Ljava/lang/Long;
+ public final fun getExtras ()Ljava/util/Map;
+ public final fun getLastAccessedAtMillis ()Ljava/lang/Long;
+ public final fun getLastModifiedAtMillis ()Ljava/lang/Long;
+ public final fun getSize ()Ljava/lang/Long;
+ public final fun getSymlinkTarget ()Lokio/Path;
+ public final fun isDirectory ()Z
+ public final fun isRegularFile ()Z
+ public fun toString ()Ljava/lang/String;
+}
+
+public abstract class okio/FileSystem {
+ public static final field Companion Lokio/FileSystem$Companion;
+ public static final field RESOURCES Lokio/FileSystem;
+ public static final field SYSTEM Lokio/FileSystem;
+ public static final field SYSTEM_TEMPORARY_DIRECTORY Lokio/Path;
+ public final fun -read (Lokio/Path;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+ public final fun -write (Lokio/Path;ZLkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+ public static synthetic fun -write$default (Lokio/FileSystem;Lokio/Path;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object;
+ public fun <init> ()V
+ public final fun appendingSink (Lokio/Path;)Lokio/Sink;
+ public abstract fun appendingSink (Lokio/Path;Z)Lokio/Sink;
+ public static synthetic fun appendingSink$default (Lokio/FileSystem;Lokio/Path;ZILjava/lang/Object;)Lokio/Sink;
+ public abstract fun atomicMove (Lokio/Path;Lokio/Path;)V
+ public abstract fun canonicalize (Lokio/Path;)Lokio/Path;
+ public fun copy (Lokio/Path;Lokio/Path;)V
+ public final fun createDirectories (Lokio/Path;)V
+ public final fun createDirectories (Lokio/Path;Z)V
+ public static synthetic fun createDirectories$default (Lokio/FileSystem;Lokio/Path;ZILjava/lang/Object;)V
+ public final fun createDirectory (Lokio/Path;)V
+ public abstract fun createDirectory (Lokio/Path;Z)V
+ public static synthetic fun createDirectory$default (Lokio/FileSystem;Lokio/Path;ZILjava/lang/Object;)V
+ public abstract fun createSymlink (Lokio/Path;Lokio/Path;)V
+ public final fun delete (Lokio/Path;)V
+ public abstract fun delete (Lokio/Path;Z)V
+ public static synthetic fun delete$default (Lokio/FileSystem;Lokio/Path;ZILjava/lang/Object;)V
+ public final fun deleteRecursively (Lokio/Path;)V
+ public fun deleteRecursively (Lokio/Path;Z)V
+ public static synthetic fun deleteRecursively$default (Lokio/FileSystem;Lokio/Path;ZILjava/lang/Object;)V
+ public final fun exists (Lokio/Path;)Z
+ public static final fun get (Ljava/nio/file/FileSystem;)Lokio/FileSystem;
+ public abstract fun list (Lokio/Path;)Ljava/util/List;
+ public abstract fun listOrNull (Lokio/Path;)Ljava/util/List;
+ public final fun listRecursively (Lokio/Path;)Lkotlin/sequences/Sequence;
+ public fun listRecursively (Lokio/Path;Z)Lkotlin/sequences/Sequence;
+ public static synthetic fun listRecursively$default (Lokio/FileSystem;Lokio/Path;ZILjava/lang/Object;)Lkotlin/sequences/Sequence;
+ public final fun metadata (Lokio/Path;)Lokio/FileMetadata;
+ public abstract fun metadataOrNull (Lokio/Path;)Lokio/FileMetadata;
+ public abstract fun openReadOnly (Lokio/Path;)Lokio/FileHandle;
+ public final fun openReadWrite (Lokio/Path;)Lokio/FileHandle;
+ public abstract fun openReadWrite (Lokio/Path;ZZ)Lokio/FileHandle;
+ public static synthetic fun openReadWrite$default (Lokio/FileSystem;Lokio/Path;ZZILjava/lang/Object;)Lokio/FileHandle;
+ public final fun sink (Lokio/Path;)Lokio/Sink;
+ public abstract fun sink (Lokio/Path;Z)Lokio/Sink;
+ public static synthetic fun sink$default (Lokio/FileSystem;Lokio/Path;ZILjava/lang/Object;)Lokio/Sink;
+ public abstract fun source (Lokio/Path;)Lokio/Source;
+}
+
+public final class okio/FileSystem$Companion {
+ public final fun get (Ljava/nio/file/FileSystem;)Lokio/FileSystem;
+}
+
+public abstract class okio/ForwardingFileSystem : okio/FileSystem {
+ public fun <init> (Lokio/FileSystem;)V
+ public fun appendingSink (Lokio/Path;Z)Lokio/Sink;
+ public fun atomicMove (Lokio/Path;Lokio/Path;)V
+ public fun canonicalize (Lokio/Path;)Lokio/Path;
+ public fun createDirectory (Lokio/Path;Z)V
+ public fun createSymlink (Lokio/Path;Lokio/Path;)V
+ public final fun delegate ()Lokio/FileSystem;
+ public fun delete (Lokio/Path;Z)V
+ public fun list (Lokio/Path;)Ljava/util/List;
+ public fun listOrNull (Lokio/Path;)Ljava/util/List;
+ public fun listRecursively (Lokio/Path;Z)Lkotlin/sequences/Sequence;
+ public fun metadataOrNull (Lokio/Path;)Lokio/FileMetadata;
+ public fun onPathParameter (Lokio/Path;Ljava/lang/String;Ljava/lang/String;)Lokio/Path;
+ public fun onPathResult (Lokio/Path;Ljava/lang/String;)Lokio/Path;
+ public fun openReadOnly (Lokio/Path;)Lokio/FileHandle;
+ public fun openReadWrite (Lokio/Path;ZZ)Lokio/FileHandle;
+ public fun sink (Lokio/Path;Z)Lokio/Sink;
+ public fun source (Lokio/Path;)Lokio/Source;
+ public fun toString ()Ljava/lang/String;
+}
+
+public abstract class okio/ForwardingSink : okio/Sink {
+ public final fun -deprecated_delegate ()Lokio/Sink;
+ public fun <init> (Lokio/Sink;)V
+ public fun close ()V
+ public final fun delegate ()Lokio/Sink;
+ public fun flush ()V
+ public fun timeout ()Lokio/Timeout;
+ public fun toString ()Ljava/lang/String;
+ public fun write (Lokio/Buffer;J)V
+}
+
+public abstract class okio/ForwardingSource : okio/Source {
+ public final fun -deprecated_delegate ()Lokio/Source;
+ public fun <init> (Lokio/Source;)V
+ public fun close ()V
+ public final fun delegate ()Lokio/Source;
+ public fun read (Lokio/Buffer;J)J
+ public fun timeout ()Lokio/Timeout;
+ public fun toString ()Ljava/lang/String;
+}
+
+public class okio/ForwardingTimeout : okio/Timeout {
+ public fun <init> (Lokio/Timeout;)V
+ public fun awaitSignal (Ljava/util/concurrent/locks/Condition;)V
+ public fun cancel ()V
+ public fun clearDeadline ()Lokio/Timeout;
+ public fun clearTimeout ()Lokio/Timeout;
+ public fun deadlineNanoTime ()J
+ public fun deadlineNanoTime (J)Lokio/Timeout;
+ public final fun delegate ()Lokio/Timeout;
+ public fun hasDeadline ()Z
+ public final fun setDelegate (Lokio/Timeout;)Lokio/ForwardingTimeout;
+ public final synthetic fun setDelegate (Lokio/Timeout;)V
+ public fun throwIfReached ()V
+ public fun timeout (JLjava/util/concurrent/TimeUnit;)Lokio/Timeout;
+ public fun timeoutNanos ()J
+ public fun waitUntilNotified (Ljava/lang/Object;)V
+}
+
+public final class okio/GzipSink : okio/Sink {
+ public final fun -deprecated_deflater ()Ljava/util/zip/Deflater;
+ public fun <init> (Lokio/Sink;)V
+ public fun close ()V
+ public final fun deflater ()Ljava/util/zip/Deflater;
+ public fun flush ()V
+ public fun timeout ()Lokio/Timeout;
+ public fun write (Lokio/Buffer;J)V
+}
+
+public final class okio/GzipSource : okio/Source {
+ public fun <init> (Lokio/Source;)V
+ public fun close ()V
+ public fun read (Lokio/Buffer;J)J
+ public fun timeout ()Lokio/Timeout;
+}
+
+public final class okio/HashingSink : okio/ForwardingSink, okio/Sink {
+ public static final field Companion Lokio/HashingSink$Companion;
+ public final fun -deprecated_hash ()Lokio/ByteString;
+ public final fun hash ()Lokio/ByteString;
+ public static final fun hmacSha1 (Lokio/Sink;Lokio/ByteString;)Lokio/HashingSink;
+ public static final fun hmacSha256 (Lokio/Sink;Lokio/ByteString;)Lokio/HashingSink;
+ public static final fun hmacSha512 (Lokio/Sink;Lokio/ByteString;)Lokio/HashingSink;
+ public static final fun md5 (Lokio/Sink;)Lokio/HashingSink;
+ public static final fun sha1 (Lokio/Sink;)Lokio/HashingSink;
+ public static final fun sha256 (Lokio/Sink;)Lokio/HashingSink;
+ public static final fun sha512 (Lokio/Sink;)Lokio/HashingSink;
+ public fun write (Lokio/Buffer;J)V
+}
+
+public final class okio/HashingSink$Companion {
+ public final fun hmacSha1 (Lokio/Sink;Lokio/ByteString;)Lokio/HashingSink;
+ public final fun hmacSha256 (Lokio/Sink;Lokio/ByteString;)Lokio/HashingSink;
+ public final fun hmacSha512 (Lokio/Sink;Lokio/ByteString;)Lokio/HashingSink;
+ public final fun md5 (Lokio/Sink;)Lokio/HashingSink;
+ public final fun sha1 (Lokio/Sink;)Lokio/HashingSink;
+ public final fun sha256 (Lokio/Sink;)Lokio/HashingSink;
+ public final fun sha512 (Lokio/Sink;)Lokio/HashingSink;
+}
+
+public final class okio/HashingSource : okio/ForwardingSource, okio/Source {
+ public static final field Companion Lokio/HashingSource$Companion;
+ public final fun -deprecated_hash ()Lokio/ByteString;
+ public final fun hash ()Lokio/ByteString;
+ public static final fun hmacSha1 (Lokio/Source;Lokio/ByteString;)Lokio/HashingSource;
+ public static final fun hmacSha256 (Lokio/Source;Lokio/ByteString;)Lokio/HashingSource;
+ public static final fun hmacSha512 (Lokio/Source;Lokio/ByteString;)Lokio/HashingSource;
+ public static final fun md5 (Lokio/Source;)Lokio/HashingSource;
+ public fun read (Lokio/Buffer;J)J
+ public static final fun sha1 (Lokio/Source;)Lokio/HashingSource;
+ public static final fun sha256 (Lokio/Source;)Lokio/HashingSource;
+ public static final fun sha512 (Lokio/Source;)Lokio/HashingSource;
+}
+
+public final class okio/HashingSource$Companion {
+ public final fun hmacSha1 (Lokio/Source;Lokio/ByteString;)Lokio/HashingSource;
+ public final fun hmacSha256 (Lokio/Source;Lokio/ByteString;)Lokio/HashingSource;
+ public final fun hmacSha512 (Lokio/Source;Lokio/ByteString;)Lokio/HashingSource;
+ public final fun md5 (Lokio/Source;)Lokio/HashingSource;
+ public final fun sha1 (Lokio/Source;)Lokio/HashingSource;
+ public final fun sha256 (Lokio/Source;)Lokio/HashingSource;
+ public final fun sha512 (Lokio/Source;)Lokio/HashingSource;
+}
+
+public final class okio/InflaterSource : okio/Source {
+ public fun <init> (Lokio/Source;Ljava/util/zip/Inflater;)V
+ public fun close ()V
+ public fun read (Lokio/Buffer;J)J
+ public final fun readOrInflate (Lokio/Buffer;J)J
+ public final fun refill ()Z
+ public fun timeout ()Lokio/Timeout;
+}
+
+public final class okio/Okio {
+ public static final fun appendingSink (Ljava/io/File;)Lokio/Sink;
+ public static final fun asResourceFileSystem (Ljava/lang/ClassLoader;)Lokio/FileSystem;
+ public static final fun blackhole ()Lokio/Sink;
+ public static final fun buffer (Lokio/Sink;)Lokio/BufferedSink;
+ public static final fun buffer (Lokio/Source;)Lokio/BufferedSource;
+ public static final fun cipherSink (Lokio/Sink;Ljavax/crypto/Cipher;)Lokio/CipherSink;
+ public static final fun cipherSource (Lokio/Source;Ljavax/crypto/Cipher;)Lokio/CipherSource;
+ public static final fun hashingSink (Lokio/Sink;Ljava/security/MessageDigest;)Lokio/HashingSink;
+ public static final fun hashingSink (Lokio/Sink;Ljavax/crypto/Mac;)Lokio/HashingSink;
+ public static final fun hashingSource (Lokio/Source;Ljava/security/MessageDigest;)Lokio/HashingSource;
+ public static final fun hashingSource (Lokio/Source;Ljavax/crypto/Mac;)Lokio/HashingSource;
+ public static final fun openZip (Lokio/FileSystem;Lokio/Path;)Lokio/FileSystem;
+ public static final fun sink (Ljava/io/File;)Lokio/Sink;
+ public static final fun sink (Ljava/io/File;Z)Lokio/Sink;
+ public static final fun sink (Ljava/io/OutputStream;)Lokio/Sink;
+ public static final fun sink (Ljava/net/Socket;)Lokio/Sink;
+ public static final fun sink (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Lokio/Sink;
+ public static synthetic fun sink$default (Ljava/io/File;ZILjava/lang/Object;)Lokio/Sink;
+ public static final fun source (Ljava/io/File;)Lokio/Source;
+ public static final fun source (Ljava/io/InputStream;)Lokio/Source;
+ public static final fun source (Ljava/net/Socket;)Lokio/Source;
+ public static final fun source (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Lokio/Source;
+ public static final fun use (Ljava/io/Closeable;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+}
+
+public final class okio/Options : kotlin/collections/AbstractList, java/util/RandomAccess {
+ public static final field Companion Lokio/Options$Companion;
+ public synthetic fun <init> ([Lokio/ByteString;[ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun contains (Ljava/lang/Object;)Z
+ public fun contains (Lokio/ByteString;)Z
+ public synthetic fun get (I)Ljava/lang/Object;
+ public fun get (I)Lokio/ByteString;
+ public fun getSize ()I
+ public final fun indexOf (Ljava/lang/Object;)I
+ public fun indexOf (Lokio/ByteString;)I
+ public final fun lastIndexOf (Ljava/lang/Object;)I
+ public fun lastIndexOf (Lokio/ByteString;)I
+ public static final fun of ([Lokio/ByteString;)Lokio/Options;
+}
+
+public final class okio/Options$Companion {
+ public final fun of ([Lokio/ByteString;)Lokio/Options;
+}
+
+public final class okio/Path : java/lang/Comparable {
+ public static final field Companion Lokio/Path$Companion;
+ public static final field DIRECTORY_SEPARATOR Ljava/lang/String;
+ public synthetic fun compareTo (Ljava/lang/Object;)I
+ public fun compareTo (Lokio/Path;)I
+ public fun equals (Ljava/lang/Object;)Z
+ public static final fun get (Ljava/io/File;)Lokio/Path;
+ public static final fun get (Ljava/io/File;Z)Lokio/Path;
+ public static final fun get (Ljava/lang/String;)Lokio/Path;
+ public static final fun get (Ljava/lang/String;Z)Lokio/Path;
+ public static final fun get (Ljava/nio/file/Path;)Lokio/Path;
+ public static final fun get (Ljava/nio/file/Path;Z)Lokio/Path;
+ public final fun getRoot ()Lokio/Path;
+ public final fun getSegments ()Ljava/util/List;
+ public final fun getSegmentsBytes ()Ljava/util/List;
+ public fun hashCode ()I
+ public final fun isAbsolute ()Z
+ public final fun isRelative ()Z
+ public final fun isRoot ()Z
+ public final fun name ()Ljava/lang/String;
+ public final fun nameBytes ()Lokio/ByteString;
+ public final fun normalized ()Lokio/Path;
+ public final fun parent ()Lokio/Path;
+ public final fun relativeTo (Lokio/Path;)Lokio/Path;
+ public final fun resolve (Ljava/lang/String;)Lokio/Path;
+ public final fun resolve (Ljava/lang/String;Z)Lokio/Path;
+ public final fun resolve (Lokio/ByteString;)Lokio/Path;
+ public final fun resolve (Lokio/ByteString;Z)Lokio/Path;
+ public final fun resolve (Lokio/Path;)Lokio/Path;
+ public final fun resolve (Lokio/Path;Z)Lokio/Path;
+ public static synthetic fun resolve$default (Lokio/Path;Ljava/lang/String;ZILjava/lang/Object;)Lokio/Path;
+ public static synthetic fun resolve$default (Lokio/Path;Lokio/ByteString;ZILjava/lang/Object;)Lokio/Path;
+ public static synthetic fun resolve$default (Lokio/Path;Lokio/Path;ZILjava/lang/Object;)Lokio/Path;
+ public final fun toFile ()Ljava/io/File;
+ public final fun toNioPath ()Ljava/nio/file/Path;
+ public fun toString ()Ljava/lang/String;
+ public final fun volumeLetter ()Ljava/lang/Character;
+}
+
+public final class okio/Path$Companion {
+ public final fun get (Ljava/io/File;)Lokio/Path;
+ public final fun get (Ljava/io/File;Z)Lokio/Path;
+ public final fun get (Ljava/lang/String;)Lokio/Path;
+ public final fun get (Ljava/lang/String;Z)Lokio/Path;
+ public final fun get (Ljava/nio/file/Path;)Lokio/Path;
+ public final fun get (Ljava/nio/file/Path;Z)Lokio/Path;
+ public static synthetic fun get$default (Lokio/Path$Companion;Ljava/io/File;ZILjava/lang/Object;)Lokio/Path;
+ public static synthetic fun get$default (Lokio/Path$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lokio/Path;
+ public static synthetic fun get$default (Lokio/Path$Companion;Ljava/nio/file/Path;ZILjava/lang/Object;)Lokio/Path;
+}
+
+public final class okio/Pipe {
+ public final fun -deprecated_sink ()Lokio/Sink;
+ public final fun -deprecated_source ()Lokio/Source;
+ public fun <init> (J)V
+ public final fun cancel ()V
+ public final fun fold (Lokio/Sink;)V
+ public final fun getCondition ()Ljava/util/concurrent/locks/Condition;
+ public final fun getLock ()Ljava/util/concurrent/locks/ReentrantLock;
+ public final fun sink ()Lokio/Sink;
+ public final fun source ()Lokio/Source;
+}
+
+public abstract interface class okio/Sink : java/io/Closeable, java/io/Flushable {
+ public abstract fun close ()V
+ public abstract fun flush ()V
+ public abstract fun timeout ()Lokio/Timeout;
+ public abstract fun write (Lokio/Buffer;J)V
+}
+
+public abstract interface class okio/Source : java/io/Closeable {
+ public abstract fun close ()V
+ public abstract fun read (Lokio/Buffer;J)J
+ public abstract fun timeout ()Lokio/Timeout;
+}
+
+public final class okio/Throttler {
+ public fun <init> ()V
+ public final fun bytesPerSecond (J)V
+ public final fun bytesPerSecond (JJ)V
+ public final fun bytesPerSecond (JJJ)V
+ public static synthetic fun bytesPerSecond$default (Lokio/Throttler;JJJILjava/lang/Object;)V
+ public final fun getCondition ()Ljava/util/concurrent/locks/Condition;
+ public final fun getLock ()Ljava/util/concurrent/locks/ReentrantLock;
+ public final fun sink (Lokio/Sink;)Lokio/Sink;
+ public final fun source (Lokio/Source;)Lokio/Source;
+}
+
+public class okio/Timeout {
+ public static final field Companion Lokio/Timeout$Companion;
+ public static final field NONE Lokio/Timeout;
+ public fun <init> ()V
+ public fun awaitSignal (Ljava/util/concurrent/locks/Condition;)V
+ public fun cancel ()V
+ public fun clearDeadline ()Lokio/Timeout;
+ public fun clearTimeout ()Lokio/Timeout;
+ public final fun deadline (JLjava/util/concurrent/TimeUnit;)Lokio/Timeout;
+ public fun deadlineNanoTime ()J
+ public fun deadlineNanoTime (J)Lokio/Timeout;
+ public fun hasDeadline ()Z
+ public final fun intersectWith (Lokio/Timeout;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
+ public fun throwIfReached ()V
+ public fun timeout (JLjava/util/concurrent/TimeUnit;)Lokio/Timeout;
+ public fun timeoutNanos ()J
+ public fun waitUntilNotified (Ljava/lang/Object;)V
+}
+
+public final class okio/Timeout$Companion {
+ public final fun minTimeout (JJ)J
+ public final fun timeout (Lokio/Timeout;JLkotlin/time/DurationUnit;)Lokio/Timeout;
+ public final fun timeout-HG0u8IE (Lokio/Timeout;J)Lokio/Timeout;
+}
+
+public final class okio/Utf8 {
+ public static final fun size (Ljava/lang/String;)J
+ public static final fun size (Ljava/lang/String;I)J
+ public static final fun size (Ljava/lang/String;II)J
+ public static synthetic fun size$default (Ljava/lang/String;IIILjava/lang/Object;)J
+}
+
+public final class okio/_JvmPlatformKt {
+ public static final fun withLock (Ljava/util/concurrent/locks/ReentrantLock;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
+}
+
+public final class okio/internal/_Utf8Kt {
+ public static final fun commonAsUtf8ToByteArray (Ljava/lang/String;)[B
+ public static final fun commonToUtf8String ([BII)Ljava/lang/String;
+ public static synthetic fun commonToUtf8String$default ([BIIILjava/lang/Object;)Ljava/lang/String;
+}
+
diff --git a/okio/build.gradle b/okio/build.gradle
deleted file mode 100644
index 980ecb0a..00000000
--- a/okio/build.gradle
+++ /dev/null
@@ -1,193 +0,0 @@
-apply plugin: 'org.jetbrains.kotlin.multiplatform'
-
-/*
- * Here's the main hierarchy of variants. Any `expect` functions in one level of the tree are
- * `actual` functions in a (potentially indirect) child node.
- *
- * ```
- * common
- * |-- jvm
- * '-- nonJvm
- * |-- js
- * '-- native
- * |- unix
- * | |-- apple
- * | | |-- iosArm64
- * | | |-- iosX64
- * | | |-- macosX64
- * | | |-- watchosArm32
- * | | |-- watchosArm64
- * | | '-- watchosX86
- * | '-- linux
- * | '-- linuxX64
- * '-- mingw
- * '-- mingwX64
- * ```
- *
- * Every child of `native` also includes a source set that depends on the pointer size:
- *
- * * sizet32 for watchOS, including watchOS 64-bit architectures
- * * sizet64 for everything else
- *
- * The `hashFunctions` source set builds on all platforms. It ships as a main source set on non-JVM
- * platforms and as a test source set on the JVM platform.
- */
-kotlin {
- jvm {
- withJava()
- }
- if (kmpJsEnabled) {
- js {
- configure([compilations.main, compilations.test]) {
- tasks.getByName(compileKotlinTaskName).kotlinOptions {
- moduleKind = "umd"
- sourceMap = true
- metaInfo = true
- }
- }
- nodejs {
- testTask {
- useMocha {
- timeout = "30s"
- }
- }
- }
- }
- }
- if (kmpNativeEnabled) {
- iosX64()
- iosArm64()
- watchosArm32()
- watchosArm64()
- watchosX86()
- // Required to generate tests tasks: https://youtrack.jetbrains.com/issue/KT-26547
- linuxX64()
- macosX64()
- mingwX64()
- }
- sourceSets {
- all {
- languageSettings {
- useExperimentalAnnotation('kotlin.RequiresOptIn')
- }
- }
- commonMain {
- dependencies {
- api deps.kotlin.stdLib.common
- }
- }
- commonTest {
- dependencies {
- implementation deps.kotlin.test.common
- implementation deps.kotlin.test.annotations
- implementation deps.kotlin.time
- }
- }
- nonJvmMain {
- kotlin.srcDirs += 'src/hashFunctions/kotlin'
- }
- jvmMain {
- dependencies {
- api deps.kotlin.stdLib.jdk6
- compileOnly deps.animalSniffer.annotations
- }
- }
- jvmTest {
- kotlin.srcDirs += 'src/hashFunctions/kotlin'
- dependencies {
- implementation deps.test.junit
- implementation deps.test.assertj
- implementation deps.kotlin.test.jdk
- }
- }
- jsMain {
- dependsOn nonJvmMain
- dependencies {
- api deps.kotlin.stdLib.js
- }
- }
- jsTest {
- dependencies {
- implementation deps.kotlin.test.js
- }
- }
-
- nativeMain {
- dependsOn nonJvmMain
- }
- nativeTest {
- dependsOn commonTest
- }
-
- sizet32Main {
- dependsOn nativeMain
- }
- sizet64Main {
- dependsOn nativeMain
- }
-
- mingwMain {
- dependsOn nativeMain
- }
- mingwX64Main {
- dependsOn sizet64Main
- dependsOn mingwMain
- }
- mingwX64Test {
- dependsOn nativeTest
- }
-
- unixMain {
- dependsOn nativeMain
- }
-
- appleMain {
- dependsOn unixMain
- }
- appleTest {
- dependsOn nativeTest
- }
- configure([iosX64Main, iosArm64Main, macosX64Main]) {
- dependsOn sizet64Main
- dependsOn appleMain
- }
- configure([iosX64Test, iosArm64Test, macosX64Test]) {
- dependsOn appleTest
- }
- configure([watchosArm32Main, watchosArm64Main, watchosX86Main]) {
- // Note that size_t is 32-bit on all watchOS versions (ie. pointers are always 32-bit).
- dependsOn sizet32Main
- dependsOn appleMain
- }
- configure([watchosArm32Test, watchosArm64Test, watchosX86Test]) {
- dependsOn appleTest
- }
-
- linuxMain {
- dependsOn unixMain
- dependsOn nativeMain
- }
- linuxX64Main {
- dependsOn sizet64Main
- dependsOn linuxMain
- }
- linuxX64Test {
- dependsOn nativeTest
- }
- }
-}
-
-tasks.withType(JavaCompile) {
- options.encoding = 'UTF-8'
- targetCompatibility = JavaVersion.VERSION_1_8
-}
-
-// modify these lines for MANIFEST.MF properties or for specific bnd instructions
-project.ext.bndManifest = '''
- Export-Package: okio
- Automatic-Module-Name: okio
- Bundle-SymbolicName: com.squareup.okio
- '''
-
-apply from: 'jvm/jvm.gradle'
-apply from: "$rootDir/gradle/gradle-mvn-mpp-push.gradle"
diff --git a/okio/build.gradle.kts b/okio/build.gradle.kts
new file mode 100644
index 00000000..cac4a345
--- /dev/null
+++ b/okio/build.gradle.kts
@@ -0,0 +1,197 @@
+import aQute.bnd.gradle.BundleTaskConvention
+import com.vanniktech.maven.publish.JavadocJar.Dokka
+import com.vanniktech.maven.publish.KotlinMultiplatform
+import com.vanniktech.maven.publish.MavenPublishBaseExtension
+import kotlinx.validation.ApiValidationExtension
+import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithTests
+import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
+import org.jetbrains.kotlin.gradle.plugin.mpp.TestExecutable
+
+plugins {
+ kotlin("multiplatform")
+ id("org.jetbrains.dokka")
+ id("com.vanniktech.maven.publish.base")
+ id("build-support")
+ id("binary-compatibility-validator")
+}
+
+/*
+ * Here's the main hierarchy of variants. Any `expect` functions in one level of the tree are
+ * `actual` functions in a (potentially indirect) child node.
+ *
+ * ```
+ * common
+ * |-- js
+ * |-- jvm
+ * |-- native
+ * | |-- mingw
+ * | | '-- mingwX64
+ * | '-- unix
+ * | |-- apple
+ * | | |-- iosArm64
+ * | | |-- iosX64
+ * | | |-- macosX64
+ * | | |-- tvosArm64
+ * | | |-- tvosX64
+ * | | |-- watchosArm32
+ * | | |-- watchosArm64
+ * | '-- linux
+ * | |-- linuxX64
+ * | '-- linuxArm64
+ * '-- wasm
+ * '-- wasmJs
+ * '-- wasmWasi
+ * ```
+ *
+ * The `nonJvm` source set excludes that platform.
+ *
+ * The `hashFunctions` source set builds on all platforms. It ships as a main source set on non-JVM
+ * platforms and as a test source set on the JVM platform.
+ */
+kotlin {
+ configureOrCreateOkioPlatforms()
+
+ sourceSets {
+ all {
+ languageSettings.apply {
+ // Required for CPointer etc. since Kotlin 1.9.
+ optIn("kotlinx.cinterop.ExperimentalForeignApi")
+ }
+ }
+
+ val commonMain by getting
+ val commonTest by getting {
+ dependencies {
+ implementation(libs.kotlin.test)
+ implementation(projects.okioTestingSupport)
+ }
+ }
+
+ val hashFunctions by creating {
+ dependsOn(commonMain)
+ }
+
+ val nonAppleMain by creating {
+ dependsOn(hashFunctions)
+ }
+
+ val nonWasmTest by creating {
+ dependencies {
+ implementation(libs.kotlin.time)
+ implementation(projects.okioFakefilesystem)
+ }
+ }
+
+ val nonJvmMain by creating {
+ dependsOn(hashFunctions)
+ dependsOn(commonMain)
+ }
+
+ val nonJvmTest by creating {
+ dependsOn(commonTest)
+ }
+
+ val jvmMain by getting {
+ }
+ val jvmTest by getting {
+ kotlin.srcDir("src/jvmTest/hashFunctions")
+ dependsOn(nonWasmTest)
+ dependencies {
+ implementation(libs.test.junit)
+ implementation(libs.test.assertj)
+ implementation(libs.test.jimfs)
+ }
+ }
+
+ if (kmpJsEnabled) {
+ val jsMain by getting {
+ dependsOn(nonJvmMain)
+ dependsOn(nonAppleMain)
+ }
+ val jsTest by getting {
+ dependsOn(nonWasmTest)
+ dependsOn(nonJvmTest)
+ }
+ }
+
+ if (kmpNativeEnabled) {
+ createSourceSet("nativeMain", parent = nonJvmMain)
+ .also { nativeMain ->
+ createSourceSet("mingwMain", parent = nativeMain, children = mingwTargets).also { mingwMain ->
+ mingwMain.dependsOn(nonAppleMain)
+ }
+ createSourceSet("unixMain", parent = nativeMain)
+ .also { unixMain ->
+ createSourceSet("linuxMain", parent = unixMain, children = linuxTargets).also { linuxMain ->
+ linuxMain.dependsOn(nonAppleMain)
+ }
+ createSourceSet("appleMain", parent = unixMain, children = appleTargets)
+ }
+ }
+
+ createSourceSet("nativeTest", parent = commonTest, children = mingwTargets + linuxTargets)
+ .also { nativeTest ->
+ nativeTest.dependsOn(nonJvmTest)
+ nativeTest.dependsOn(nonWasmTest)
+ createSourceSet("appleTest", parent = nativeTest, children = appleTargets)
+ }
+ }
+
+ if (kmpWasmEnabled) {
+ createSourceSet("wasmMain", parent = commonMain, children = wasmTargets)
+ .also { wasmMain ->
+ wasmMain.dependsOn(nonJvmMain)
+ wasmMain.dependsOn(nonAppleMain)
+ }
+ createSourceSet("wasmTest", parent = commonTest, children = wasmTargets)
+ .also { wasmTest ->
+ wasmTest.dependsOn(nonJvmTest)
+ }
+ }
+ }
+
+ targets.withType<KotlinNativeTargetWithTests<*>> {
+ binaries {
+ // Configure a separate test where code runs in background
+ test("background", setOf(NativeBuildType.DEBUG)) {
+ freeCompilerArgs += "-trw"
+ }
+ }
+ testRuns {
+ val background by creating {
+ setExecutionSourceFrom(binaries.getByName("backgroundDebugTest") as TestExecutable)
+ }
+ }
+ }
+}
+
+tasks {
+ val jvmJar by getting(Jar::class) {
+ // BundleTaskConvention() crashes unless there's a 'main' source set.
+ sourceSets.create(SourceSet.MAIN_SOURCE_SET_NAME)
+ val bndConvention = BundleTaskConvention(this)
+ bndConvention.setBnd(
+ """
+ Export-Package: okio
+ Automatic-Module-Name: okio
+ Bundle-SymbolicName: com.squareup.okio
+ """,
+ )
+ // Call the convention when the task has finished to modify the jar to contain OSGi metadata.
+ doLast {
+ bndConvention.buildBundle()
+ }
+ }
+}
+
+configure<MavenPublishBaseExtension> {
+ configure(
+ KotlinMultiplatform(javadocJar = Dokka("dokkaGfm")),
+ )
+}
+
+plugins.withId("binary-compatibility-validator") {
+ configure<ApiValidationExtension> {
+ ignoredProjects += "jmh"
+ }
+}
diff --git a/okio/gradle.properties b/okio/gradle.properties
deleted file mode 100644
index 775966eb..00000000
--- a/okio/gradle.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-POM_ARTIFACT_ID=okio
-POM_NAME=Okio
diff --git a/okio/jvm/japicmp/build.gradle b/okio/jvm/japicmp/build.gradle
deleted file mode 100644
index 782c6996..00000000
--- a/okio/jvm/japicmp/build.gradle
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2019 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import me.champeau.gradle.japicmp.JapicmpTask
-
-apply plugin: 'java-library'
-apply plugin: 'me.champeau.gradle.japicmp'
-
-configurations {
- baseline
- latest
-}
-
-dependencies {
- baseline('com.squareup.okio:okio:1.14.1') {
- transitive = false
- force = true
- }
- latest project(path: ':okio', configuration: 'jvmRuntimeElements')
-}
-
-task japicmp(type: JapicmpTask, dependsOn: 'jar') {
- oldClasspath = configurations.baseline
- newClasspath = configurations.latest
- onlyBinaryIncompatibleModified = true
- failOnModification = true
- txtOutputFile = file("$buildDir/reports/japi.txt")
- ignoreMissingClasses = true
- includeSynthetic = true
- classExcludes = [
- 'okio.ByteString', // Bytecode version changed from 51.0 to 50.0
- 'okio.RealBufferedSink', // Internal.
- 'okio.RealBufferedSource', // Internal.
- 'okio.SegmentedByteString', // Internal.
- 'okio.SegmentPool', // Internal.
- 'okio.Util', // Internal.
- 'okio.Options', // Bytecode version changed from 51.0 to 50.0
- ]
- methodExcludes = [
- 'okio.ByteString#getByte(int)', // Became 'final' in 1.15.0.
- 'okio.ByteString#size()', // Became 'final' in 1.15.0.
- ]
-}
-check.dependsOn japicmp
diff --git a/okio/jvm/jmh/build.gradle b/okio/jvm/jmh/build.gradle
deleted file mode 100644
index 54eebdd3..00000000
--- a/okio/jvm/jmh/build.gradle
+++ /dev/null
@@ -1,36 +0,0 @@
-import com.github.jengelman.gradle.plugins.shadow.transformers.DontIncludeResourceTransformer
-import com.github.jengelman.gradle.plugins.shadow.transformers.IncludeResourceTransformer
-
-apply plugin: 'java-library'
-apply plugin: 'org.jetbrains.kotlin.jvm'
-apply plugin: 'com.github.johnrengelman.shadow'
-apply plugin: 'me.champeau.gradle.jmh'
-
-jmhJar {
- def excludeAllBenchmarkLists = new DontIncludeResourceTransformer()
- excludeAllBenchmarkLists.resource = "META-INF/BenchmarkList"
- transform(excludeAllBenchmarkLists)
-
- def includeCorrectBenchmarkList = new IncludeResourceTransformer()
- includeCorrectBenchmarkList.resource = "META-INF/BenchmarkList"
- includeCorrectBenchmarkList.file = new File("${project.buildDir}/jmh-generated-resources/META-INF/BenchmarkList")
- transform(includeCorrectBenchmarkList)
-}
-
-jmh {
- jvmArgs = ['-Djmh.separateClasspathJAR=true']
- include = ['com\\.squareup\\.okio\\.benchmarks\\.MessageDigestBenchmark.*']
- duplicateClassesStrategy = 'warn'
-}
-
-dependencies {
- compile project(':okio')
- compile deps.kotlin.stdLib.jdk6
- compile deps.jmh.core
- jmh project(path: ':okio', configuration: 'jvmRuntimeElements')
- jmh deps.kotlin.stdLib.jdk6
- jmh deps.jmh.core
- jmh deps.jmh.generator
-}
-
-assemble.dependsOn(jmhJar)
diff --git a/okio/jvm/jmh/build.gradle.kts b/okio/jvm/jmh/build.gradle.kts
new file mode 100644
index 00000000..0e9c04b7
--- /dev/null
+++ b/okio/jvm/jmh/build.gradle.kts
@@ -0,0 +1,12 @@
+plugins {
+ kotlin("jvm")
+ id("me.champeau.jmh")
+}
+
+jmh {
+}
+
+dependencies {
+ api(projects.okio)
+ api(libs.jmh.core)
+}
diff --git a/okio/jvm/jvm.gradle b/okio/jvm/jvm.gradle
deleted file mode 100644
index cd81891e..00000000
--- a/okio/jvm/jvm.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-apply plugin: 'java-library'
-apply plugin: 'ru.vyarus.animalsniffer'
-
-kotlin.targets.matching { it.platformType.name == 'jvm' }.all { target ->
- target.project.sourceCompatibility = JavaVersion.VERSION_1_7
- target.project.targetCompatibility = JavaVersion.VERSION_1_7
-
- tasks['jvmJar'].configure { t ->
- // the bnd task convention modifies this jar task accordingly
- def bndConvention = bndBundleTaskConventionClass.newInstance(t);
- bndConvention.bnd = project.ext.bndManifest
- // call the convention when the task has finished to modify the jar to contain OSGi metadata
- t.doLast {
- bndConvention.buildBundle()
- }
- }
-
- target.project.animalsniffer {
- sourceSets = [target.project.sourceSets.main]
- }
-
- target.project.dependencies {
- signature 'net.sf.androidscents.signature:android-api-level-15:4.0.3_r5@signature'
- signature 'org.codehaus.mojo.signature:java17:1.0@signature'
- }
-}
diff --git a/okio/src/appleMain/kotlin/okio/ApplePosixVariant.kt b/okio/src/appleMain/kotlin/okio/ApplePosixVariant.kt
new file mode 100644
index 00000000..8840180b
--- /dev/null
+++ b/okio/src/appleMain/kotlin/okio/ApplePosixVariant.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.cinterop.alloc
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.ptr
+import platform.posix.ENOENT
+import platform.posix.S_IFDIR
+import platform.posix.S_IFMT
+import platform.posix.S_IFREG
+import platform.posix.errno
+import platform.posix.lstat
+import platform.posix.stat
+
+internal actual fun PosixFileSystem.variantMetadataOrNull(path: Path): FileMetadata? {
+ return memScoped {
+ val stat = alloc<stat>()
+ if (lstat(path.toString(), stat.ptr) != 0) {
+ if (errno == ENOENT) return null
+ throw errnoToIOException(errno)
+ }
+ return@memScoped FileMetadata(
+ isRegularFile = stat.st_mode.toInt() and S_IFMT == S_IFREG,
+ isDirectory = stat.st_mode.toInt() and S_IFMT == S_IFDIR,
+ symlinkTarget = symlinkTarget(stat, path),
+ size = stat.st_size,
+ createdAtMillis = stat.st_ctimespec.epochMillis,
+ lastModifiedAtMillis = stat.st_mtimespec.epochMillis,
+ lastAccessedAtMillis = stat.st_atimespec.epochMillis,
+ )
+ }
+}
diff --git a/okio/src/appleMain/kotlin/okio/ByteString.kt b/okio/src/appleMain/kotlin/okio/ByteString.kt
index eb141033..7d49ebd6 100644
--- a/okio/src/appleMain/kotlin/okio/ByteString.kt
+++ b/okio/src/appleMain/kotlin/okio/ByteString.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2020 Square, Inc.
+ * Copyright (C) 2018 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,20 +13,211 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package okio
+import kotlin.experimental.ExperimentalNativeApi
+import kotlinx.cinterop.UnsafeNumber
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.usePinned
+import okio.internal.HashFunction
+import okio.internal.Hmac
+import okio.internal.Md5
+import okio.internal.Sha1
+import okio.internal.Sha256
+import okio.internal.Sha512
+import okio.internal.commonBase64
+import okio.internal.commonBase64Url
+import okio.internal.commonCompareTo
+import okio.internal.commonCopyInto
+import okio.internal.commonDecodeBase64
+import okio.internal.commonDecodeHex
+import okio.internal.commonEncodeUtf8
+import okio.internal.commonEndsWith
+import okio.internal.commonEquals
+import okio.internal.commonGetByte
+import okio.internal.commonGetSize
+import okio.internal.commonHashCode
+import okio.internal.commonHex
+import okio.internal.commonIndexOf
+import okio.internal.commonInternalArray
+import okio.internal.commonLastIndexOf
+import okio.internal.commonOf
+import okio.internal.commonRangeEquals
+import okio.internal.commonStartsWith
+import okio.internal.commonSubstring
+import okio.internal.commonToAsciiLowercase
+import okio.internal.commonToAsciiUppercase
+import okio.internal.commonToByteArray
+import okio.internal.commonToByteString
+import okio.internal.commonToString
+import okio.internal.commonUtf8
+import okio.internal.commonWrite
import platform.Foundation.NSData
import platform.posix.memcpy
-fun NSData.toByteString(): ByteString {
- val data = this
- return ByteString(
- ByteArray(data.length.toInt()).apply {
- usePinned { pinned ->
- memcpy(pinned.addressOf(0), data.bytes, data.length)
+actual open class ByteString
+internal actual constructor(
+ internal actual val data: ByteArray,
+) : Comparable<ByteString> {
+ @Suppress("SetterBackingFieldAssignment")
+ internal actual var hashCode: Int = 0 // 0 if unknown.
+ set(value) {
+ // Do nothing to avoid IllegalImmutabilityException.
+ }
+
+ @Suppress("SetterBackingFieldAssignment")
+ internal actual var utf8: String? = null
+ set(value) {
+ // Do nothing to avoid IllegalImmutabilityException.
+ }
+
+ actual open fun utf8(): String = commonUtf8()
+
+ actual open fun base64(): String = commonBase64()
+
+ actual open fun base64Url(): String = commonBase64Url()
+
+ actual open fun hex(): String = commonHex()
+
+ actual fun md5() = digest(Md5())
+
+ actual fun sha1() = digest(Sha1())
+
+ actual fun sha256() = digest(Sha256())
+
+ actual fun sha512() = digest(Sha512())
+
+ /** Returns the 160-bit SHA-1 HMAC of this byte string. */
+ actual fun hmacSha1(key: ByteString) = digest(Hmac.sha1(key))
+
+ /** Returns the 256-bit SHA-256 HMAC of this byte string. */
+ actual fun hmacSha256(key: ByteString) = digest(Hmac.sha256(key))
+
+ /** Returns the 512-bit SHA-512 HMAC of this byte string. */
+ actual fun hmacSha512(key: ByteString) = digest(Hmac.sha512(key))
+
+ internal open fun digest(hashFunction: HashFunction): ByteString {
+ hashFunction.update(data, 0, size)
+ val digestBytes = hashFunction.digest()
+ return ByteString(digestBytes)
+ }
+
+ actual open fun toAsciiLowercase(): ByteString = commonToAsciiLowercase()
+
+ actual open fun toAsciiUppercase(): ByteString = commonToAsciiUppercase()
+
+ actual open fun substring(beginIndex: Int, endIndex: Int): ByteString =
+ commonSubstring(beginIndex, endIndex)
+
+ internal actual open fun internalGet(pos: Int): Byte {
+ if (pos >= size || pos < 0) throw ArrayIndexOutOfBoundsException("size=$size pos=$pos")
+ return commonGetByte(pos)
+ }
+
+ actual operator fun get(index: Int): Byte = internalGet(index)
+
+ actual val size
+ get() = getSize()
+
+ internal actual open fun getSize() = commonGetSize()
+
+ actual open fun toByteArray() = commonToByteArray()
+
+ internal actual open fun internalArray() = commonInternalArray()
+
+ internal actual open fun write(buffer: Buffer, offset: Int, byteCount: Int) =
+ commonWrite(buffer, offset, byteCount)
+
+ actual open fun rangeEquals(
+ offset: Int,
+ other: ByteString,
+ otherOffset: Int,
+ byteCount: Int,
+ ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
+
+ actual open fun rangeEquals(
+ offset: Int,
+ other: ByteArray,
+ otherOffset: Int,
+ byteCount: Int,
+ ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
+
+ actual open fun copyInto(
+ offset: Int,
+ target: ByteArray,
+ targetOffset: Int,
+ byteCount: Int,
+ ) = commonCopyInto(offset, target, targetOffset, byteCount)
+
+ actual fun startsWith(prefix: ByteString) = commonStartsWith(prefix)
+
+ actual fun startsWith(prefix: ByteArray) = commonStartsWith(prefix)
+
+ actual fun endsWith(suffix: ByteString) = commonEndsWith(suffix)
+
+ actual fun endsWith(suffix: ByteArray) = commonEndsWith(suffix)
+
+ actual fun indexOf(other: ByteString, fromIndex: Int) = indexOf(other.internalArray(), fromIndex)
+
+ actual open fun indexOf(other: ByteArray, fromIndex: Int) = commonIndexOf(other, fromIndex)
+
+ actual fun lastIndexOf(other: ByteString, fromIndex: Int) = commonLastIndexOf(other, fromIndex)
+
+ actual open fun lastIndexOf(other: ByteArray, fromIndex: Int) = commonLastIndexOf(other, fromIndex)
+
+ actual override fun equals(other: Any?) = commonEquals(other)
+
+ actual override fun hashCode() = commonHashCode()
+
+ actual override fun compareTo(other: ByteString) = commonCompareTo(other)
+
+ /**
+ * Returns a human-readable string that describes the contents of this byte string. Typically this
+ * is a string like `[text=Hello]` or `[hex=0000ffff]`.
+ */
+ actual override fun toString() = commonToString()
+
+ actual companion object {
+ actual val EMPTY: ByteString = ByteString(byteArrayOf())
+
+ actual fun of(vararg data: Byte) = commonOf(data)
+
+ actual fun ByteArray.toByteString(offset: Int, byteCount: Int): ByteString =
+ commonToByteString(offset, byteCount)
+
+ actual fun String.encodeUtf8(): ByteString = commonEncodeUtf8()
+
+ actual fun String.decodeBase64(): ByteString? = commonDecodeBase64()
+
+ actual fun String.decodeHex() = commonDecodeHex()
+
+ @OptIn(UnsafeNumber::class, ExperimentalNativeApi::class)
+ @CName("of")
+ fun NSData.toByteString(): ByteString {
+ val data = this
+ val size = data.length.toInt()
+ return if (size != 0) {
+ ByteString(
+ ByteArray(size).apply {
+ usePinned { pinned ->
+ memcpy(pinned.addressOf(0), data.bytes, data.length)
+ }
+ },
+ )
+ } else {
+ EMPTY
}
}
- )
+ }
+}
+
+@Deprecated(
+ message = "Moved to ByteString companion object",
+ replaceWith = ReplaceWith("this.toByteString()", "okio.ByteString.Companion.toByteString"),
+)
+fun NSData.toByteString(): ByteString {
+ with(ByteString) {
+ return toByteString()
+ }
}
diff --git a/okio/src/nonJvmMain/kotlin/okio/SegmentedByteString.kt b/okio/src/appleMain/kotlin/okio/SegmentedByteString.kt
index fe718901..485d834e 100644
--- a/okio/src/nonJvmMain/kotlin/okio/SegmentedByteString.kt
+++ b/okio/src/appleMain/kotlin/okio/SegmentedByteString.kt
@@ -16,6 +16,7 @@
package okio
import okio.internal.HashFunction
+import okio.internal.commonCopyInto
import okio.internal.commonEquals
import okio.internal.commonGetSize
import okio.internal.commonHashCode
@@ -28,7 +29,7 @@ import okio.internal.forEachSegment
internal actual class SegmentedByteString internal actual constructor(
internal actual val segments: Array<ByteArray>,
- internal actual val directory: IntArray
+ internal actual val directory: IntArray,
) : ByteString(EMPTY.data) {
override fun base64() = toByteString().base64()
@@ -57,21 +58,28 @@ internal actual class SegmentedByteString internal actual constructor(
offset: Int,
other: ByteString,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
override fun rangeEquals(
offset: Int,
other: ByteArray,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
+ override fun copyInto(
+ offset: Int,
+ target: ByteArray,
+ targetOffset: Int,
+ byteCount: Int,
+ ) = commonCopyInto(offset, target, targetOffset, byteCount)
+
override fun indexOf(other: ByteArray, fromIndex: Int) = toByteString().indexOf(other, fromIndex)
override fun lastIndexOf(other: ByteArray, fromIndex: Int) = toByteString().lastIndexOf(
other,
- fromIndex
+ fromIndex,
)
override fun digest(hashFunction: HashFunction): ByteString {
diff --git a/okio/src/appleTest/kotlin/okio/AppleByteStringTest.kt b/okio/src/appleTest/kotlin/okio/AppleByteStringTest.kt
index 5ff03b21..0e600e06 100644
--- a/okio/src/appleTest/kotlin/okio/AppleByteStringTest.kt
+++ b/okio/src/appleTest/kotlin/okio/AppleByteStringTest.kt
@@ -15,17 +15,33 @@
*/
package okio
+import kotlin.test.Test
+import kotlin.test.assertEquals
import platform.Foundation.NSData
import platform.Foundation.NSString
import platform.Foundation.NSUTF8StringEncoding
import platform.Foundation.dataUsingEncoding
-import kotlin.test.Test
-import kotlin.test.assertEquals
class AppleByteStringTest {
@Test fun nsDataToByteString() {
val data = ("Hello" as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData
- val byteString = data.toByteString()
+
+ @Suppress("DEPRECATION") // Ensure deprecated function continues to work.
+ val byteStringDeprecated = data.toByteString()
+ assertEquals("Hello", byteStringDeprecated.utf8())
+
+ val byteString = with(ByteString) { data.toByteString() }
assertEquals("Hello", byteString.utf8())
}
+
+ @Test fun emptyNsDataToByteString() {
+ val data = ("" as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData
+
+ @Suppress("DEPRECATION") // Ensure deprecated function continues to work.
+ val byteStringDeprecated = data.toByteString()
+ assertEquals(ByteString.EMPTY, byteStringDeprecated)
+
+ val byteString = with(ByteString) { data.toByteString() }
+ assertEquals(ByteString.EMPTY, byteString)
+ }
}
diff --git a/okio/src/commonMain/kotlin/okio/-Base64.kt b/okio/src/commonMain/kotlin/okio/Base64.kt
index 98db0b5a..150793cc 100644
--- a/okio/src/commonMain/kotlin/okio/-Base64.kt
+++ b/okio/src/commonMain/kotlin/okio/Base64.kt
@@ -14,17 +14,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:JvmName("-Base64") // A leading '-' hides this class from Java.
-@file:JvmName("-Base64")
package okio
-import okio.ByteString.Companion.encodeUtf8
import kotlin.jvm.JvmName
+import kotlin.native.concurrent.SharedImmutable
+import okio.ByteString.Companion.encodeUtf8
/** @author Alexander Y. Kleymenov */
+@SharedImmutable
internal val BASE64 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".encodeUtf8().data
+
+@SharedImmutable
internal val BASE64_URL_SAFE =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".encodeUtf8().data
@@ -53,17 +57,17 @@ internal fun String.decodeBase64ToArray(): ByteArray? {
// char ASCII value
// A 65 0
// Z 90 25 (ASCII - 65)
- bits = c.toInt() - 65
+ bits = c.code - 65
} else if (c in 'a'..'z') {
// char ASCII value
// a 97 26
// z 122 51 (ASCII - 71)
- bits = c.toInt() - 71
+ bits = c.code - 71
} else if (c in '0'..'9') {
// char ASCII value
// 0 48 52
// 9 57 61 (ASCII + 4)
- bits = c.toInt() + 4
+ bits = c.code + 4
} else if (c == '+' || c == '-') {
bits = 62
} else if (c == '/' || c == '_') {
@@ -132,8 +136,8 @@ internal fun ByteArray.encodeBase64(map: ByteArray = BASE64): String {
val b0 = this[i].toInt()
out[index++] = map[b0 and 0xff shr 2]
out[index++] = map[b0 and 0x03 shl 4]
- out[index++] = '='.toByte()
- out[index] = '='.toByte()
+ out[index++] = '='.code.toByte()
+ out[index] = '='.code.toByte()
}
2 -> {
val b0 = this[i++].toInt()
@@ -141,7 +145,7 @@ internal fun ByteArray.encodeBase64(map: ByteArray = BASE64): String {
out[index++] = map[(b0 and 0xff shr 2)]
out[index++] = map[(b0 and 0x03 shl 4) or (b1 and 0xff shr 4)]
out[index++] = map[(b1 and 0x0f shl 2)]
- out[index] = '='.toByte()
+ out[index] = '='.code.toByte()
}
}
return out.toUtf8String()
diff --git a/okio/src/commonMain/kotlin/okio/Buffer.kt b/okio/src/commonMain/kotlin/okio/Buffer.kt
index 16394d2c..009ed233 100644
--- a/okio/src/commonMain/kotlin/okio/Buffer.kt
+++ b/okio/src/commonMain/kotlin/okio/Buffer.kt
@@ -46,7 +46,7 @@ expect class Buffer() : BufferedSource, BufferedSink {
fun copyTo(
out: Buffer,
offset: Long = 0L,
- byteCount: Long
+ byteCount: Long,
): Buffer
/**
@@ -55,7 +55,7 @@ expect class Buffer() : BufferedSource, BufferedSink {
*/
fun copyTo(
out: Buffer,
- offset: Long = 0L
+ offset: Long = 0L,
): Buffer
/**
@@ -133,7 +133,10 @@ expect class Buffer() : BufferedSource, BufferedSink {
override fun writeHexadecimalUnsignedLong(v: Long): Buffer
- /** Returns a deep copy of this buffer. */
+ /**
+ * Returns a deep copy of this buffer. The returned [Buffer] initially shares the underlying
+ * [ByteArray]s. See [UnsafeCursor] for more details.
+ */
fun copy(): Buffer
/** Returns an immutable copy of this buffer as a byte string. */
@@ -142,9 +145,9 @@ expect class Buffer() : BufferedSource, BufferedSink {
/** Returns an immutable copy of the first `byteCount` bytes of this buffer as a byte string. */
fun snapshot(byteCount: Int): ByteString
- fun readUnsafe(unsafeCursor: UnsafeCursor = UnsafeCursor()): UnsafeCursor
+ fun readUnsafe(unsafeCursor: UnsafeCursor = DEFAULT__new_UnsafeCursor): UnsafeCursor
- fun readAndWriteUnsafe(unsafeCursor: UnsafeCursor = UnsafeCursor()): UnsafeCursor
+ fun readAndWriteUnsafe(unsafeCursor: UnsafeCursor = DEFAULT__new_UnsafeCursor): UnsafeCursor
/**
* A handle to the underlying data in a buffer. This handle is unsafe because it does not enforce
@@ -240,7 +243,7 @@ expect class Buffer() : BufferedSource, BufferedSink {
* // start = 0 end = 3
* ```
*
- * There is an optimization in `Buffer.clone()` and other methods that allows two segments to
+ * There is an optimization in `Buffer.copy()` and other methods that allows two segments to
* share the same underlying byte array. Clones can't write to the shared byte array; instead they
* allocate a new (private) segment early.
*
@@ -253,7 +256,7 @@ expect class Buffer() : BufferedSource, BufferedSink {
* // ^ ^
* // start = 2 end = 5000
*
- * nana2 = nana.clone()
+ * nana2 = nana.copy()
* nana2.writeUtf8("batman")
*
* // [ 'n', 'a', 'n', 'a', ..., 'n', 'a', 'n', 'a', '?', '?', '?', ...]
@@ -337,14 +340,19 @@ expect class Buffer() : BufferedSource, BufferedSink {
* You can reuse instances of this class if you like. Use the overloads of [Buffer.readUnsafe] and
* [Buffer.readAndWriteUnsafe] that take a cursor and close it after use.
*/
- class UnsafeCursor constructor() {
+ class UnsafeCursor constructor() : Closeable {
@JvmField var buffer: Buffer?
+
@JvmField var readWrite: Boolean
internal var segment: Segment?
+
@JvmField var offset: Long
+
@JvmField var data: ByteArray?
+
@JvmField var start: Int
+
@JvmField var end: Int
/**
@@ -403,6 +411,6 @@ expect class Buffer() : BufferedSource, BufferedSink {
*/
fun expandBuffer(minByteCount: Int): Long
- fun close()
+ override fun close()
}
}
diff --git a/okio/src/commonMain/kotlin/okio/BufferedSink.kt b/okio/src/commonMain/kotlin/okio/BufferedSink.kt
index 40c26585..03c8230a 100644
--- a/okio/src/commonMain/kotlin/okio/BufferedSink.kt
+++ b/okio/src/commonMain/kotlin/okio/BufferedSink.kt
@@ -19,7 +19,7 @@ package okio
* A sink that keeps a buffer internally so that callers can do small writes without a performance
* penalty.
*/
-expect interface BufferedSink : Sink {
+expect sealed interface BufferedSink : Sink {
/** This sink's internal buffer. */
val buffer: Buffer
diff --git a/okio/src/commonMain/kotlin/okio/BufferedSource.kt b/okio/src/commonMain/kotlin/okio/BufferedSource.kt
index 0ba4d152..86b4803a 100644
--- a/okio/src/commonMain/kotlin/okio/BufferedSource.kt
+++ b/okio/src/commonMain/kotlin/okio/BufferedSource.kt
@@ -20,7 +20,7 @@ package okio
* penalty. It also allows clients to read ahead, buffering as much as necessary before consuming
* input.
*/
-expect interface BufferedSource : Source {
+expect sealed interface BufferedSource : Source {
/** This source's internal buffer. */
val buffer: Buffer
diff --git a/okio/src/commonMain/kotlin/okio/ByteString.kt b/okio/src/commonMain/kotlin/okio/ByteString.kt
index 7eb34c6a..3f4fb82f 100644
--- a/okio/src/commonMain/kotlin/okio/ByteString.kt
+++ b/okio/src/commonMain/kotlin/okio/ByteString.kt
@@ -42,7 +42,7 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
internal var hashCode: Int
internal var utf8: String?
- /** Constructs a new `String` by decoding the bytes as `UTF-8`. */
+ /** Constructs a new `String` by decoding the bytes as `UTF-8`. */
fun utf8(): String
/**
@@ -54,29 +54,38 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
/** Returns this byte string encoded as [URL-safe Base64](http://www.ietf.org/rfc/rfc4648.txt). */
fun base64Url(): String
- /** Returns this byte string encoded in hexadecimal. */
+ /** Returns this byte string encoded in hexadecimal. */
fun hex(): String
- /** Returns the 128-bit MD5 hash of this byte string. */
+ /**
+ * Returns the 128-bit MD5 hash of this byte string.
+ *
+ * MD5 has been vulnerable to collisions since 2004. It should not be used in new code.
+ */
fun md5(): ByteString
- /** Returns the 160-bit SHA-1 hash of this byte string. */
+ /**
+ * Returns the 160-bit SHA-1 hash of this byte string.
+ *
+ * SHA-1 has been vulnerable to collisions since 2017. It should not be used in new code.
+ */
fun sha1(): ByteString
- /** Returns the 256-bit SHA-256 hash of this byte string. */
+ /** Returns the 256-bit SHA-256 hash of this byte string. */
fun sha256(): ByteString
- /** Returns the 512-bit SHA-512 hash of this byte string. */
+ /** Returns the 512-bit SHA-512 hash of this byte string. */
fun sha512(): ByteString
- /** Returns the 160-bit SHA-1 HMAC of this byte string. */
+ /** Returns the 160-bit SHA-1 HMAC of this byte string. */
fun hmacSha1(key: ByteString): ByteString
- /** Returns the 256-bit SHA-256 HMAC of this byte string. */
+ /** Returns the 256-bit SHA-256 HMAC of this byte string. */
fun hmacSha256(key: ByteString): ByteString
- /** Returns the 512-bit SHA-512 HMAC of this byte string. */
+ /** Returns the 512-bit SHA-512 HMAC of this byte string. */
fun hmacSha512(key: ByteString): ByteString
+
/**
* Returns a byte string equal to this byte string, but with the bytes 'A' through 'Z' replaced
* with the corresponding byte in 'a' through 'z'. Returns this byte string if it contains no
@@ -89,7 +98,7 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
* `beginIndex` and ends at the specified `endIndex`. Returns this byte string if `beginIndex` is
* 0 and `endIndex` is the length of this byte string.
*/
- fun substring(beginIndex: Int = 0, endIndex: Int = size): ByteString
+ fun substring(beginIndex: Int = 0, endIndex: Int = DEFAULT__ByteString_size): ByteString
/**
* Returns a byte string equal to this byte string, but with the bytes 'a' through 'z' replaced
@@ -98,16 +107,17 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
*/
fun toAsciiUppercase(): ByteString
- /** Returns the byte at `pos`. */
+ /** Returns the byte at `pos`. */
internal fun internalGet(pos: Int): Byte
- /** Returns the byte at `index`. */
+ /** Returns the byte at `index`. */
@JvmName("getByte")
operator fun get(index: Int): Byte
/** Returns the number of bytes in this ByteString. */
val size: Int
- @JvmName("size") get
+ @JvmName("size")
+ get
// Hack to work around Kotlin's limitation for using JvmName on open/override vals/funs
internal fun getSize(): Int
@@ -115,7 +125,7 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
/** Returns a byte array containing a copy of the bytes in this `ByteString`. */
fun toByteArray(): ByteArray
- /** Writes the contents of this byte string to `buffer`. */
+ /** Writes the contents of this byte string to `buffer`. */
internal fun write(buffer: Buffer, offset: Int, byteCount: Int)
/** Returns the bytes of this string without a defensive copy. Do not mutate! */
@@ -133,6 +143,14 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
*/
fun rangeEquals(offset: Int, other: ByteArray, otherOffset: Int, byteCount: Int): Boolean
+ /**
+ * Copies bytes of this in `[offset..offset+byteCount]` to other in
+ * `[targetOffset..targetOffset+byteCount]`.
+ *
+ * @throws IndexOutOfBoundsException if either range is out of bounds.
+ */
+ fun copyInto(offset: Int = 0, target: ByteArray, targetOffset: Int = 0, byteCount: Int)
+
fun startsWith(prefix: ByteString): Boolean
fun startsWith(prefix: ByteArray): Boolean
@@ -147,9 +165,9 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
@JvmOverloads
fun indexOf(other: ByteArray, fromIndex: Int = 0): Int
- fun lastIndexOf(other: ByteString, fromIndex: Int = size): Int
+ fun lastIndexOf(other: ByteString, fromIndex: Int = DEFAULT__ByteString_size): Int
- fun lastIndexOf(other: ByteArray, fromIndex: Int = size): Int
+ fun lastIndexOf(other: ByteArray, fromIndex: Int = DEFAULT__ByteString_size): Int
override fun equals(other: Any?): Boolean
@@ -164,7 +182,7 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
override fun toString(): String
companion object {
- /** A singleton empty `ByteString`. */
+ /** A singleton empty `ByteString`. */
@JvmField
val EMPTY: ByteString
@@ -177,9 +195,9 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
* starting at `offset`.
*/
@JvmStatic
- fun ByteArray.toByteString(offset: Int = 0, byteCount: Int = size): ByteString
+ fun ByteArray.toByteString(offset: Int = 0, byteCount: Int = DEFAULT__ByteString_size): ByteString
- /** Returns a new byte string containing the `UTF-8` bytes of this [String]. */
+ /** Returns a new byte string containing the `UTF-8` bytes of this [String]. */
@JvmStatic
fun String.encodeUtf8(): ByteString
@@ -190,7 +208,7 @@ internal constructor(data: ByteArray) : Comparable<ByteString> {
@JvmStatic
fun String.decodeBase64(): ByteString?
- /** Decodes the hex-encoded bytes and returns their value a byte string. */
+ /** Decodes the hex-encoded bytes and returns their value a byte string. */
@JvmStatic
fun String.decodeHex(): ByteString
}
diff --git a/okio/src/commonMain/kotlin/okio/-Platform.kt b/okio/src/commonMain/kotlin/okio/CommonPlatform.kt
index 4790d3cd..e073d0b3 100644
--- a/okio/src/commonMain/kotlin/okio/-Platform.kt
+++ b/okio/src/commonMain/kotlin/okio/CommonPlatform.kt
@@ -13,9 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:JvmName("-CommonPlatform") // A leading '-' hides this class from Java.
package okio
+import kotlin.jvm.JvmName
+
internal expect fun ByteArray.toUtf8String(): String
internal expect fun String.asUtf8ToByteArray(): ByteArray
@@ -23,15 +26,24 @@ internal expect fun String.asUtf8ToByteArray(): ByteArray
// TODO make internal https://youtrack.jetbrains.com/issue/KT-37316
expect class ArrayIndexOutOfBoundsException(message: String?) : IndexOutOfBoundsException
-internal expect inline fun <R> synchronized(lock: Any, block: () -> R): R
+expect class Lock
+
+expect inline fun <T> Lock.withLock(action: () -> T): T
+
+internal expect fun newLock(): Lock
expect open class IOException(message: String?, cause: Throwable?) : Exception {
- constructor(message: String? = null)
+ constructor(message: String?)
+ constructor()
}
-expect open class EOFException(message: String? = null) : IOException
+expect class ProtocolException(message: String) : IOException
+
+expect open class EOFException(message: String?) : IOException {
+ constructor()
+}
-expect class FileNotFoundException(message: String? = null) : IOException
+expect class FileNotFoundException(message: String?) : IOException
expect interface Closeable {
/**
diff --git a/okio/src/commonMain/kotlin/okio/ExperimentalFileSystem.kt b/okio/src/commonMain/kotlin/okio/ExperimentalFileSystem.kt
index 7fc3a4a2..c5e182eb 100644
--- a/okio/src/commonMain/kotlin/okio/ExperimentalFileSystem.kt
+++ b/okio/src/commonMain/kotlin/okio/ExperimentalFileSystem.kt
@@ -15,13 +15,16 @@
*/
package okio
-import kotlin.RequiresOptIn.Level.ERROR
-import kotlin.annotation.AnnotationRetention.BINARY
+import kotlin.DeprecationLevel.HIDDEN
+import kotlin.annotation.AnnotationRetention.SOURCE
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.PROPERTY
-@RequiresOptIn(level = ERROR, message = "okio's FileSystem is unstable and subject to change")
-@Retention(BINARY)
+@Deprecated(
+ message = "This annotation is obsolete and should be removed.",
+ level = HIDDEN,
+)
+@Retention(SOURCE)
@Target(CLASS, FUNCTION, PROPERTY)
annotation class ExperimentalFileSystem
diff --git a/okio/src/commonMain/kotlin/okio/FileHandle.kt b/okio/src/commonMain/kotlin/okio/FileHandle.kt
new file mode 100644
index 00000000..d05c3c99
--- /dev/null
+++ b/okio/src/commonMain/kotlin/okio/FileHandle.kt
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+/**
+ * An open file for reading and writing; using either streaming and random access.
+ *
+ * Use [read] and [write] to perform one-off random-access reads and writes. Use [source], [sink],
+ * and [appendingSink] for streaming reads and writes.
+ *
+ * File handles must be closed when they are no longer needed. It is an error to read, write, or
+ * create streams after a file handle is closed. The operating system resources held by a file
+ * handle will be released once the file handle **and** all of its streams are closed.
+ *
+ * Although this class offers both reading and writing APIs, file handle instances may be
+ * read-only or write-only. For example, a handle to a file on a read-only file system will throw an
+ * exception if a write is attempted.
+ *
+ * File handles may be used by multiple threads concurrently. But the individual sources and sinks
+ * produced by a file handle are not safe for concurrent use.
+ */
+abstract class FileHandle(
+ /**
+ * True if this handle supports both reading and writing. If this is false all write operations
+ * including [write], [sink], [resize], and [flush] will all throw [IllegalStateException] if
+ * called.
+ */
+ val readWrite: Boolean,
+) : Closeable {
+ /**
+ * True once the file handle is closed. Resources should be released with [protectedClose] once
+ * this is true and [openStreamCount] is 0.
+ */
+ private var closed = false
+
+ /**
+ * Reference count of the number of open sources and sinks on this file handle. Resources should
+ * be released with [protectedClose] once this is 0 and [closed] is true.
+ */
+ private var openStreamCount = 0
+
+ val lock: Lock = newLock()
+
+ /**
+ * Reads at least 1, and up to [byteCount] bytes from this starting at [fileOffset] and copies
+ * them to [array] at [arrayOffset]. Returns the number of bytes read, or -1 if [fileOffset]
+ * equals [size].
+ */
+ @Throws(IOException::class)
+ fun read(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ): Int {
+ lock.withLock {
+ check(!closed) { "closed" }
+ }
+ return protectedRead(fileOffset, array, arrayOffset, byteCount)
+ }
+
+ /**
+ * Reads at least 1, and up to [byteCount] bytes from this starting at [fileOffset] and appends
+ * them to [sink]. Returns the number of bytes read, or -1 if [fileOffset] equals [size].
+ */
+ @Throws(IOException::class)
+ fun read(fileOffset: Long, sink: Buffer, byteCount: Long): Long {
+ lock.withLock {
+ check(!closed) { "closed" }
+ }
+ return readNoCloseCheck(fileOffset, sink, byteCount)
+ }
+
+ /**
+ * Returns the total number of bytes in the file. This will change if the file size changes.
+ */
+ @Throws(IOException::class)
+ fun size(): Long {
+ lock.withLock {
+ check(!closed) { "closed" }
+ }
+ return protectedSize()
+ }
+
+ /**
+ * Changes the number of bytes in this file to [size]. This will remove bytes from the end if the
+ * new size is smaller. It will add `0` bytes to the end if it is larger.
+ */
+ @Throws(IOException::class)
+ fun resize(size: Long) {
+ check(readWrite) { "file handle is read-only" }
+ lock.withLock {
+ check(!closed) { "closed" }
+ }
+ return protectedResize(size)
+ }
+
+ /** Reads [byteCount] bytes from [array] and writes them to this at [fileOffset]. */
+ fun write(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ) {
+ check(readWrite) { "file handle is read-only" }
+ lock.withLock {
+ check(!closed) { "closed" }
+ }
+ return protectedWrite(fileOffset, array, arrayOffset, byteCount)
+ }
+
+ /** Removes [byteCount] bytes from [source] and writes them to this at [fileOffset]. */
+ @Throws(IOException::class)
+ fun write(fileOffset: Long, source: Buffer, byteCount: Long) {
+ check(readWrite) { "file handle is read-only" }
+ lock.withLock {
+ check(!closed) { "closed" }
+ }
+ writeNoCloseCheck(fileOffset, source, byteCount)
+ }
+
+ /** Pushes all buffered bytes to their final destination. */
+ @Throws(IOException::class)
+ fun flush() {
+ check(readWrite) { "file handle is read-only" }
+ lock.withLock {
+ check(!closed) { "closed" }
+ }
+ return protectedFlush()
+ }
+
+ /**
+ * Returns a source that reads from this starting at [fileOffset]. The returned source must be
+ * closed when it is no longer needed.
+ */
+ @Throws(IOException::class)
+ fun source(fileOffset: Long = 0L): Source {
+ lock.withLock {
+ check(!closed) { "closed" }
+ openStreamCount++
+ }
+ return FileHandleSource(this, fileOffset)
+ }
+
+ /**
+ * Returns the position of [source] in the file. The argument [source] must be either a source
+ * produced by this file handle, or a [BufferedSource] that directly wraps such a source. If the
+ * parameter is a [BufferedSource], it adjusts for buffered bytes.
+ */
+ @Throws(IOException::class)
+ fun position(source: Source): Long {
+ var source = source
+ var bufferSize = 0L
+
+ if (source is RealBufferedSource) {
+ bufferSize = source.buffer.size
+ source = source.source
+ }
+
+ require(source is FileHandleSource && source.fileHandle === this) {
+ "source was not created by this FileHandle"
+ }
+ check(!source.closed) { "closed" }
+
+ return source.position - bufferSize
+ }
+
+ /**
+ * Change the position of [source] in the file to [position]. The argument [source] must be either
+ * a source produced by this file handle, or a [BufferedSource] that directly wraps such a source.
+ * If the parameter is a [BufferedSource], it will skip or clear buffered bytes.
+ */
+ @Throws(IOException::class)
+ fun reposition(source: Source, position: Long) {
+ if (source is RealBufferedSource) {
+ val fileHandleSource = source.source
+ require(fileHandleSource is FileHandleSource && fileHandleSource.fileHandle === this) {
+ "source was not created by this FileHandle"
+ }
+ check(!fileHandleSource.closed) { "closed" }
+
+ val bufferSize = source.buffer.size
+ val toSkip = position - (fileHandleSource.position - bufferSize)
+ if (toSkip in 0L until bufferSize) {
+ // The new position requires only a buffer change.
+ source.skip(toSkip)
+ } else {
+ // The new position doesn't share data with the current buffer.
+ source.buffer.clear()
+ fileHandleSource.position = position
+ }
+ } else {
+ require(source is FileHandleSource && source.fileHandle === this) {
+ "source was not created by this FileHandle"
+ }
+ check(!source.closed) { "closed" }
+ source.position = position
+ }
+ }
+
+ /**
+ * Returns a sink that writes to this starting at [fileOffset]. The returned sink must be closed
+ * when it is no longer needed.
+ */
+ @Throws(IOException::class)
+ fun sink(fileOffset: Long = 0L): Sink {
+ check(readWrite) { "file handle is read-only" }
+ lock.withLock {
+ check(!closed) { "closed" }
+ openStreamCount++
+ }
+ return FileHandleSink(this, fileOffset)
+ }
+
+ /**
+ * Returns a sink that writes to this starting at the end. The returned sink must be closed when
+ * it is no longer needed.
+ */
+ @Throws(IOException::class)
+ fun appendingSink(): Sink {
+ return sink(size())
+ }
+
+ /**
+ * Returns the position of [sink] in the file. The argument [sink] must be either a sink produced
+ * by this file handle, or a [BufferedSink] that directly wraps such a sink. If the parameter is a
+ * [BufferedSink], it adjusts for buffered bytes.
+ */
+ @Throws(IOException::class)
+ fun position(sink: Sink): Long {
+ var sink = sink
+ var bufferSize = 0L
+
+ if (sink is RealBufferedSink) {
+ bufferSize = sink.buffer.size
+ sink = sink.sink
+ }
+
+ require(sink is FileHandleSink && sink.fileHandle === this) {
+ "sink was not created by this FileHandle"
+ }
+ check(!sink.closed) { "closed" }
+
+ return sink.position + bufferSize
+ }
+
+ /**
+ * Change the position of [sink] in the file to [position]. The argument [sink] must be either a
+ * sink produced by this file handle, or a [BufferedSink] that directly wraps such a sink. If the
+ * parameter is a [BufferedSink], it emits for buffered bytes.
+ */
+ @Throws(IOException::class)
+ fun reposition(sink: Sink, position: Long) {
+ if (sink is RealBufferedSink) {
+ val fileHandleSink = sink.sink
+ require(fileHandleSink is FileHandleSink && fileHandleSink.fileHandle === this) {
+ "sink was not created by this FileHandle"
+ }
+ check(!fileHandleSink.closed) { "closed" }
+
+ sink.emit()
+ fileHandleSink.position = position
+ } else {
+ require(sink is FileHandleSink && sink.fileHandle === this) {
+ "sink was not created by this FileHandle"
+ }
+ check(!sink.closed) { "closed" }
+ sink.position = position
+ }
+ }
+
+ @Throws(IOException::class)
+ final override fun close() {
+ lock.withLock {
+ if (closed) return
+ closed = true
+ if (openStreamCount != 0) return
+ }
+ protectedClose()
+ }
+
+ /** Like [read] but not performing any close check. */
+ @Throws(IOException::class)
+ protected abstract fun protectedRead(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ): Int
+
+ /** Like [write] but not performing any close check. */
+ @Throws(IOException::class)
+ protected abstract fun protectedWrite(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ )
+
+ /** Like [flush] but not performing any close check. */
+ @Throws(IOException::class)
+ protected abstract fun protectedFlush()
+
+ /** Like [resize] but not performing any close check. */
+ @Throws(IOException::class)
+ protected abstract fun protectedResize(size: Long)
+
+ /** Like [size] but not performing any close check. */
+ @Throws(IOException::class)
+ protected abstract fun protectedSize(): Long
+
+ /**
+ * Subclasses should implement this to release resources held by this file handle. It is invoked
+ * once both the file handle is closed, and also all sources and sinks produced by it are also
+ * closed.
+ */
+ @Throws(IOException::class)
+ protected abstract fun protectedClose()
+
+ private fun readNoCloseCheck(fileOffset: Long, sink: Buffer, byteCount: Long): Long {
+ require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
+
+ var currentOffset = fileOffset
+ val targetOffset = fileOffset + byteCount
+
+ while (currentOffset < targetOffset) {
+ val tail = sink.writableSegment(1)
+ val readByteCount = protectedRead(
+ fileOffset = currentOffset,
+ array = tail.data,
+ arrayOffset = tail.limit,
+ byteCount = minOf(targetOffset - currentOffset, Segment.SIZE - tail.limit).toInt(),
+ )
+
+ if (readByteCount == -1) {
+ if (tail.pos == tail.limit) {
+ // We allocated a tail segment, but didn't end up needing it. Recycle!
+ sink.head = tail.pop()
+ SegmentPool.recycle(tail)
+ }
+ if (fileOffset == currentOffset) return -1L // We wanted bytes but didn't return any.
+ break
+ }
+
+ tail.limit += readByteCount
+ currentOffset += readByteCount
+ sink.size += readByteCount
+ }
+
+ return currentOffset - fileOffset
+ }
+
+ private fun writeNoCloseCheck(fileOffset: Long, source: Buffer, byteCount: Long) {
+ checkOffsetAndCount(source.size, 0L, byteCount)
+
+ var currentOffset = fileOffset
+ val targetOffset = fileOffset + byteCount
+
+ while (currentOffset < targetOffset) {
+ val head = source.head!!
+ val toCopy = minOf(targetOffset - currentOffset, head.limit - head.pos).toInt()
+ protectedWrite(currentOffset, head.data, head.pos, toCopy)
+
+ head.pos += toCopy
+ currentOffset += toCopy
+ source.size -= toCopy
+
+ if (head.pos == head.limit) {
+ source.head = head.pop()
+ SegmentPool.recycle(head)
+ }
+ }
+ }
+
+ private class FileHandleSink(
+ val fileHandle: FileHandle,
+ var position: Long,
+ ) : Sink {
+ var closed = false
+
+ override fun write(source: Buffer, byteCount: Long) {
+ check(!closed) { "closed" }
+ fileHandle.writeNoCloseCheck(position, source, byteCount)
+ position += byteCount
+ }
+
+ override fun flush() {
+ check(!closed) { "closed" }
+ fileHandle.protectedFlush()
+ }
+
+ override fun timeout() = Timeout.NONE
+
+ override fun close() {
+ if (closed) return
+ closed = true
+ fileHandle.lock.withLock {
+ fileHandle.openStreamCount--
+ if (fileHandle.openStreamCount != 0 || !fileHandle.closed) return@close
+ }
+ fileHandle.protectedClose()
+ }
+ }
+
+ private class FileHandleSource(
+ val fileHandle: FileHandle,
+ var position: Long,
+ ) : Source {
+ var closed = false
+
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ check(!closed) { "closed" }
+ val result = fileHandle.readNoCloseCheck(position, sink, byteCount)
+ if (result != -1L) position += result
+ return result
+ }
+
+ override fun timeout() = Timeout.NONE
+
+ override fun close() {
+ if (closed) return
+ closed = true
+ fileHandle.lock.withLock {
+ fileHandle.openStreamCount--
+ if (fileHandle.openStreamCount != 0 || !fileHandle.closed) return@close
+ }
+ fileHandle.protectedClose()
+ }
+ }
+}
diff --git a/okio/src/commonMain/kotlin/okio/FileMetadata.kt b/okio/src/commonMain/kotlin/okio/FileMetadata.kt
new file mode 100644
index 00000000..ede70e6f
--- /dev/null
+++ b/okio/src/commonMain/kotlin/okio/FileMetadata.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.reflect.KClass
+import kotlin.reflect.cast
+
+/**
+ * Description of a file or another object referenced by a path.
+ *
+ * In simple use a file system is a mechanism for organizing files and directories on a local
+ * storage device. In practice file systems are more capable and their contents more varied. For
+ * example, a path may refer to:
+ *
+ * * An operating system process that consumes data, produces data, or both. For example, reading
+ * from the `/dev/urandom` file on Linux returns a unique sequence of pseudorandom bytes to each
+ * reader.
+ *
+ * * A stream that connects a pair of programs together. A pipe is a special file that a producing
+ * program writes to and a consuming program reads from. Both programs operate concurrently. The
+ * size of a pipe is not well defined: the writer can write as much data as the reader is able to
+ * read.
+ *
+ * * A file on a remote file system. The performance and availability of remote files may be quite
+ * different from that of local files!
+ *
+ * * A symbolic link (symlink) to another path. When attempting to access this path the file system
+ * will follow the link and return data from the target path.
+ *
+ * * The same content as another path without a symlink. On UNIX file systems an inode is an
+ * anonymous handle to a file's content, and multiple paths may target the same inode without any
+ * other relationship to one another. A consequence of this design is that a directory with three
+ * 1 GiB files may only need 1 GiB on the storage device.
+ *
+ * This class does not attempt to model these rich file system features! It exposes a limited view
+ * useful for programs with only basic file system needs. Be cautious of the potential consequences
+ * of special files when writing programs that operate on a file system.
+ *
+ * File metadata is subject to change, and code that operates on file systems should defend against
+ * changes to the file that occur between reading metadata and subsequent operations.
+ */
+class FileMetadata(
+ /** True if this file is a container of bytes. If this is true, then [size] is non-null. */
+ val isRegularFile: Boolean = false,
+
+ /**
+ * True if the path refers to a directory that contains 0 or more child paths.
+ *
+ * Note that a path does not need to be a directory for [FileSystem.list] to return successfully.
+ * For example, mounted storage devices may have child files, but do not identify themselves as
+ * directories.
+ */
+ val isDirectory: Boolean = false,
+
+ /**
+ * The absolute or relative path that this file is a symlink to, or null if this is not a symlink.
+ * If this is a relative path, it is relative to the source file's parent directory.
+ */
+ val symlinkTarget: Path? = null,
+
+ /**
+ * The number of bytes readable from this file. The amount of storage resources consumed by this
+ * file may be larger (due to block size overhead, redundant copies for RAID, etc.), or smaller
+ * (due to file system compression, shared inodes, etc).
+ */
+ val size: Long? = null,
+
+ /**
+ * The system time of the host computer when this file was created, if the host file system
+ * supports this feature. This is typically available on Windows NTFS file systems and not
+ * available on UNIX or Windows FAT file systems.
+ */
+ val createdAtMillis: Long? = null,
+
+ /**
+ * The system time of the host computer when this file was most recently written.
+ *
+ * Note that the accuracy of the returned time may be much more coarse than its precision. In
+ * particular, this value is expressed with millisecond precision but may be accessed at
+ * second- or day-accuracy only.
+ */
+ val lastModifiedAtMillis: Long? = null,
+
+ /**
+ * The system time of the host computer when this file was most recently read or written.
+ *
+ * Note that the accuracy of the returned time may be much more coarse than its precision. In
+ * particular, this value is expressed with millisecond precision but may be accessed at
+ * second- or day-accuracy only.
+ */
+ val lastAccessedAtMillis: Long? = null,
+
+ extras: Map<KClass<*>, Any> = mapOf(),
+) {
+ /**
+ * Additional file system-specific metadata organized by the class of that metadata. File systems
+ * may use this to include information like permissions, content-type, or linked applications.
+ *
+ * Values in this map should be instances of immutable classes. Keys should be the types of those
+ * classes.
+ */
+ val extras: Map<KClass<*>, Any> = extras.toMap()
+
+ /** Returns extra metadata of type [type], or null if no such metadata is held. */
+ fun <T : Any> extra(type: KClass<out T>): T? {
+ val value = extras[type] ?: return null
+ return type.cast(value)
+ }
+
+ fun copy(
+ isRegularFile: Boolean = this.isRegularFile,
+ isDirectory: Boolean = this.isDirectory,
+ symlinkTarget: Path? = this.symlinkTarget,
+ size: Long? = this.size,
+ createdAtMillis: Long? = this.createdAtMillis,
+ lastModifiedAtMillis: Long? = this.lastModifiedAtMillis,
+ lastAccessedAtMillis: Long? = this.lastAccessedAtMillis,
+ extras: Map<KClass<*>, Any> = this.extras,
+ ): FileMetadata {
+ return FileMetadata(
+ isRegularFile = isRegularFile,
+ isDirectory = isDirectory,
+ symlinkTarget = symlinkTarget,
+ size = size,
+ createdAtMillis = createdAtMillis,
+ lastAccessedAtMillis = lastAccessedAtMillis,
+ lastModifiedAtMillis = lastModifiedAtMillis,
+ extras = extras,
+ )
+ }
+
+ override fun toString(): String {
+ val fields = mutableListOf<String>()
+ if (isRegularFile) fields += "isRegularFile"
+ if (isDirectory) fields += "isDirectory"
+ if (size != null) fields += "byteCount=$size"
+ if (createdAtMillis != null) fields += "createdAt=$createdAtMillis"
+ if (lastModifiedAtMillis != null) fields += "lastModifiedAt=$lastModifiedAtMillis"
+ if (lastAccessedAtMillis != null) fields += "lastAccessedAt=$lastAccessedAtMillis"
+ if (extras.isNotEmpty()) fields += "extras=$extras"
+ return fields.joinToString(separator = ", ", prefix = "FileMetadata(", postfix = ")")
+ }
+}
diff --git a/okio/src/commonMain/kotlin/okio/FileSystem.kt b/okio/src/commonMain/kotlin/okio/FileSystem.kt
new file mode 100644
index 00000000..9535880b
--- /dev/null
+++ b/okio/src/commonMain/kotlin/okio/FileSystem.kt
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+/**
+ * Read and write access to a hierarchical collection of files, addressed by [paths][Path]. This
+ * is a natural interface to the current computer's local file system.
+ *
+ * Other implementations are possible:
+ *
+ * * `FakeFileSystem` is an in-memory file system suitable for testing. Note that this class is
+ * included in the `okio-fakefilesystem` artifact.
+ *
+ * * [ForwardingFileSystem] is a file system decorator. Use it to apply monitoring, encryption,
+ * compression, or filtering to another file system.
+ *
+ * * A ZIP file system could provide access to the contents of a `.zip` file.
+ *
+ * For improved capability and testability, consider structuring your classes to dependency inject
+ * a `FileSystem` rather than using `FileSystem.SYSTEM` directly.
+ *
+ * Small API
+ * ---------
+ *
+ * This interface is deliberately limited in which features it supports.
+ *
+ * It is not suitable for high-latency or unreliable remote file systems. It lacks support for
+ * retries, timeouts, cancellation, and bulk operations.
+ *
+ * It cannot create special file types like hard links, pipes, or mounts. Reading or writing these
+ * files works as if they were regular files.
+ *
+ * It cannot read or write file access control features like the UNIX `chmod` and Windows access
+ * control lists. It does honor these controls and will fail with an [IOException] if privileges
+ * are insufficient!
+ *
+ * It cannot lock files or check which files are locked.
+ *
+ * It cannot watch the file system for changes.
+ *
+ * Applications that need rich file system features should use another API!
+ *
+ * Multiplatform
+ * -------------
+ *
+ * This class supports a matrix of Kotlin platforms (JVM, Kotlin/Native, Kotlin/JS) and operating
+ * systems (Linux, macOS, and Windows). It attempts to balance working similarly across platforms
+ * with being consistent with the local operating system.
+ *
+ * This is a blocking API which limits its applicability on concurrent Node.js services. File
+ * operations will block the event loop (and all JavaScript execution!) until they complete.
+ *
+ * It supports the path schemes of both Windows (like `C:\Users`) and UNIX (like `/home`). Note that
+ * path resolution rules differ by platform.
+ *
+ * Differences vs. Java IO APIs
+ * ----------------------------
+ *
+ * The `java.io.File` class is Java's original file system API. The `delete` and `renameTo` methods
+ * return false if the operation failed. The `list` method returns null if the file isn't a
+ * directory or could not be listed. This class always throws an [IOException] when an operation
+ * doesn't succeed.
+ *
+ * The `java.nio.file.Path` and `java.nio.file.Files` classes are the entry points of Java's new file system
+ * API. Each `Path` instance is scoped to a particular file system, though that is often implicit
+ * because the `Paths.get()` function automatically uses the default (ie. system) file system.
+ * In Okio's API paths are just identifiers; you must use a specific `FileSystem` object to do
+ * I/O with.
+ */
+expect abstract class FileSystem() {
+
+ /**
+ * Resolves [path] against the current working directory and symlinks in this file system. The
+ * returned path identifies the same file as [path], but with an absolute path that does not
+ * include any symbolic links.
+ *
+ * This is similar to `File.getCanonicalFile()` on the JVM and `realpath` on POSIX. Unlike
+ * `File.getCanonicalFile()`, this throws if the file doesn't exist.
+ *
+ * @throws IOException if [path] cannot be resolved. This will occur if the file doesn't exist,
+ * if the current working directory doesn't exist or is inaccessible, or if another failure
+ * occurs while resolving the path.
+ */
+ @Throws(IOException::class)
+ abstract fun canonicalize(path: Path): Path
+
+ /**
+ * Returns metadata of the file, directory, or object identified by [path].
+ *
+ * @throws IOException if [path] does not exist or its metadata cannot be read.
+ */
+ @Throws(IOException::class)
+ fun metadata(path: Path): FileMetadata
+
+ /**
+ * Returns metadata of the file, directory, or object identified by [path]. This returns null if
+ * there is no file at [path].
+ *
+ * @throws IOException if [path] cannot be accessed due to a connectivity problem, permissions
+ * problem, or other issue.
+ */
+ @Throws(IOException::class)
+ abstract fun metadataOrNull(path: Path): FileMetadata?
+
+ /**
+ * Returns true if [path] identifies an object on this file system.
+ *
+ * @throws IOException if [path] cannot be accessed due to a connectivity problem, permissions
+ * problem, or other issue.
+ */
+ @Throws(IOException::class)
+ fun exists(path: Path): Boolean
+
+ /**
+ * Returns the children of [dir]. The returned list is sorted using natural ordering. If [dir] is
+ * a relative path, the returned elements will also be relative paths. If it is an absolute path,
+ * the returned elements will also be absolute paths.
+ *
+ * Note that a path does not need to be a [directory][FileMetadata.isDirectory] for this function
+ * to return successfully. For example, mounted storage devices may have child files but do not
+ * identify themselves as directories.
+ *
+ * @throws IOException if [dir] does not exist or cannot be listed. A path cannot be listed if the
+ * current process doesn't have access to [dir], or if there's a loop of symbolic links, or if
+ * any name is too long.
+ */
+ @Throws(IOException::class)
+ abstract fun list(dir: Path): List<Path>
+
+ /**
+ * Returns the children of the directory identified by [dir]. The returned list is sorted using
+ * natural ordering. If [dir] is a relative path, the returned elements will also be relative
+ * paths. If it is an absolute path, the returned elements will also be absolute paths.
+ *
+ * This returns null if [dir] does not exist or cannot be listed. A directory cannot be listed if
+ * the current process doesn't have access to [dir], or if there's a loop of symbolic links, or if
+ * any name is too long.
+ */
+ abstract fun listOrNull(dir: Path): List<Path>?
+
+ /**
+ * Returns a sequence that **lazily** traverses the children of [dir] using repeated calls to
+ * [list]. If none of [dir]'s children are directories this returns the same elements as [list].
+ *
+ * The returned sequence visits the tree of files in depth-first order. Parent paths are returned
+ * before their children.
+ *
+ * Note that [listRecursively] does not throw exceptions but the returned sequence does. When it
+ * is iterated, the returned sequence throws a [FileNotFoundException] if [dir] does not exist, or
+ * an [IOException] if [dir] cannot be listed.
+ *
+ * @param followSymlinks true to follow symlinks while traversing the children. If [dir] itself is
+ * a symlink it will be followed even if this parameter is false.
+ */
+ open fun listRecursively(dir: Path, followSymlinks: Boolean = false): Sequence<Path>
+
+ /**
+ * Returns a handle to read [file]. This will fail if the file doesn't already exist.
+ *
+ * @throws IOException if [file] does not exist, is not a file, or cannot be accessed. A file
+ * cannot be accessed if the current process doesn't have sufficient permissions for [file],
+ * if there's a loop of symbolic links, or if any name is too long.
+ */
+ @Throws(IOException::class)
+ abstract fun openReadOnly(file: Path): FileHandle
+
+ /**
+ * Returns a handle to read and write [file]. This will create the file if it doesn't already
+ * exist.
+ *
+ * @param mustCreate true to throw an [IOException] instead of overwriting an existing file.
+ * This is equivalent to `O_EXCL` on POSIX and `CREATE_NEW` on Windows.
+ * @param mustExist true to throw an [IOException] instead of creating a new file. This is
+ * equivalent to `r+` on POSIX and `OPEN_EXISTING` on Windows.
+ * @throws IOException if [file] is not a file, or cannot be accessed. A file cannot be accessed
+ * if the current process doesn't have sufficient reading and writing permissions for [file],
+ * if there's a loop of symbolic links, or if any name is too long.
+ */
+ @Throws(IOException::class)
+ abstract fun openReadWrite(
+ file: Path,
+ mustCreate: Boolean = false,
+ mustExist: Boolean = false,
+ ): FileHandle
+
+ /**
+ * Returns a source that reads the bytes of [file] from beginning to end.
+ *
+ * @throws IOException if [file] does not exist, is not a file, or cannot be read. A file cannot
+ * be read if the current process doesn't have access to [file], if there's a loop of symbolic
+ * links, or if any name is too long.
+ */
+ @Throws(IOException::class)
+ abstract fun source(file: Path): Source
+
+ /**
+ * Creates a source to read [file], executes [readerAction] to read it, and then closes the
+ * source. This is a compact way to read the contents of a file.
+ */
+ @Throws(IOException::class)
+ inline fun <T> read(file: Path, readerAction: BufferedSource.() -> T): T
+
+ /**
+ * Returns a sink that writes bytes to [file] from beginning to end. If [file] already exists it
+ * will be replaced with the new data.
+ *
+ * @param mustCreate true to throw an [IOException] instead of overwriting an existing file.
+ * This is equivalent to `O_EXCL` on POSIX and `CREATE_NEW` on Windows.
+ *
+ * @throws IOException if [file] cannot be written. A file cannot be written if its enclosing
+ * directory does not exist, if the current process doesn't have access to [file], if there's
+ * a loop of symbolic links, or if any name is too long.
+ */
+ @Throws(IOException::class)
+ abstract fun sink(file: Path, mustCreate: Boolean = false): Sink
+
+ /**
+ * Creates a sink to write [file], executes [writerAction] to write it, and then closes the sink.
+ * This is a compact way to write a file.
+ *
+ * @param mustCreate true to throw an [IOException] instead of overwriting an existing file.
+ * This is equivalent to `O_EXCL` on POSIX and `CREATE_NEW` on Windows.
+ */
+ @Throws(IOException::class)
+ inline fun <T> write(
+ file: Path,
+ mustCreate: Boolean = false,
+ writerAction: BufferedSink.() -> T,
+ ): T
+
+ /**
+ * Returns a sink that appends bytes to the end of [file], creating it if it doesn't already
+ * exist.
+ *
+ * @param mustExist true to throw an [IOException] instead of creating a new file. This is
+ * equivalent to `r+` on POSIX and `OPEN_EXISTING` on Windows.
+ *
+ * @throws IOException if [file] cannot be written. A file cannot be written if its enclosing
+ * directory does not exist, if the current process doesn't have access to [file], if there's
+ * a loop of symbolic links, or if any name is too long.
+ */
+ @Throws(IOException::class)
+ abstract fun appendingSink(file: Path, mustExist: Boolean = false): Sink
+
+ /**
+ * Creates a directory at the path identified by [dir].
+ *
+ * @param mustCreate true to throw an [IOException] if the directory already exists.
+ * @throws IOException if [dir]'s parent does not exist, is not a directory, or cannot be written.
+ * A directory cannot be created if the current process doesn't have access, if there's a loop
+ * of symbolic links, or if any name is too long.
+ */
+ @Throws(IOException::class)
+ abstract fun createDirectory(dir: Path, mustCreate: Boolean = false)
+
+ /**
+ * Creates a directory at the path identified by [dir], and any enclosing parent path directories,
+ * recursively.
+ *
+ * @param mustCreate true to throw an [IOException] instead of overwriting an existing directory.
+ * @throws IOException if any [metadata] or [createDirectory] operation fails.
+ */
+ @Throws(IOException::class)
+ fun createDirectories(dir: Path, mustCreate: Boolean = false)
+
+ /**
+ * Moves [source] to [target] in-place if the underlying file system supports it. If [target]
+ * exists, it is first removed. If `source == target`, this operation does nothing. This may be
+ * used to move a file or a directory.
+ *
+ * **Only as Atomic as the Underlying File System Supports**
+ *
+ * FAT and NTFS file systems cannot atomically move a file over an existing file. If the target
+ * file already exists, the move is performed into two steps:
+ *
+ * 1. Atomically delete the target file.
+ * 2. Atomically rename the source file to the target file.
+ *
+ * The delete step and move step are each atomic but not atomic in aggregate! If this process
+ * crashes, the host operating system crashes, or the hardware fails it is possible that the
+ * delete step will succeed and the rename will not.
+ *
+ * **Entire-file or nothing**
+ *
+ * These are the possible results of this operation:
+ *
+ * * This operation returns normally, the source file is absent, and the target file contains the
+ * data previously held by the source file. This is the success case.
+ *
+ * * The operation throws an [IOException] and the file system is unchanged. For example, this
+ * occurs if this process lacks permissions to perform the move.
+ *
+ * * This operation throws an [IOException], the target file is deleted, but the source file is
+ * unchanged. This is the partial failure case described above and is only possible on
+ * file systems like FAT and NTFS that do not support atomic file replacement. Typically in
+ * such cases this operation won't return at all because the process or operating system has
+ * also crashed.
+ *
+ * There is no failure mode where the target file holds a subset of the bytes of the source file.
+ * If the rename step cannot be performed atomically, this function will throw an [IOException]
+ * before attempting a move. Typically this occurs if the source and target files are on different
+ * physical volumes.
+ *
+ * **Non-Atomic Moves**
+ *
+ * If you need to move files across volumes, use [copy] followed by [delete], and change your
+ * application logic to recover should the copy step suffer a partial failure.
+ *
+ * @throws IOException if the move cannot be performed, or cannot be performed atomically. Moves
+ * fail if the source doesn't exist, if the target is not writable, if the target already
+ * exists and cannot be replaced, or if the move would cause physical or quota limits to be
+ * exceeded. This list of potential problems is not exhaustive.
+ */
+ @Throws(IOException::class)
+ abstract fun atomicMove(source: Path, target: Path)
+
+ /**
+ * Copies all the bytes from the file at [source] to the file at [target]. This does not copy
+ * file metadata like last modified time, permissions, or extended attributes.
+ *
+ * This function is not atomic; a failure may leave [target] in an inconsistent state. For
+ * example, [target] may be empty or contain only a prefix of [source].
+ *
+ * @throws IOException if [source] cannot be read or if [target] cannot be written.
+ */
+ @Throws(IOException::class)
+ open fun copy(source: Path, target: Path)
+
+ /**
+ * Deletes the file or directory at [path].
+ *
+ * @param mustExist true to throw an [IOException] if there is nothing at [path] to delete.
+ * @throws IOException if there is a file or directory but it could not be deleted. Deletes fail
+ * if the current process doesn't have access, if the file system is readonly, or if [path]
+ * is a non-empty directory. This list of potential problems is not exhaustive.
+ */
+ @Throws(IOException::class)
+ abstract fun delete(path: Path, mustExist: Boolean = false)
+
+ /**
+ * Recursively deletes all children of [fileOrDirectory] if it is a directory, then deletes
+ * [fileOrDirectory] itself.
+ *
+ * This function does not defend against race conditions. For example, if child files are created
+ * or deleted in [fileOrDirectory] while this function is executing, this may fail with an
+ * [IOException].
+ *
+ * @param mustExist true to throw an [IOException] if there is nothing at [fileOrDirectory] to
+ * delete.
+ * @throws IOException if any [metadata], [list], or [delete] operation fails.
+ */
+ @Throws(IOException::class)
+ open fun deleteRecursively(fileOrDirectory: Path, mustExist: Boolean = false)
+
+ /**
+ * Creates a symbolic link at [source] that resolves to [target]. If [target] is a relative path,
+ * it is relative to `source.parent`.
+ *
+ * @throws IOException if [source] cannot be created. This may be because it already exists
+ * or because its storage doesn't support symlinks. This list of potential problems is not
+ * exhaustive.
+ */
+ @Throws(IOException::class)
+ abstract fun createSymlink(source: Path, target: Path)
+
+ companion object {
+ /**
+ * Returns a writable temporary directory on [SYSTEM].
+ *
+ * This is platform-specific.
+ *
+ * * **JVM and Android**: the path in the `java.io.tmpdir` system property
+ * * **Linux, iOS, and macOS**: the path in the `TMPDIR` environment variable.
+ * * **Windows**: the first non-null of `TEMP`, `TMP`, and `USERPROFILE` environment variables.
+ *
+ * **Note that the returned directory is not generally private.** Other users or processes that
+ * share this file system may read data that is written to this directory, or write malicious
+ * data for this process to receive.
+ */
+ val SYSTEM_TEMPORARY_DIRECTORY: Path
+ }
+}
diff --git a/okio/src/commonMain/kotlin/okio/ForwardingFileSystem.kt b/okio/src/commonMain/kotlin/okio/ForwardingFileSystem.kt
new file mode 100644
index 00000000..3548b52b
--- /dev/null
+++ b/okio/src/commonMain/kotlin/okio/ForwardingFileSystem.kt
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.jvm.JvmName
+
+/**
+ * A [FileSystem] that forwards calls to another, intended for subclassing.
+ *
+ * ### Fault Injection
+ *
+ * You can use this to deterministically trigger file system failures in tests. This is useful to
+ * confirm that your program behaves correctly even if its file system operations fail. For example,
+ * this subclass fails every access of files named `unlucky.txt`:
+ *
+ * ```
+ * val faultyFileSystem = object : ForwardingFileSystem(FileSystem.SYSTEM) {
+ * override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path {
+ * if (path.name == "unlucky.txt") throw IOException("synthetic failure!")
+ * return path
+ * }
+ * }
+ * ```
+ *
+ * You can fail specific operations by overriding them directly:
+ *
+ * ```
+ * val faultyFileSystem = object : ForwardingFileSystem(FileSystem.SYSTEM) {
+ * override fun delete(path: Path) {
+ * throw IOException("synthetic failure!")
+ * }
+ * }
+ * ```
+ *
+ * ### Observability
+ *
+ * You can extend this to verify which files your program accesses. This is a testing file system
+ * that records accesses as they happen:
+ *
+ * ```
+ * class LoggingFileSystem : ForwardingFileSystem(FileSystem.SYSTEM) {
+ * val log = mutableListOf<String>()
+ *
+ * override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path {
+ * log += "$functionName($parameterName=$path)"
+ * return path
+ * }
+ * }
+ * ```
+ *
+ * This makes it easy for tests to assert exactly which files were accessed.
+ *
+ * ```
+ * @Test
+ * fun testMergeJsonReports() {
+ * createSampleJsonReports()
+ * loggingFileSystem.log.clear()
+ *
+ * mergeJsonReports()
+ *
+ * assertThat(loggingFileSystem.log).containsExactly(
+ * "list(dir=json_reports)",
+ * "source(file=json_reports/2020-10.json)",
+ * "source(file=json_reports/2020-12.json)",
+ * "source(file=json_reports/2020-11.json)",
+ * "sink(file=json_reports/2020-all.json)"
+ * )
+ * }
+ * ```
+ *
+ * ### Transformations
+ *
+ * Subclasses can transform file names and content.
+ *
+ * For example, your program may be written to operate on a well-known directory like `/etc/` or
+ * `/System`. You can rewrite paths to make such operations safer to test.
+ *
+ * You may also transform file content to apply application-layer encryption or compression. This
+ * is particularly useful in situations where it's difficult or impossible to enable those features
+ * in the underlying file system.
+ *
+ * ### Abstract Functions Only
+ *
+ * Some file system functions like [copy] are implemented by using other features. These are the
+ * non-abstract functions in the [FileSystem] interface.
+ *
+ * **This class forwards only the abstract functions;** non-abstract functions delegate to the
+ * other functions of this class. If desired, subclasses may override non-abstract functions to
+ * forward them.
+ */
+abstract class ForwardingFileSystem(
+ /** [FileSystem] to which this instance is delegating. */
+ @get:JvmName("delegate")
+ val delegate: FileSystem,
+) : FileSystem() {
+
+ /**
+ * Invoked each time a path is passed as a parameter to this file system. This returns the path to
+ * pass to [delegate], which should be [path] itself or a path on [delegate] that corresponds to
+ * it.
+ *
+ * Subclasses may override this to log accesses, fail on unexpected accesses, or map paths across
+ * file systems.
+ *
+ * The base implementation returns [path].
+ *
+ * Note that this function will be called twice for calls to [atomicMove]; once for the source
+ * file and once for the target file.
+ *
+ * @param path the path passed to any of the functions of this.
+ * @param functionName a string like "canonicalize", "metadataOrNull", or "appendingSink".
+ * @param parameterName a string like "path", "file", "source", or "target".
+ * @return the path to pass to [delegate] for the same parameter.
+ */
+ open fun onPathParameter(path: Path, functionName: String, parameterName: String): Path = path
+
+ /**
+ * Invoked each time a path is returned by [delegate]. This returns the path to return to the
+ * caller, which should be [path] itself or a path on this that corresponds to it.
+ *
+ * Subclasses may override this to log accesses, fail on unexpected path accesses, or map
+ * directories or path names.
+ *
+ * The base implementation returns [path].
+ *
+ * @param path the path returned by any of the functions of this.
+ * @param functionName a string like "canonicalize" or "list".
+ * @return the path to return to the caller.
+ */
+ open fun onPathResult(path: Path, functionName: String): Path = path
+
+ @Throws(IOException::class)
+ override fun canonicalize(path: Path): Path {
+ val path = onPathParameter(path, "canonicalize", "path")
+ val result = delegate.canonicalize(path)
+ return onPathResult(result, "canonicalize")
+ }
+
+ @Throws(IOException::class)
+ override fun metadataOrNull(path: Path): FileMetadata? {
+ val path = onPathParameter(path, "metadataOrNull", "path")
+ val metadataOrNull = delegate.metadataOrNull(path) ?: return null
+ if (metadataOrNull.symlinkTarget == null) return metadataOrNull
+
+ val symlinkTarget = onPathResult(metadataOrNull.symlinkTarget, "metadataOrNull")
+ return metadataOrNull.copy(symlinkTarget = symlinkTarget)
+ }
+
+ @Throws(IOException::class)
+ override fun list(dir: Path): List<Path> {
+ val dir = onPathParameter(dir, "list", "dir")
+ val result = delegate.list(dir)
+ val paths = result.mapTo(mutableListOf()) { onPathResult(it, "list") }
+ paths.sort()
+ return paths
+ }
+
+ override fun listOrNull(dir: Path): List<Path>? {
+ val dir = onPathParameter(dir, "listOrNull", "dir")
+ val result = delegate.listOrNull(dir) ?: return null
+ val paths = result.mapTo(mutableListOf()) { onPathResult(it, "listOrNull") }
+ paths.sort()
+ return paths
+ }
+
+ override fun listRecursively(dir: Path, followSymlinks: Boolean): Sequence<Path> {
+ val dir = onPathParameter(dir, "listRecursively", "dir")
+ val result = delegate.listRecursively(dir, followSymlinks)
+ return result.map { onPathResult(it, "listRecursively") }
+ }
+
+ @Throws(IOException::class)
+ override fun openReadOnly(file: Path): FileHandle {
+ val file = onPathParameter(file, "openReadOnly", "file")
+ return delegate.openReadOnly(file)
+ }
+
+ @Throws(IOException::class)
+ override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
+ val file = onPathParameter(file, "openReadWrite", "file")
+ return delegate.openReadWrite(file, mustCreate, mustExist)
+ }
+
+ @Throws(IOException::class)
+ override fun source(file: Path): Source {
+ val file = onPathParameter(file, "source", "file")
+ return delegate.source(file)
+ }
+
+ @Throws(IOException::class)
+ override fun sink(file: Path, mustCreate: Boolean): Sink {
+ val file = onPathParameter(file, "sink", "file")
+ return delegate.sink(file, mustCreate)
+ }
+
+ @Throws(IOException::class)
+ override fun appendingSink(file: Path, mustExist: Boolean): Sink {
+ val file = onPathParameter(file, "appendingSink", "file")
+ return delegate.appendingSink(file, mustExist)
+ }
+
+ @Throws(IOException::class)
+ override fun createDirectory(dir: Path, mustCreate: Boolean) {
+ val dir = onPathParameter(dir, "createDirectory", "dir")
+ delegate.createDirectory(dir, mustCreate)
+ }
+
+ @Throws(IOException::class)
+ override fun atomicMove(source: Path, target: Path) {
+ val source = onPathParameter(source, "atomicMove", "source")
+ val target = onPathParameter(target, "atomicMove", "target")
+ delegate.atomicMove(source, target)
+ }
+
+ @Throws(IOException::class)
+ override fun delete(path: Path, mustExist: Boolean) {
+ val path = onPathParameter(path, "delete", "path")
+ delegate.delete(path, mustExist)
+ }
+
+ @Throws(IOException::class)
+ override fun createSymlink(source: Path, target: Path) {
+ val source = onPathParameter(source, "createSymlink", "source")
+ val target = onPathParameter(target, "createSymlink", "target")
+ delegate.createSymlink(source, target)
+ }
+
+ override fun toString() = "${this::class.simpleName}($delegate)"
+}
diff --git a/okio/src/commonMain/kotlin/okio/ForwardingSource.kt b/okio/src/commonMain/kotlin/okio/ForwardingSource.kt
new file mode 100644
index 00000000..0b3e9f6a
--- /dev/null
+++ b/okio/src/commonMain/kotlin/okio/ForwardingSource.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+/** A [Source] which forwards calls to another. Useful for subclassing. */
+expect abstract class ForwardingSource constructor(
+ /** [Source] to which this instance is delegating. */
+ delegate: Source,
+) : Source {
+ /** [Source] to which this instance is delegating. */
+ val delegate: Source
+
+ // TODO 'Source by delegate' once https://youtrack.jetbrains.com/issue/KT-23935 is fixed.
+
+ @Throws(IOException::class)
+ override fun read(sink: Buffer, byteCount: Long): Long
+
+ override fun timeout(): Timeout
+
+ @Throws(IOException::class)
+ override fun close()
+
+ override fun toString(): String
+}
diff --git a/okio/src/commonMain/kotlin/okio/HashingSink.kt b/okio/src/commonMain/kotlin/okio/HashingSink.kt
index 06cb6bf0..d5b8f8db 100644
--- a/okio/src/commonMain/kotlin/okio/HashingSink.kt
+++ b/okio/src/commonMain/kotlin/okio/HashingSink.kt
@@ -42,10 +42,18 @@ expect class HashingSink : Sink {
val hash: ByteString
companion object {
- /** Returns a sink that uses the obsolete MD5 hash algorithm to produce 128-bit hashes. */
+ /**
+ * Returns a sink that uses the obsolete MD5 hash algorithm to produce 128-bit hashes.
+ *
+ * MD5 has been vulnerable to collisions since 2004. It should not be used in new code.
+ */
fun md5(sink: Sink): HashingSink
- /** Returns a sink that uses the obsolete SHA-1 hash algorithm to produce 160-bit hashes. */
+ /**
+ * Returns a sink that uses the obsolete SHA-1 hash algorithm to produce 160-bit hashes.
+ *
+ * SHA-1 has been vulnerable to collisions since 2017. It should not be used in new code.
+ */
fun sha1(sink: Sink): HashingSink
/** Returns a sink that uses the SHA-256 hash algorithm to produce 256-bit hashes. */
diff --git a/okio/src/commonMain/kotlin/okio/HashingSource.kt b/okio/src/commonMain/kotlin/okio/HashingSource.kt
index 9c61c995..52905ea7 100644
--- a/okio/src/commonMain/kotlin/okio/HashingSource.kt
+++ b/okio/src/commonMain/kotlin/okio/HashingSource.kt
@@ -43,10 +43,18 @@ expect class HashingSource : Source {
val hash: ByteString
companion object {
- /** Returns a source that uses the obsolete MD5 hash algorithm to produce 128-bit hashes. */
+ /**
+ * Returns a source that uses the obsolete MD5 hash algorithm to produce 128-bit hashes.
+ *
+ * MD5 has been vulnerable to collisions since 2004. It should not be used in new code.
+ */
fun md5(source: Source): HashingSource
- /** Returns a source that uses the obsolete SHA-1 hash algorithm to produce 160-bit hashes. */
+ /**
+ * Returns a source that uses the obsolete SHA-1 hash algorithm to produce 160-bit hashes.
+ *
+ * SHA-1 has been vulnerable to collisions since 2017. It should not be used in new code.
+ */
fun sha1(source: Source): HashingSource
/** Returns a source that uses the SHA-256 hash algorithm to produce 256-bit hashes. */
diff --git a/okio/src/commonMain/kotlin/okio/Okio.kt b/okio/src/commonMain/kotlin/okio/Okio.kt
index 116678aa..861d5112 100644
--- a/okio/src/commonMain/kotlin/okio/Okio.kt
+++ b/okio/src/commonMain/kotlin/okio/Okio.kt
@@ -56,13 +56,16 @@ inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
result = block(this)
} catch (t: Throwable) {
thrown = t
- }
-
- try {
- this?.close()
- } catch (t: Throwable) {
- if (thrown == null) thrown = t
- else thrown.addSuppressed(t)
+ } finally {
+ try {
+ this?.close()
+ } catch (t: Throwable) {
+ if (thrown == null) {
+ thrown = t
+ } else {
+ thrown.addSuppressed(t)
+ }
+ }
}
if (thrown != null) throw thrown
diff --git a/okio/src/commonMain/kotlin/okio/Options.kt b/okio/src/commonMain/kotlin/okio/Options.kt
index 9ce07391..e8dae6e1 100644
--- a/okio/src/commonMain/kotlin/okio/Options.kt
+++ b/okio/src/commonMain/kotlin/okio/Options.kt
@@ -20,7 +20,7 @@ import kotlin.jvm.JvmStatic
/** An indexed set of values that may be read with [BufferedSource.select]. */
class Options private constructor(
internal val byteStrings: Array<out ByteString>,
- internal val trie: IntArray
+ internal val trie: IntArray,
) : AbstractList<ByteString>(), RandomAccess {
override val size: Int
@@ -111,7 +111,7 @@ class Options private constructor(
byteStrings: List<ByteString>,
fromIndex: Int = 0,
toIndex: Int = byteStrings.size,
- indexes: List<Int>
+ indexes: List<Int>,
) {
require(fromIndex < toIndex)
for (i in fromIndex until toIndex) {
@@ -179,7 +179,7 @@ class Options private constructor(
byteStrings = byteStrings,
fromIndex = rangeStart,
toIndex = rangeEnd,
- indexes = indexes
+ indexes = indexes,
)
}
@@ -223,7 +223,7 @@ class Options private constructor(
byteStrings = byteStrings,
fromIndex = fromIndex,
toIndex = toIndex,
- indexes = indexes
+ indexes = indexes,
)
node.writeAll(childNodes)
}
diff --git a/okio/src/commonMain/kotlin/okio/Path.kt b/okio/src/commonMain/kotlin/okio/Path.kt
new file mode 100644
index 00000000..337abac7
--- /dev/null
+++ b/okio/src/commonMain/kotlin/okio/Path.kt
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import okio.Path.Companion.toPath
+
+/**
+ * A hierarchical address on a file system. A path is an identifier only; a [FileSystem] is required
+ * to access the file that a path refers to, if any.
+ *
+ * UNIX and Windows Paths
+ * ----------------------
+ *
+ * Paths follow different rules on UNIX vs. Windows operating systems. On UNIX operating systems
+ * (including Linux, Android, macOS, and iOS), the `/` slash character separates path segments. On
+ * Windows, the `\` backslash character separates path segments. The two platforms each have their
+ * own rules for path resolution. This class implements all rules on all platforms; for example you
+ * can model a Linux path in a native Windows application.
+ *
+ * Absolute and Relative Paths
+ * ---------------------------
+ *
+ * * **Absolute paths** identify a location independent of any working directory. On UNIX, absolute
+ * paths are prefixed with a slash, `/`. On Windows, absolute paths are one of two forms. The
+ * first is a volume letter, a colon, and a backslash, like `C:\`. The second is called a
+ * Universal Naming Convention (UNC) path, and it is prefixed by two backslashes `\\`. The term
+ * ‘fully-qualified path’ is a synonym of ‘absolute path’.
+ *
+ * * **Relative paths** are everything else. On their own, relative paths do not identify a
+ * location on a file system; they are relative to the system's current working directory. Use
+ * [FileSystem.canonicalize] to convert a relative path to its absolute path on a particular
+ * file system.
+ *
+ * There are some special cases when working with relative paths.
+ *
+ * On Windows, each volume (like `A:\` and `C:\`) has its own current working directory. A path
+ * prefixed with a volume letter and colon but no slash (like `A:letter.doc`) is relative to the
+ * working directory on the named volume. For example, if the working directory on `A:\` is
+ * `A:\jesse`, then the path `A:letter.doc` resolves to `A:\jesse\letter.doc`.
+ *
+ * The path string `C:\Windows` is an absolute path when following Windows rules and a relative
+ * path when following UNIX rules. For example, if the current working directory is
+ * `/Users/jesse`, then `C:\Windows` resolves to `/Users/jesse/C:/Windows`.
+ *
+ * This class decides which rules to follow by inspecting the first slash character in the path
+ * string. If the path contains no slash characters, it uses the host platform's rules. Or you may
+ * explicitly specify which rules to use by specifying the `directorySeparator` parameter in
+ * [toPath]. Pass `"/"` to get UNIX rules and `"\"` to get Windows rules.
+ *
+ * Path Traversal
+ * --------------
+ *
+ * After the optional path root (like `/` on UNIX, like `X:\` or `\\` on Windows), the remainder of
+ * the path is a sequence of segments separated by `/` or `\` characters. Segments satisfy these
+ * rules:
+ *
+ * * Segments are always non-empty.
+ * * If the segment is `.`, then the full path must be `.`.
+ * * For normalized paths, if the segment is `..`, then the path must be relative. All `..`
+ * segments precede all other segments. In all cases, a segment `..` cannot be the first segment
+ * of an absolute path.
+ *
+ * The only path that ends with `/` is the file system root, `/`. The dot path `.` is a relative
+ * path that resolves to whichever path it is resolved against.
+ *
+ * The [name] is the last segment in a path. It is typically a file or directory name, like
+ * `README.md` or `Desktop`. The name may be another special value:
+ *
+ * * The empty string is the name of the file system root path (full path `/`).
+ * * `.` is the name of the identity relative path (full path `.`).
+ * * `..` is the name of a path consisting of only `..` segments (such as `../../..`).
+ *
+ * Comparing Paths
+ * ---------------
+ *
+ * Path implements [Comparable], [equals], and [hashCode]. If two paths are equal then they operate
+ * on the same file on the file system.
+ *
+ * Note that the converse is not true: **if two paths are non-equal, they may still resolve to the
+ * same file on the file system.** Here are some of the ways non-equal paths resolve to the same
+ * file:
+ *
+ * * **Case differences.** The default file system on macOS is case-insensitive. The paths
+ * `/Users/jesse/notes.txt` and `/USERS/JESSE/NOTES.TXT` are non-equal but these paths resolve to
+ * the same file.
+ * * **Mounting differences.** Volumes may be mounted at multiple paths. On macOS,
+ * `/Users/jesse/notes.txt` and `/Volumes/Macintosh HD/Users/jesse/notes.txt` typically resolve
+ * to the same file. On Windows, `C:\project\notes.txt` and `\\localhost\c$\project\notes.txt`
+ * typically resolve to the same file.
+ * * **Hard links.** UNIX file systems permit multiple paths to refer for same file. The paths may
+ * be wildly different, like `/Users/jesse/bruce_wayne.vcard` and
+ * `/Users/jesse/batman.vcard`, but changes via either path are reflected in both.
+ * * **Symlinks.** Symlinks permit multiple paths and directories to refer to the same file. On
+ * macOS `/tmp` is symlinked to `/private/tmp`, so `/tmp/notes.txt` and `/private/tmp/notes.txt`
+ * resolve to the same file.
+ *
+ * To test whether two paths refer to the same file, try [FileSystem.canonicalize] first. This
+ * follows symlinks and looks up the preserved casing for case-insensitive case-preserved paths.
+ * **This method does not guarantee a unique result, however.** For example, each hard link to a
+ * file may return its own canonical path.
+ *
+ * Paths are sorted in case-sensitive order.
+ *
+ * Sample Paths
+ * ------------
+ *
+ * <table>
+ * <tr><th> Path <th> Parent <th> Root <th> Name <th> Notes </tr>
+ * <tr><td> `/` <td> null <td> `/` <td> (empty) <td> root </tr>
+ * <tr><td> `/home/jesse/notes.txt` <td> `/home/jesse` <td> `/` <td> `notes.txt` <td> absolute path </tr>
+ * <tr><td> `project/notes.txt` <td> `project` <td> null <td> `notes.txt` <td> relative path </tr>
+ * <tr><td> `../../project/notes.txt` <td> `../../project` <td> null <td> `notes.txt` <td> relative path with traversal </tr>
+ * <tr><td> `../../..` <td> null <td> null <td> `..` <td> relative path with traversal </tr>
+ * <tr><td> `.` <td> null <td> null <td> `.` <td> current working directory </tr>
+ * <tr><td> `C:\` <td> null <td> `C:\` <td> (empty) <td> volume root (Windows) </tr>
+ * <tr><td> `C:\Windows\notepad.exe` <td> `C:\Windows` <td> `C:\` <td> `notepad.exe` <td> volume absolute path (Windows) </tr>
+ * <tr><td> `\` <td> null <td> `\` <td> (empty) <td> absolute path (Windows) </tr>
+ * <tr><td> `\Windows\notepad.exe` <td> `\Windows` <td> `\` <td> `notepad.exe` <td> absolute path (Windows) </tr>
+ * <tr><td> `C:` <td> null <td> null <td> (empty) <td> volume-relative path (Windows) </tr>
+ * <tr><td> `C:project\notes.txt` <td> `C:project` <td> null <td> `notes.txt` <td> volume-relative path (Windows) </tr>
+ * <tr><td> `\\server` <td> null <td> `\\server` <td> `server` <td> UNC server (Windows) </tr>
+ * <tr><td> `\\server\project\notes.txt` <td> `\\server\project` <td> `\\server` <td> `notes.txt` <td> UNC absolute path (Windows) </tr>
+ * </table>
+ */
+expect class Path internal constructor(bytes: ByteString) : Comparable<Path> {
+ /**
+ * This is the root path if this is an absolute path, or null if it is a relative path. UNIX paths
+ * have a single root, `/`. Each volume on Windows is its own root, like `C:\` and `D:\`. The
+ * path to the current volume `\` is its own root. Windows UNC paths like `\\server` are also
+ * roots.
+ */
+ val root: Path?
+
+ /**
+ * The components of this path that are usually delimited by slashes. If the root is not null it
+ * precedes these segments. If this path is a root its segments list is empty.
+ */
+ val segments: List<String>
+
+ val segmentsBytes: List<ByteString>
+
+ internal val bytes: ByteString
+
+ /** This is true if [root] is not null. */
+ val isAbsolute: Boolean
+
+ /** This is true if [root] is null. */
+ val isRelative: Boolean
+
+ /**
+ * This is the volume letter like "C" on Windows paths that starts with a volume letter. For
+ * example, on the path "C:\Windows" this returns "C". This property is null if this is not a
+ * Windows path, or if it doesn't have a volume letter.
+ *
+ * Note that paths that start with a volume letter are not necessarily absolute paths. For
+ * example, the path "C:notepad.exe" is relative to whatever the current working directory is on
+ * the C: drive.
+ */
+ val volumeLetter: Char?
+
+ val nameBytes: ByteString
+
+ val name: String
+
+ /**
+ * Returns the path immediately enclosing this path.
+ *
+ * This returns null if this has no parent. That includes these paths:
+ *
+ * * The file system root (`/`)
+ * * The identity relative path (`.`)
+ * * A Windows volume root (like `C:\`)
+ * * A Windows Universal Naming Convention (UNC) root path (`\\server`)
+ * * A reference to the current working directory on a Windows volume (`C:`).
+ * * A series of relative paths (like `..` and `../..`).
+ */
+ val parent: Path?
+
+ /** Returns true if `this == this.root`. That is, this is an absolute path with no parent. */
+ val isRoot: Boolean
+
+ /**
+ * Returns a path that resolves [child] relative to this path. Note that the result isn't
+ * guaranteed to be normalized even if this and [child] are both normalized themselves.
+ *
+ * If [child] is an [absolute path][isAbsolute] or [has a volume letter][hasVolumeLetter] then
+ * this function is equivalent to `child.toPath()`.
+ */
+ operator fun div(child: String): Path
+
+ /**
+ * Returns a path that resolves [child] relative to this path. Note that the result isn't
+ * guaranteed to be normalized even if this and [child] are both normalized themselves.
+ *
+ * If [child] is an [absolute path][isAbsolute] or [has a volume letter][hasVolumeLetter] then
+ * this function is equivalent to `child.toPath()`.
+ */
+ operator fun div(child: ByteString): Path
+
+ /**
+ * Returns a path that resolves [child] relative to this path. Note that the result isn't
+ * guaranteed to be normalized even if this and [child] are both normalized themselves.
+ *
+ * If [child] is an [absolute path][isAbsolute] or [has a volume letter][hasVolumeLetter] then
+ * this function is equivalent to `child.toPath()`.
+ */
+ operator fun div(child: Path): Path
+
+ /**
+ * Returns a path that resolves [child] relative to this path.
+ *
+ * Set [normalize] to true to eagerly consume `..` segments on the resolved path. In all cases,
+ * leading `..` on absolute paths will be removed. If [normalize] is false, note that the result
+ * isn't guaranteed to be normalized even if this and [child] are both normalized themselves.
+ *
+ * If [child] is an [absolute path][isAbsolute] or [has a volume letter][hasVolumeLetter] then
+ * this function is equivalent to `child.toPath(normalize)`.
+ */
+ fun resolve(child: String, normalize: Boolean = false): Path
+
+ /**
+ * Returns a path that resolves [child] relative to this path.
+ *
+ * Set [normalize] to true to eagerly consume `..` segments on the resolved path. In all cases,
+ * leading `..` on absolute paths will be removed. If [normalize] is false, note that the result
+ * isn't guaranteed to be normalized even if this and [child] are both normalized themselves.
+ *
+ * If [child] is an [absolute path][isAbsolute] or [has a volume letter][hasVolumeLetter] then
+ * this function is equivalent to `child.toPath(normalize)`.
+ */
+ fun resolve(child: ByteString, normalize: Boolean = false): Path
+
+ /**
+ * Returns a path that resolves [child] relative to this path.
+ *
+ * Set [normalize] to true to eagerly consume `..` segments on the resolved path. In all cases,
+ * leading `..` on absolute paths will be removed. If [normalize] is false, note that the result
+ * isn't guaranteed to be normalized even if this and [child] are both normalized themselves.
+ *
+ * If [child] is an [absolute path][isAbsolute] or [has a volume letter][hasVolumeLetter] then
+ * this function is equivalent to `child.toPath(normalize)`.
+ */
+ fun resolve(child: Path, normalize: Boolean = false): Path
+
+ /**
+ * Returns this path relative to [other]. This effectively inverts the resolve operator, `/`. For
+ * any two paths `a` and `b` that have the same root, `a / (b.relativeTo(a))` is equal to `b`. If
+ * both paths don't use the same slash, the resolved path will use the slash of the [other] path.
+ *
+ * @throws IllegalArgumentException if this path and the [other] path are not both
+ * [absolute paths][isAbsolute] or both [relative paths][isRelative], or if they are both
+ * [absolute paths][isAbsolute] but of different roots (C: vs D:, or C: vs \\server, etc.).
+ * It will also throw if the relative path is impossible to resolve. For instance, it is
+ * impossible to resolve the path `../a` relative to `../../b`.
+ */
+ @Throws(IllegalArgumentException::class)
+ fun relativeTo(other: Path): Path
+
+ /**
+ * Returns the normalized version of this path. This has the same effect as
+ * `this.toString().toPath(normalize = true)`.
+ */
+ fun normalized(): Path
+
+ override fun compareTo(other: Path): Int
+
+ override fun equals(other: Any?): Boolean
+
+ override fun hashCode(): Int
+
+ override fun toString(): String
+
+ companion object {
+ /**
+ * Either `/` (on UNIX-like systems including Android, iOS, and Linux) or `\` (on Windows
+ * systems).
+ *
+ * This separator is used by `FileSystem.SYSTEM` and possibly other file systems on the host
+ * system. Some file system implementations may not use this separator.
+ */
+ val DIRECTORY_SEPARATOR: String
+
+ /**
+ * Returns the [Path] representation for this string.
+ *
+ * Set [normalize] to true to eagerly consume `..` segments in your path. In all cases, leading
+ * `..` on absolute paths will be removed.
+ *
+ * ```
+ * "/Users/jesse/Documents/../notes.txt".toPath(normalize = false).toString() => "/Users/jesse/Documents/../notes.txt"
+ * "/Users/jesse/Documents/../notes.txt".toPath(normalize = true).toString() => "/Users/jesse/notes.txt"
+ * ```
+ */
+ fun String.toPath(normalize: Boolean = false): Path
+ }
+}
diff --git a/okio/src/commonMain/kotlin/okio/PeekSource.kt b/okio/src/commonMain/kotlin/okio/PeekSource.kt
index 598d83de..70f8ef92 100644
--- a/okio/src/commonMain/kotlin/okio/PeekSource.kt
+++ b/okio/src/commonMain/kotlin/okio/PeekSource.kt
@@ -26,7 +26,7 @@ package okio
* invalid and throw [IllegalStateException] on any future reads.
*/
internal class PeekSource(
- private val upstream: BufferedSource
+ private val upstream: BufferedSource,
) : Source {
private val buffer = upstream.buffer
private var expectedSegment = buffer.head
@@ -42,7 +42,7 @@ internal class PeekSource(
// do not match the current head and head position of the upstream buffer
check(
expectedSegment == null ||
- expectedSegment === buffer.head && expectedPos == buffer.head!!.pos
+ expectedSegment === buffer.head && expectedPos == buffer.head!!.pos,
) {
"Peek source is invalid because upstream source was used"
}
diff --git a/okio/src/commonMain/kotlin/okio/RealBufferedSink.kt b/okio/src/commonMain/kotlin/okio/RealBufferedSink.kt
index 80e3fae4..81032153 100644
--- a/okio/src/commonMain/kotlin/okio/RealBufferedSink.kt
+++ b/okio/src/commonMain/kotlin/okio/RealBufferedSink.kt
@@ -17,7 +17,7 @@
package okio
internal expect class RealBufferedSink(
- sink: Sink
+ sink: Sink,
) : BufferedSink {
val sink: Sink
var closed: Boolean
diff --git a/okio/src/commonMain/kotlin/okio/RealBufferedSource.kt b/okio/src/commonMain/kotlin/okio/RealBufferedSource.kt
index b626e425..b6f7322e 100644
--- a/okio/src/commonMain/kotlin/okio/RealBufferedSource.kt
+++ b/okio/src/commonMain/kotlin/okio/RealBufferedSource.kt
@@ -17,7 +17,7 @@
package okio
internal expect class RealBufferedSource(
- source: Source
+ source: Source,
) : BufferedSource {
val source: Source
var closed: Boolean
diff --git a/okio/src/commonMain/kotlin/okio/Segment.kt b/okio/src/commonMain/kotlin/okio/Segment.kt
index 36879499..3a34c59f 100644
--- a/okio/src/commonMain/kotlin/okio/Segment.kt
+++ b/okio/src/commonMain/kotlin/okio/Segment.kt
@@ -167,8 +167,10 @@ internal class Segment {
}
data.copyInto(
- sink.data, destinationOffset = sink.limit, startIndex = pos,
- endIndex = pos + byteCount
+ sink.data,
+ destinationOffset = sink.limit,
+ startIndex = pos,
+ endIndex = pos + byteCount,
)
sink.limit += byteCount
pos += byteCount
diff --git a/okio/src/commonMain/kotlin/okio/SegmentedByteString.kt b/okio/src/commonMain/kotlin/okio/SegmentedByteString.kt
index bda4f4a3..1eb5628e 100644
--- a/okio/src/commonMain/kotlin/okio/SegmentedByteString.kt
+++ b/okio/src/commonMain/kotlin/okio/SegmentedByteString.kt
@@ -42,7 +42,7 @@ package okio
*/
internal expect class SegmentedByteString internal constructor(
segments: Array<ByteArray>,
- directory: IntArray
+ directory: IntArray,
) : ByteString {
internal val segments: Array<ByteArray>
diff --git a/okio/src/commonMain/kotlin/okio/Utf8.kt b/okio/src/commonMain/kotlin/okio/Utf8.kt
index ca20e214..465a4abf 100644
--- a/okio/src/commonMain/kotlin/okio/Utf8.kt
+++ b/okio/src/commonMain/kotlin/okio/Utf8.kt
@@ -82,7 +82,7 @@ fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long {
var result = 0L
var i = beginIndex
while (i < endIndex) {
- val c = this[i].toInt()
+ val c = this[i].code
if (c < 0x80) {
// A 7-bit character with 1 byte.
@@ -97,7 +97,7 @@ fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long {
result += 3
i++
} else {
- val low = if (i + 1 < endIndex) this[i + 1].toInt() else 0
+ val low = if (i + 1 < endIndex) this[i + 1].code else 0
if (c > 0xdbff || low < 0xdc00 || low > 0xdfff) {
// A malformed surrogate, which yields '?'.
result++
@@ -113,9 +113,9 @@ fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long {
return result
}
-internal const val REPLACEMENT_BYTE: Byte = '?'.toByte()
+internal const val REPLACEMENT_BYTE: Byte = '?'.code.toByte()
internal const val REPLACEMENT_CHARACTER: Char = '\ufffd'
-internal const val REPLACEMENT_CODE_POINT: Int = REPLACEMENT_CHARACTER.toInt()
+internal const val REPLACEMENT_CODE_POINT: Int = REPLACEMENT_CHARACTER.code
@Suppress("NOTHING_TO_INLINE") // Syntactic sugar.
internal inline fun isIsoControl(codePoint: Int): Boolean =
@@ -132,7 +132,7 @@ internal inline fun isUtf8Continuation(byte: Byte): Boolean {
internal inline fun String.processUtf8Bytes(
beginIndex: Int,
endIndex: Int,
- yield: (Byte) -> Unit
+ yield: (Byte) -> Unit,
) {
// Transcode a UTF-16 String to UTF-8 bytes.
var index = beginIndex
@@ -142,20 +142,20 @@ internal inline fun String.processUtf8Bytes(
when {
c < '\u0080' -> {
// Emit a 7-bit character with 1 byte.
- yield(c.toByte()) // 0xxxxxxx
+ yield(c.code.toByte()) // 0xxxxxxx
index++
// Assume there is going to be more ASCII
while (index < endIndex && this[index] < '\u0080') {
- yield(this[index++].toByte())
+ yield(this[index++].code.toByte())
}
}
c < '\u0800' -> {
// Emit a 11-bit character with 2 bytes.
/* ktlint-disable no-multi-spaces */
- yield((c.toInt() shr 6 or 0xc0).toByte()) // 110xxxxx
- yield((c.toInt() and 0x3f or 0x80).toByte()) // 10xxxxxx
+ yield((c.code shr 6 or 0xc0).toByte()) // 110xxxxx
+ yield((c.code and 0x3f or 0x80).toByte()) // 10xxxxxx
/* ktlint-enable no-multi-spaces */
index++
}
@@ -163,9 +163,9 @@ internal inline fun String.processUtf8Bytes(
c !in '\ud800'..'\udfff' -> {
// Emit a 16-bit character with 3 bytes.
/* ktlint-disable no-multi-spaces */
- yield((c.toInt() shr 12 or 0xe0).toByte()) // 1110xxxx
- yield((c.toInt() shr 6 and 0x3f or 0x80).toByte()) // 10xxxxxx
- yield((c.toInt() and 0x3f or 0x80).toByte()) // 10xxxxxx
+ yield((c.code shr 12 or 0xe0).toByte()) // 1110xxxx
+ yield((c.code shr 6 and 0x3f or 0x80).toByte()) // 10xxxxxx
+ yield((c.code and 0x3f or 0x80).toByte()) // 10xxxxxx
/* ktlint-enable no-multi-spaces */
index++
}
@@ -185,7 +185,7 @@ internal inline fun String.processUtf8Bytes(
// UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits)
// Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits)
val codePoint = (
- ((c.toInt() shl 10) + this[index + 1].toInt()) +
+ ((c.code shl 10) + this[index + 1].code) +
(0x010000 - (0xd800 shl 10) - 0xdc00)
)
@@ -207,7 +207,7 @@ internal inline fun String.processUtf8Bytes(
internal inline fun ByteArray.processUtf8CodePoints(
beginIndex: Int,
endIndex: Int,
- yield: (Int) -> Unit
+ yield: (Int) -> Unit,
) {
var index = beginIndex
while (index < endIndex) {
@@ -247,6 +247,7 @@ internal inline fun ByteArray.processUtf8CodePoints(
// Value added to the high UTF-16 surrogate after shifting
internal const val HIGH_SURROGATE_HEADER = 0xd800 - (0x010000 ushr 10)
+
// Value added to the low UTF-16 surrogate after masking
internal const val LOG_SURROGATE_HEADER = 0xdc00
@@ -254,7 +255,7 @@ internal const val LOG_SURROGATE_HEADER = 0xdc00
internal inline fun ByteArray.processUtf16Chars(
beginIndex: Int,
endIndex: Int,
- yield: (Char) -> Unit
+ yield: (Char) -> Unit,
) {
var index = beginIndex
while (index < endIndex) {
@@ -262,13 +263,13 @@ internal inline fun ByteArray.processUtf16Chars(
when {
b0 >= 0 -> {
// 0b0xxxxxxx
- yield(b0.toChar())
+ yield(b0.toInt().toChar())
index++
// Assume there is going to be more ASCII
// This is almost double the performance of the outer loop
while (index < endIndex && this[index] >= 0) {
- yield(this[index++].toChar())
+ yield(this[index++].toInt().toChar())
}
}
b0 shr 5 == -2 -> {
@@ -391,7 +392,7 @@ internal const val MASK_2BYTES = 0x0f80
internal inline fun ByteArray.process2Utf8Bytes(
beginIndex: Int,
endIndex: Int,
- yield: (Int) -> Unit
+ yield: (Int) -> Unit,
): Int {
if (endIndex <= beginIndex + 1) {
yield(REPLACEMENT_CODE_POINT)
@@ -434,7 +435,7 @@ internal const val MASK_3BYTES = -0x01e080
internal inline fun ByteArray.process3Utf8Bytes(
beginIndex: Int,
endIndex: Int,
- yield: (Int) -> Unit
+ yield: (Int) -> Unit,
): Int {
if (endIndex <= beginIndex + 2) {
// At least 2 bytes remaining
@@ -494,7 +495,7 @@ internal const val MASK_4BYTES = 0x381f80
internal inline fun ByteArray.process4Utf8Bytes(
beginIndex: Int,
endIndex: Int,
- yield: (Int) -> Unit
+ yield: (Int) -> Unit,
): Int {
if (endIndex <= beginIndex + 3) {
// At least 3 bytes remaining
diff --git a/okio/src/commonMain/kotlin/okio/-Util.kt b/okio/src/commonMain/kotlin/okio/Util.kt
index 9ab36882..bfd8fec1 100644
--- a/okio/src/commonMain/kotlin/okio/-Util.kt
+++ b/okio/src/commonMain/kotlin/okio/Util.kt
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-@file:JvmName("-Util")
+@file:JvmName("-SegmentedByteString") // A leading '-' hides this class from Java.
package okio
-import okio.internal.HEX_DIGIT_CHARS
import kotlin.jvm.JvmName
+import kotlin.native.concurrent.SharedImmutable
+import okio.internal.HEX_DIGIT_CHARS
internal fun checkOffsetAndCount(size: Long, offset: Long, byteCount: Long) {
if (offset or byteCount < 0 || offset > size || size - offset < byteCount) {
@@ -93,7 +93,7 @@ internal fun arrayRangeEquals(
aOffset: Int,
b: ByteArray,
bOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean {
for (i in 0 until byteCount) {
if (a[i + aOffset] != b[i + bOffset]) return false
@@ -105,7 +105,7 @@ internal fun Byte.toHexString(): String {
val result = CharArray(2)
result[0] = HEX_DIGIT_CHARS[this shr 4 and 0xf]
result[1] = HEX_DIGIT_CHARS[this and 0xf] // ktlint-disable no-multi-spaces
- return String(result)
+ return result.concatToString()
}
internal fun Int.toHexString(): String {
@@ -128,7 +128,7 @@ internal fun Int.toHexString(): String {
i++
}
- return String(result, i, result.size - i)
+ return result.concatToString(i, result.size)
}
internal fun Long.toHexString(): String {
@@ -159,5 +159,28 @@ internal fun Long.toHexString(): String {
i++
}
- return String(result, i, result.size - i)
+ return result.concatToString(i, result.size)
+}
+
+// Work around a problem where Kotlin/JS IR can't handle default parameters on expect functions
+// that depend on the receiver. We use well-known, otherwise-impossible values here and must check
+// for them in the receiving function, then swap in the true default value.
+// https://youtrack.jetbrains.com/issue/KT-45542
+
+@SharedImmutable
+internal val DEFAULT__new_UnsafeCursor = Buffer.UnsafeCursor()
+internal fun resolveDefaultParameter(unsafeCursor: Buffer.UnsafeCursor): Buffer.UnsafeCursor {
+ if (unsafeCursor === DEFAULT__new_UnsafeCursor) return Buffer.UnsafeCursor()
+ return unsafeCursor
+}
+
+internal val DEFAULT__ByteString_size = -1234567890
+internal fun ByteString.resolveDefaultParameter(position: Int): Int {
+ if (position == DEFAULT__ByteString_size) return size
+ return position
+}
+
+internal fun ByteArray.resolveDefaultParameter(sizeParam: Int): Int {
+ if (sizeParam == DEFAULT__ByteString_size) return size
+ return sizeParam
}
diff --git a/okio/src/commonMain/kotlin/okio/internal/-Utf8.kt b/okio/src/commonMain/kotlin/okio/internal/-Utf8.kt
index 926f1853..865ff2c1 100644
--- a/okio/src/commonMain/kotlin/okio/internal/-Utf8.kt
+++ b/okio/src/commonMain/kotlin/okio/internal/-Utf8.kt
@@ -1,3 +1,4 @@
+// ktlint-disable filename
/*
* Copyright (C) 2018 Square, Inc.
*
@@ -13,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package okio.internal
import okio.ArrayIndexOutOfBoundsException
@@ -35,7 +35,7 @@ fun ByteArray.commonToUtf8String(beginIndex: Int = 0, endIndex: Int = size): Str
chars[length++] = c
}
- return String(chars, 0, length)
+ return chars.concatToString(0, length)
}
fun String.commonAsUtf8ToByteArray(): ByteArray {
@@ -52,7 +52,7 @@ fun String.commonAsUtf8ToByteArray(): ByteArray {
}
return bytes.copyOf(size)
}
- bytes[index] = b0.toByte()
+ bytes[index] = b0.code.toByte()
}
return bytes.copyOf(length)
diff --git a/okio/src/commonMain/kotlin/okio/internal/Buffer.kt b/okio/src/commonMain/kotlin/okio/internal/Buffer.kt
index 0cb15cc4..2270fc1d 100644
--- a/okio/src/commonMain/kotlin/okio/internal/Buffer.kt
+++ b/okio/src/commonMain/kotlin/okio/internal/Buffer.kt
@@ -13,12 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:JvmName("-Buffer") // A leading '-' hides this class from Java.
// TODO move to Buffer class: https://youtrack.jetbrains.com/issue/KT-20427
@file:Suppress("NOTHING_TO_INLINE")
package okio.internal
+import kotlin.jvm.JvmName
+import kotlin.native.concurrent.SharedImmutable
import okio.ArrayIndexOutOfBoundsException
import okio.Buffer
import okio.Buffer.UnsafeCursor
@@ -35,8 +38,10 @@ import okio.and
import okio.asUtf8ToByteArray
import okio.checkOffsetAndCount
import okio.minOf
+import okio.resolveDefaultParameter
import okio.toHexString
+@SharedImmutable
internal val HEX_DIGIT_BYTES = "0123456789abcdef".asUtf8ToByteArray()
// Threshold determined empirically via ReadByteStringBenchmark
@@ -52,7 +57,7 @@ internal fun rangeEquals(
segmentPos: Int,
bytes: ByteArray,
bytesOffset: Int,
- bytesLimit: Int
+ bytesLimit: Int,
): Boolean {
var segment = segment
var segmentPos = segmentPos
@@ -81,7 +86,7 @@ internal fun rangeEquals(
internal fun Buffer.readUtf8Line(newline: Long): String {
return when {
- newline > 0 && this[newline - 1] == '\r'.toByte() -> {
+ newline > 0 && this[newline - 1] == '\r'.code.toByte() -> {
// Read everything until '\r\n', then skip the '\r\n'.
val result = readUtf8(newline - 1L)
skip(2L)
@@ -102,7 +107,7 @@ internal fun Buffer.readUtf8Line(newline: Long): String {
*/
internal inline fun <T> Buffer.seek(
fromIndex: Long,
- lambda: (Segment?, Long) -> T
+ lambda: (Segment?, Long) -> T,
): T {
var s: Segment = head ?: return lambda(null, -1L)
@@ -234,7 +239,7 @@ internal fun Buffer.selectPrefix(options: Options, selectTruncated: Boolean = fa
internal inline fun Buffer.commonCopyTo(
out: Buffer,
offset: Long,
- byteCount: Long
+ byteCount: Long,
): Buffer {
var offset = offset
var byteCount = byteCount
@@ -434,7 +439,7 @@ internal inline fun Buffer.commonSkip(byteCount: Long) {
internal inline fun Buffer.commonWrite(
byteString: ByteString,
offset: Int = 0,
- byteCount: Int = byteString.size
+ byteCount: Int = byteString.size,
): Buffer {
byteString.write(this, offset, byteCount)
return this
@@ -444,7 +449,7 @@ internal inline fun Buffer.commonWriteDecimalLong(v: Long): Buffer {
var v = v
if (v == 0L) {
// Both a shortcut and required since the following code can't handle zero.
- return writeByte('0'.toInt())
+ return writeByte('0'.code)
}
var negative = false
@@ -458,33 +463,61 @@ internal inline fun Buffer.commonWriteDecimalLong(v: Long): Buffer {
// Binary search for character width which favors matching lower numbers.
var width =
- if (v < 100000000L)
- if (v < 10000L)
- if (v < 100L)
- if (v < 10L) 1
- else 2
- else if (v < 1000L) 3
- else 4
- else if (v < 1000000L)
- if (v < 100000L) 5
- else 6
- else if (v < 10000000L) 7
- else 8
- else if (v < 1000000000000L)
- if (v < 10000000000L)
- if (v < 1000000000L) 9
- else 10
- else if (v < 100000000000L) 11
- else 12
- else if (v < 1000000000000000L)
- if (v < 10000000000000L) 13
- else if (v < 100000000000000L) 14
- else 15
- else if (v < 100000000000000000L)
- if (v < 10000000000000000L) 16
- else 17
- else if (v < 1000000000000000000L) 18
- else 19
+ if (v < 100000000L) {
+ if (v < 10000L) {
+ if (v < 100L) {
+ if (v < 10L) {
+ 1
+ } else {
+ 2
+ }
+ } else if (v < 1000L) {
+ 3
+ } else {
+ 4
+ }
+ } else if (v < 1000000L) {
+ if (v < 100000L) {
+ 5
+ } else {
+ 6
+ }
+ } else if (v < 10000000L) {
+ 7
+ } else {
+ 8
+ }
+ } else if (v < 1000000000000L) {
+ if (v < 10000000000L) {
+ if (v < 1000000000L) {
+ 9
+ } else {
+ 10
+ }
+ } else if (v < 100000000000L) {
+ 11
+ } else {
+ 12
+ }
+ } else if (v < 1000000000000000L) {
+ if (v < 10000000000000L) {
+ 13
+ } else if (v < 100000000000000L) {
+ 14
+ } else {
+ 15
+ }
+ } else if (v < 100000000000000000L) {
+ if (v < 10000000000000000L) {
+ 16
+ } else {
+ 17
+ }
+ } else if (v < 1000000000000000000L) {
+ 18
+ } else {
+ 19
+ }
if (negative) {
++width
}
@@ -498,7 +531,7 @@ internal inline fun Buffer.commonWriteDecimalLong(v: Long): Buffer {
v /= 10
}
if (negative) {
- data[--pos] = '-'.toByte()
+ data[--pos] = '-'.code.toByte()
}
tail.limit += width
@@ -510,7 +543,7 @@ internal inline fun Buffer.commonWriteHexadecimalUnsignedLong(v: Long): Buffer {
var v = v
if (v == 0L) {
// Both a shortcut and required since the following code can't handle zero.
- return writeByte('0'.toInt())
+ return writeByte('0'.code)
}
// Mask every bit below the most significant bit to a 1
@@ -572,7 +605,7 @@ internal inline fun Buffer.commonWrite(source: ByteArray) = write(source, 0, sou
internal inline fun Buffer.commonWrite(
source: ByteArray,
offset: Int,
- byteCount: Int
+ byteCount: Int,
): Buffer {
var offset = offset
checkOffsetAndCount(source.size.toLong(), offset.toLong(), byteCount.toLong())
@@ -586,7 +619,7 @@ internal inline fun Buffer.commonWrite(
destination = tail.data,
destinationOffset = tail.limit,
startIndex = offset,
- endIndex = offset + toCopy
+ endIndex = offset + toCopy,
)
offset += toCopy
@@ -625,7 +658,10 @@ internal inline fun Buffer.commonRead(sink: ByteArray, offset: Int, byteCount: I
val s = head ?: return -1
val toCopy = minOf(byteCount, s.limit - s.pos)
s.data.copyInto(
- destination = sink, destinationOffset = offset, startIndex = s.pos, endIndex = s.pos + toCopy
+ destination = sink,
+ destinationOffset = offset,
+ startIndex = s.pos,
+ endIndex = s.pos + toCopy,
)
s.pos += toCopy
@@ -662,8 +698,8 @@ internal inline fun Buffer.commonReadDecimalLong(): Long {
while (pos < limit) {
val b = data[pos]
- if (b >= '0'.toByte() && b <= '9'.toByte()) {
- val digit = '0'.toByte() - b
+ if (b >= '0'.code.toByte() && b <= '9'.code.toByte()) {
+ val digit = '0'.code.toByte() - b
// Detect when the digit would cause an overflow.
if (value < OVERFLOW_ZONE || value == OVERFLOW_ZONE && digit < overflowDigit) {
@@ -673,15 +709,10 @@ internal inline fun Buffer.commonReadDecimalLong(): Long {
}
value *= 10L
value += digit.toLong()
- } else if (b == '-'.toByte() && seen == 0) {
+ } else if (b == '-'.code.toByte() && seen == 0) {
negative = true
overflowDigit -= 1
} else {
- if (seen == 0) {
- throw NumberFormatException(
- "Expected leading [0-9] or '-' character but was 0x${b.toHexString()}"
- )
- }
// Set a flag to stop iteration. We still need to run through segment updating below.
done = true
break
@@ -699,6 +730,14 @@ internal inline fun Buffer.commonReadDecimalLong(): Long {
} while (!done && head != null)
size -= seen.toLong()
+
+ val minimumSeen = if (negative) 2 else 1
+ if (seen < minimumSeen) {
+ if (size == 0L) throw EOFException()
+ val expected = if (negative) "Expected a digit" else "Expected a digit or '-'"
+ throw NumberFormatException("$expected but was 0x${get(0).toHexString()}")
+ }
+
return if (negative) value else -value
}
@@ -720,16 +759,16 @@ internal inline fun Buffer.commonReadHexadecimalUnsignedLong(): Long {
val digit: Int
val b = data[pos]
- if (b >= '0'.toByte() && b <= '9'.toByte()) {
- digit = b - '0'.toByte()
- } else if (b >= 'a'.toByte() && b <= 'f'.toByte()) {
- digit = b - 'a'.toByte() + 10
- } else if (b >= 'A'.toByte() && b <= 'F'.toByte()) {
- digit = b - 'A'.toByte() + 10 // We never write uppercase, but we support reading it.
+ if (b >= '0'.code.toByte() && b <= '9'.code.toByte()) {
+ digit = b - '0'.code.toByte()
+ } else if (b >= 'a'.code.toByte() && b <= 'f'.code.toByte()) {
+ digit = b - 'a'.code.toByte() + 10
+ } else if (b >= 'A'.code.toByte() && b <= 'F'.code.toByte()) {
+ digit = b - 'A'.code.toByte() + 10 // We never write uppercase, but we support reading it.
} else {
if (seen == 0) {
throw NumberFormatException(
- "Expected leading [0-9a-fA-F] character but was 0x${b.toHexString()}"
+ "Expected leading [0-9a-fA-F] character but was 0x${b.toHexString()}",
)
}
// Set a flag to stop iteration. We still need to run through segment updating below.
@@ -825,7 +864,7 @@ internal inline fun Buffer.commonReadUtf8(byteCount: Long): String {
}
internal inline fun Buffer.commonReadUtf8Line(): String? {
- val newline = indexOf('\n'.toByte())
+ val newline = indexOf('\n'.code.toByte())
return when {
newline != -1L -> readUtf8Line(newline)
@@ -837,11 +876,11 @@ internal inline fun Buffer.commonReadUtf8Line(): String? {
internal inline fun Buffer.commonReadUtf8LineStrict(limit: Long): String {
require(limit >= 0L) { "limit < 0: $limit" }
val scanLength = if (limit == Long.MAX_VALUE) Long.MAX_VALUE else limit + 1L
- val newline = indexOf('\n'.toByte(), 0L, scanLength)
+ val newline = indexOf('\n'.code.toByte(), 0L, scanLength)
if (newline != -1L) return readUtf8Line(newline)
if (scanLength < size &&
- this[scanLength - 1] == '\r'.toByte() &&
- this[scanLength] == '\n'.toByte()
+ this[scanLength - 1] == '\r'.code.toByte() &&
+ this[scanLength] == '\n'.code.toByte()
) {
return readUtf8Line(scanLength) // The line was 'limit' UTF-8 bytes followed by \r\n.
}
@@ -850,8 +889,8 @@ internal inline fun Buffer.commonReadUtf8LineStrict(limit: Long): String {
throw EOFException(
"\\n not found: limit=${minOf(
size,
- limit
- )} content=${data.readByteString().hex()}${'…'}"
+ limit,
+ )} content=${data.readByteString().hex()}${'…'}",
)
}
@@ -938,7 +977,7 @@ internal inline fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endI
// Transcode a UTF-16 Java String to UTF-8 bytes.
var i = beginIndex
while (i < endIndex) {
- var c = string[i].toInt()
+ var c = string[i].code
when {
c < 0x80 -> {
@@ -953,7 +992,7 @@ internal inline fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endI
// Fast-path contiguous runs of ASCII characters. This is ugly, but yields a ~4x performance
// improvement over independent calls to writeByte().
while (i < runLimit) {
- c = string[i].toInt()
+ c = string[i].code
if (c >= 0x80) break
data[segmentOffset + i++] = c.toByte() // 0xxxxxxx
}
@@ -992,9 +1031,9 @@ internal inline fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endI
// c is a surrogate. Make sure it is a high surrogate & that its successor is a low
// surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement
// character.
- val low = (if (i + 1 < endIndex) string[i + 1].toInt() else 0)
+ val low = (if (i + 1 < endIndex) string[i + 1].code else 0)
if (c > 0xdbff || low !in 0xdc00..0xdfff) {
- writeByte('?'.toInt())
+ writeByte('?'.code)
i++
} else {
// UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits)
@@ -1039,7 +1078,7 @@ internal inline fun Buffer.commonWriteUtf8CodePoint(codePoint: Int): Buffer {
}
codePoint in 0xd800..0xdfff -> {
// Emit a replacement character for a partial surrogate.
- writeByte('?'.toInt())
+ writeByte('?'.code)
}
codePoint < 0x10000 -> {
// Emit a 16-bit code point with 3 bytes.
@@ -1373,7 +1412,7 @@ internal inline fun Buffer.commonRangeEquals(
offset: Long,
bytes: ByteString,
bytesOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean {
if (offset < 0L ||
bytesOffset < 0 ||
@@ -1507,6 +1546,7 @@ internal inline fun Buffer.commonSnapshot(byteCount: Int): ByteString {
}
internal fun Buffer.commonReadUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor {
+ val unsafeCursor = resolveDefaultParameter(unsafeCursor)
check(unsafeCursor.buffer == null) { "already attached to a buffer" }
unsafeCursor.buffer = this
@@ -1515,6 +1555,7 @@ internal fun Buffer.commonReadUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor {
}
internal fun Buffer.commonReadAndWriteUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor {
+ val unsafeCursor = resolveDefaultParameter(unsafeCursor)
check(unsafeCursor.buffer == null) { "already attached to a buffer" }
unsafeCursor.buffer = this
diff --git a/okio/src/commonMain/kotlin/okio/internal/ByteString.kt b/okio/src/commonMain/kotlin/okio/internal/ByteString.kt
index 7a1a488b..311c17e5 100644
--- a/okio/src/commonMain/kotlin/okio/internal/ByteString.kt
+++ b/okio/src/commonMain/kotlin/okio/internal/ByteString.kt
@@ -13,9 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:JvmName("-ByteString") // A leading '-' hides this class from Java.
package okio.internal
+import kotlin.jvm.JvmName
+import kotlin.native.concurrent.SharedImmutable
import okio.BASE64_URL_SAFE
import okio.Buffer
import okio.ByteString
@@ -28,6 +31,7 @@ import okio.decodeBase64ToArray
import okio.encodeBase64
import okio.isIsoControl
import okio.processUtf8CodePoints
+import okio.resolveDefaultParameter
import okio.shr
import okio.toUtf8String
@@ -51,6 +55,7 @@ internal inline fun ByteString.commonBase64(): String = data.encodeBase64()
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonBase64Url() = data.encodeBase64(map = BASE64_URL_SAFE)
+@SharedImmutable
internal val HEX_DIGIT_CHARS =
charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
@@ -62,7 +67,7 @@ internal inline fun ByteString.commonHex(): String {
result[c++] = HEX_DIGIT_CHARS[b shr 4 and 0xf]
result[c++] = HEX_DIGIT_CHARS[b and 0xf] // ktlint-disable no-multi-spaces
}
- return String(result)
+ return result.concatToString()
}
@Suppress("NOTHING_TO_INLINE")
@@ -71,7 +76,7 @@ internal inline fun ByteString.commonToAsciiLowercase(): ByteString {
var i = 0
while (i < data.size) {
var c = data[i]
- if (c < 'A'.toByte() || c > 'Z'.toByte()) {
+ if (c < 'A'.code.toByte() || c > 'Z'.code.toByte()) {
i++
continue
}
@@ -81,7 +86,7 @@ internal inline fun ByteString.commonToAsciiLowercase(): ByteString {
lowercase[i++] = (c - ('A' - 'a')).toByte()
while (i < lowercase.size) {
c = lowercase[i]
- if (c < 'A'.toByte() || c > 'Z'.toByte()) {
+ if (c < 'A'.code.toByte() || c > 'Z'.code.toByte()) {
i++
continue
}
@@ -99,7 +104,7 @@ internal inline fun ByteString.commonToAsciiUppercase(): ByteString {
var i = 0
while (i < data.size) {
var c = data[i]
- if (c < 'a'.toByte() || c > 'z'.toByte()) {
+ if (c < 'a'.code.toByte() || c > 'z'.code.toByte()) {
i++
continue
}
@@ -109,7 +114,7 @@ internal inline fun ByteString.commonToAsciiUppercase(): ByteString {
lowercase[i++] = (c - ('a' - 'A')).toByte()
while (i < lowercase.size) {
c = lowercase[i]
- if (c < 'a'.toByte() || c > 'z'.toByte()) {
+ if (c < 'a'.code.toByte() || c > 'z'.code.toByte()) {
i++
continue
}
@@ -123,6 +128,7 @@ internal inline fun ByteString.commonToAsciiUppercase(): ByteString {
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonSubstring(beginIndex: Int, endIndex: Int): ByteString {
+ val endIndex = resolveDefaultParameter(endIndex)
require(beginIndex >= 0) { "beginIndex < 0" }
require(endIndex <= data.size) { "endIndex > length(${data.size})" }
@@ -152,7 +158,7 @@ internal inline fun ByteString.commonRangeEquals(
offset: Int,
other: ByteString,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = other.rangeEquals(otherOffset, this.data, offset, byteCount)
@Suppress("NOTHING_TO_INLINE")
@@ -160,7 +166,7 @@ internal inline fun ByteString.commonRangeEquals(
offset: Int,
other: ByteArray,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean {
return (
offset >= 0 && offset <= data.size - byteCount &&
@@ -170,6 +176,16 @@ internal inline fun ByteString.commonRangeEquals(
}
@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonCopyInto(
+ offset: Int,
+ target: ByteArray,
+ targetOffset: Int,
+ byteCount: Int,
+) {
+ data.copyInto(target, targetOffset, offset, offset + byteCount)
+}
+
+@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonStartsWith(prefix: ByteString) =
rangeEquals(0, prefix, 0, prefix.size)
@@ -199,11 +215,12 @@ internal inline fun ByteString.commonIndexOf(other: ByteArray, fromIndex: Int):
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonLastIndexOf(
other: ByteString,
- fromIndex: Int
+ fromIndex: Int,
) = lastIndexOf(other.internalArray(), fromIndex)
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteString.commonLastIndexOf(other: ByteArray, fromIndex: Int): Int {
+ val fromIndex = resolveDefaultParameter(fromIndex)
val limit = data.size - other.size
for (i in minOf(fromIndex, limit) downTo 0) {
if (arrayRangeEquals(data, i, other, 0, other.size)) {
@@ -255,6 +272,7 @@ internal inline fun commonOf(data: ByteArray) = ByteString(data.copyOf())
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteArray.commonToByteString(offset: Int, byteCount: Int): ByteString {
+ val byteCount = resolveDefaultParameter(byteCount)
checkOffsetAndCount(size.toLong(), offset.toLong(), byteCount.toLong())
return ByteString(copyOfRange(offset, offset + byteCount))
}
@@ -332,7 +350,7 @@ private fun codePointIndexToCharIndex(s: ByteArray, codePointCount: Int): Int {
return charCount
}
- if ((c != '\n'.toInt() && c != '\r'.toInt() && isIsoControl(c)) ||
+ if ((c != '\n'.code && c != '\r'.code && isIsoControl(c)) ||
c == REPLACEMENT_CODE_POINT
) {
return -1
diff --git a/okio/src/commonMain/kotlin/okio/internal/FileSystem.kt b/okio/src/commonMain/kotlin/okio/internal/FileSystem.kt
new file mode 100644
index 00000000..72c541d1
--- /dev/null
+++ b/okio/src/commonMain/kotlin/okio/internal/FileSystem.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("-FileSystem") // A leading '-' hides this class from Java.
+
+package okio.internal
+
+import kotlin.jvm.JvmName
+import okio.FileMetadata
+import okio.FileNotFoundException
+import okio.FileSystem
+import okio.IOException
+import okio.Path
+import okio.buffer
+import okio.use
+
+/**
+ * Returns metadata of the file, directory, or object identified by [path].
+ *
+ * @throws IOException if [path] does not exist or its metadata cannot be read.
+ */
+@Throws(IOException::class)
+internal fun FileSystem.commonMetadata(path: Path): FileMetadata {
+ return metadataOrNull(path) ?: throw FileNotFoundException("no such file: $path")
+}
+
+@Throws(IOException::class)
+internal fun FileSystem.commonExists(path: Path): Boolean {
+ return metadataOrNull(path) != null
+}
+
+@Throws(IOException::class)
+internal fun FileSystem.commonCreateDirectories(dir: Path, mustCreate: Boolean) {
+ // Compute the sequence of directories to create.
+ val directories = ArrayDeque<Path>()
+ var path: Path? = dir
+ while (path != null && !exists(path)) {
+ directories.addFirst(path)
+ path = path.parent
+ }
+
+ if (mustCreate && directories.isEmpty()) throw IOException("$dir already exists.")
+
+ // Create them.
+ for (toCreate in directories) {
+ // We know we are creating new directories by now so we don't have to pass down `mustCreate`.
+ createDirectory(toCreate)
+ }
+}
+
+@Throws(IOException::class)
+internal fun FileSystem.commonCopy(source: Path, target: Path) {
+ source(source).use { bytesIn ->
+ sink(target).buffer().use { bytesOut ->
+ bytesOut.writeAll(bytesIn)
+ }
+ }
+}
+
+@Throws(IOException::class)
+internal fun FileSystem.commonDeleteRecursively(fileOrDirectory: Path, mustExist: Boolean) {
+ val sequence = sequence {
+ collectRecursively(
+ fileSystem = this@commonDeleteRecursively,
+ stack = ArrayDeque(),
+ path = fileOrDirectory,
+ followSymlinks = false,
+ postorder = true,
+ )
+ }
+ val iterator = sequence.iterator()
+ while (iterator.hasNext()) {
+ val toDelete = iterator.next()
+ delete(toDelete, mustExist = mustExist && !iterator.hasNext())
+ }
+}
+
+@Throws(IOException::class)
+internal fun FileSystem.commonListRecursively(dir: Path, followSymlinks: Boolean): Sequence<Path> {
+ return sequence {
+ val stack = ArrayDeque<Path>()
+ stack.addLast(dir)
+ for (child in list(dir)) {
+ collectRecursively(
+ fileSystem = this@commonListRecursively,
+ stack = stack,
+ path = child,
+ followSymlinks = followSymlinks,
+ postorder = false,
+ )
+ }
+ }
+}
+
+internal suspend fun SequenceScope<Path>.collectRecursively(
+ fileSystem: FileSystem,
+ stack: ArrayDeque<Path>,
+ path: Path,
+ followSymlinks: Boolean,
+ postorder: Boolean,
+) {
+ // For listRecursively, visit enclosing directory first.
+ if (!postorder) {
+ yield(path)
+ }
+
+ val children = fileSystem.listOrNull(path) ?: listOf()
+ if (children.isNotEmpty()) {
+ // Figure out if path is a symlink and detect symlink cycles.
+ var symlinkPath = path
+ var symlinkCount = 0
+ while (true) {
+ if (followSymlinks && symlinkPath in stack) throw IOException("symlink cycle at $path")
+ symlinkPath = fileSystem.symlinkTarget(symlinkPath) ?: break
+ symlinkCount++
+ }
+
+ // Recursively visit children.
+ if (followSymlinks || symlinkCount == 0) {
+ stack.addLast(symlinkPath)
+ try {
+ for (child in children) {
+ collectRecursively(fileSystem, stack, child, followSymlinks, postorder)
+ }
+ } finally {
+ stack.removeLast()
+ }
+ }
+ }
+
+ // For deleteRecursively, visit enclosing directory last.
+ if (postorder) {
+ yield(path)
+ }
+}
+
+/** Returns a resolved path to the symlink target, resolving it if necessary. */
+@Throws(IOException::class)
+internal fun FileSystem.symlinkTarget(path: Path): Path? {
+ val target = metadata(path).symlinkTarget ?: return null
+ return path.parent!!.div(target)
+}
diff --git a/okio/src/commonMain/kotlin/okio/internal/Path.kt b/okio/src/commonMain/kotlin/okio/internal/Path.kt
new file mode 100644
index 00000000..6910e30b
--- /dev/null
+++ b/okio/src/commonMain/kotlin/okio/internal/Path.kt
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("-Path") // A leading '-' hides this class from Java.
+
+package okio.internal
+
+import kotlin.jvm.JvmName
+import kotlin.native.concurrent.SharedImmutable
+import okio.Buffer
+import okio.ByteString
+import okio.ByteString.Companion.encodeUtf8
+import okio.Path
+import okio.Path.Companion.toPath
+
+@SharedImmutable
+private val SLASH = "/".encodeUtf8()
+
+@SharedImmutable
+private val BACKSLASH = "\\".encodeUtf8()
+
+@SharedImmutable
+private val ANY_SLASH = "/\\".encodeUtf8()
+
+@SharedImmutable
+private val DOT = ".".encodeUtf8()
+
+@SharedImmutable
+private val DOT_DOT = "..".encodeUtf8()
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonRoot(): Path? {
+ return when (val rootLength = rootLength()) {
+ -1 -> null
+ else -> Path(bytes.substring(0, rootLength))
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonSegments(): List<String> {
+ return commonSegmentsBytes().map { it.utf8() }
+}
+
+/** This function skips the root then splits on slash. */
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonSegmentsBytes(): List<ByteString> {
+ val result = mutableListOf<ByteString>()
+ var segmentStart = rootLength()
+
+ // segmentStart should always follow a `\`, but for UNC paths it doesn't.
+ if (segmentStart == -1) {
+ segmentStart = 0
+ } else if (segmentStart < bytes.size && bytes[segmentStart] == '\\'.code.toByte()) {
+ segmentStart++
+ }
+
+ for (i in segmentStart until bytes.size) {
+ if (bytes[i] == '/'.code.toByte() || bytes[i] == '\\'.code.toByte()) {
+ result += bytes.substring(segmentStart, i)
+ segmentStart = i + 1
+ }
+ }
+
+ if (segmentStart < bytes.size) {
+ result += bytes.substring(segmentStart, bytes.size)
+ }
+
+ return result
+}
+
+/** Return the length of the prefix of this that is the root path, or -1 if it has no root. */
+private fun Path.rootLength(): Int {
+ if (bytes.size == 0) return -1
+ if (bytes[0] == '/'.code.toByte()) return 1
+
+ if (bytes[0] == '\\'.code.toByte()) {
+ if (bytes.size > 2 && bytes[1] == '\\'.code.toByte()) {
+ // Look for a root like `\\localhost`.
+ var uncRootEnd = bytes.indexOf(BACKSLASH, fromIndex = 2)
+ if (uncRootEnd == -1) uncRootEnd = bytes.size
+ return uncRootEnd
+ }
+
+ // We found a root like `\`.
+ return 1
+ }
+
+ // Look for a root like `C:\`.
+ if (bytes.size > 2 && bytes[1] == ':'.code.toByte() && bytes[2] == '\\'.code.toByte()) {
+ val c = bytes[0].toInt().toChar()
+ if (c !in 'a'..'z' && c !in 'A'..'Z') return -1
+ return 3
+ }
+
+ return -1
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonIsAbsolute(): Boolean {
+ return rootLength() != -1
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonIsRelative(): Boolean {
+ return rootLength() == -1
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonVolumeLetter(): Char? {
+ if (bytes.indexOf(SLASH) != -1) return null
+ if (bytes.size < 2) return null
+ if (bytes[1] != ':'.code.toByte()) return null
+ val c = bytes[0].toInt().toChar()
+ if (c !in 'a'..'z' && c !in 'A'..'Z') return null
+ return c
+}
+
+private val Path.indexOfLastSlash: Int
+ get() {
+ val lastSlash = bytes.lastIndexOf(SLASH)
+ if (lastSlash != -1) return lastSlash
+ return bytes.lastIndexOf(BACKSLASH)
+ }
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonNameBytes(): ByteString {
+ val lastSlash = indexOfLastSlash
+ return when {
+ lastSlash != -1 -> bytes.substring(lastSlash + 1)
+ volumeLetter != null && bytes.size == 2 -> ByteString.EMPTY // "C:" has no name.
+ else -> bytes
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonName(): String {
+ return nameBytes.utf8()
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonParent(): Path? {
+ if (bytes == DOT || bytes == SLASH || bytes == BACKSLASH || lastSegmentIsDotDot()) {
+ return null // Terminal path.
+ }
+
+ val lastSlash = indexOfLastSlash
+ when {
+ lastSlash == 2 && volumeLetter != null -> {
+ if (bytes.size == 3) return null // "C:\" has no parent.
+ return Path(bytes.substring(endIndex = 3)) // Keep the trailing '\' in C:\.
+ }
+ lastSlash == 1 && bytes.startsWith(BACKSLASH) -> {
+ return null // "\\server" is a UNC path with no parent.
+ }
+ lastSlash == -1 && volumeLetter != null -> {
+ if (bytes.size == 2) return null // "C:" has no parent.
+ return Path(bytes.substring(endIndex = 2)) // C: is volume-relative.
+ }
+ lastSlash == -1 -> {
+ return Path(DOT) // Parent is the current working directory.
+ }
+ lastSlash == 0 -> {
+ return Path(bytes.substring(endIndex = 1)) // Parent is the filesystem root '/'.
+ }
+ else -> {
+ return Path(bytes.substring(endIndex = lastSlash))
+ }
+ }
+}
+
+private fun Path.lastSegmentIsDotDot(): Boolean {
+ if (bytes.endsWith(DOT_DOT)) {
+ if (bytes.size == 2) return true // ".." is the whole string.
+ if (bytes.rangeEquals(bytes.size - 3, SLASH, 0, 1)) return true // Ends with "/..".
+ if (bytes.rangeEquals(bytes.size - 3, BACKSLASH, 0, 1)) return true // Ends with "\..".
+ }
+ return false
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonIsRoot(): Boolean {
+ return rootLength() == bytes.size
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonResolve(child: String, normalize: Boolean): Path {
+ return commonResolve(Buffer().writeUtf8(child), normalize = normalize)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonResolve(child: ByteString, normalize: Boolean): Path {
+ return commonResolve(Buffer().write(child), normalize = normalize)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonResolve(child: Buffer, normalize: Boolean): Path {
+ return commonResolve(child.toPath(normalize = false), normalize = normalize)
+}
+
+internal fun Path.commonResolve(child: Path, normalize: Boolean): Path {
+ if (child.isAbsolute || child.volumeLetter != null) return child
+
+ val slash = slash ?: child.slash ?: Path.DIRECTORY_SEPARATOR.toSlash()
+
+ val buffer = Buffer()
+ buffer.write(bytes)
+ if (buffer.size > 0) {
+ buffer.write(slash)
+ }
+ buffer.write(child.bytes)
+ return buffer.toPath(normalize = normalize)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonRelativeTo(other: Path): Path {
+ require(root == other.root) {
+ "Paths of different roots cannot be relative to each other: $this and $other"
+ }
+
+ val thisSegments = this.segmentsBytes
+ val otherSegments = other.segmentsBytes
+
+ // We look at the path both have in common.
+ var firstNewSegmentIndex = 0
+ val minSegmentsSize = minOf(thisSegments.size, otherSegments.size)
+ while (firstNewSegmentIndex < minSegmentsSize &&
+ thisSegments[firstNewSegmentIndex] == otherSegments[firstNewSegmentIndex]
+ ) {
+ firstNewSegmentIndex++
+ }
+
+ if (firstNewSegmentIndex == minSegmentsSize && bytes.size == other.bytes.size) {
+ // `this` and `other` are the same path.
+ return ".".toPath()
+ }
+
+ require(otherSegments.subList(firstNewSegmentIndex, otherSegments.size).indexOf(DOT_DOT) == -1) {
+ "Impossible relative path to resolve: $this and $other"
+ }
+
+ val buffer = Buffer()
+ val slash = other.slash ?: slash ?: Path.DIRECTORY_SEPARATOR.toSlash()
+ for (i in firstNewSegmentIndex until otherSegments.size) {
+ buffer.write(DOT_DOT)
+ buffer.write(slash)
+ }
+ for (i in firstNewSegmentIndex until thisSegments.size) {
+ buffer.write(thisSegments[i])
+ buffer.write(slash)
+ }
+ return buffer.toPath(normalize = false)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonNormalized(): Path {
+ return toString().toPath(normalize = true)
+}
+
+private val Path.slash: ByteString?
+ get() {
+ return when {
+ bytes.indexOf(SLASH) != -1 -> SLASH
+ bytes.indexOf(BACKSLASH) != -1 -> BACKSLASH
+ else -> null
+ }
+ }
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonCompareTo(other: Path): Int {
+ return bytes.compareTo(other.bytes)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonEquals(other: Any?): Boolean {
+ return other is Path && other.bytes == bytes
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonHashCode(): Int {
+ return bytes.hashCode()
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun Path.commonToString(): String {
+ return bytes.utf8()
+}
+
+internal fun String.commonToPath(normalize: Boolean): Path {
+ return Buffer().writeUtf8(this).toPath(normalize)
+}
+
+/** Consume the buffer and return it as a path. */
+internal fun Buffer.toPath(normalize: Boolean): Path {
+ var slash: ByteString? = null
+ val result = Buffer()
+
+ // Consume the absolute path prefix, like `/`, `\\`, `C:`, or `C:\` and write the
+ // canonicalized prefix to result.
+ var leadingSlashCount = 0
+ while (rangeEquals(0L, SLASH) || rangeEquals(0L, BACKSLASH)) {
+ val byte = readByte()
+ slash = slash ?: byte.toSlash()
+ leadingSlashCount++
+ }
+ val windowsUncPath = leadingSlashCount >= 2 && slash == BACKSLASH
+ if (windowsUncPath) {
+ // This is a Windows UNC path, like \\server\directory\file.txt.
+ result.write(slash!!)
+ result.write(slash)
+ } else if (leadingSlashCount > 0) {
+ // This is platform-dependent:
+ // * On UNIX: an absolute path like /home
+ // * On Windows: this is relative to the current volume, like \Windows.
+ result.write(slash!!)
+ } else {
+ // This path doesn't start with any slash. We must initialize the slash character to use.
+ val limit = indexOfElement(ANY_SLASH)
+ slash = slash ?: when (limit) {
+ -1L -> Path.DIRECTORY_SEPARATOR.toSlash()
+ else -> get(limit).toSlash()
+ }
+ if (startsWithVolumeLetterAndColon(slash)) {
+ if (limit == 2L) {
+ result.write(this, 3L) // Absolute on a named volume, like `C:\`.
+ } else {
+ result.write(this, 2L) // Relative to the named volume, like `C:`.
+ }
+ }
+ }
+
+ val absolute = result.size > 0
+
+ val canonicalParts = mutableListOf<ByteString>()
+ while (!exhausted()) {
+ val limit = indexOfElement(ANY_SLASH)
+
+ val part: ByteString
+ if (limit == -1L) {
+ part = readByteString()
+ } else {
+ part = readByteString(limit)
+ readByte()
+ }
+
+ if (part == DOT_DOT) {
+ if (absolute && canonicalParts.isEmpty()) {
+ // Silently consume `..`.
+ } else if (!normalize || !absolute && (canonicalParts.isEmpty() || canonicalParts.last() == DOT_DOT)) {
+ canonicalParts.add(part) // '..' doesn't pop '..' for relative paths.
+ } else if (windowsUncPath && canonicalParts.size == 1) {
+ // `..` doesn't pop UNC hostnames.
+ } else {
+ canonicalParts.removeLastOrNull()
+ }
+ } else if (part != DOT && part != ByteString.EMPTY) {
+ canonicalParts.add(part)
+ }
+ }
+
+ for (i in 0 until canonicalParts.size) {
+ if (i > 0) result.write(slash)
+ result.write(canonicalParts[i])
+ }
+ if (result.size == 0L) {
+ result.write(DOT)
+ }
+
+ return Path(result.readByteString())
+}
+
+private fun String.toSlash(): ByteString {
+ return when (this) {
+ "/" -> SLASH
+ "\\" -> BACKSLASH
+ else -> throw IllegalArgumentException("not a directory separator: $this")
+ }
+}
+
+private fun Byte.toSlash(): ByteString {
+ return when (toInt()) {
+ '/'.code -> SLASH
+ '\\'.code -> BACKSLASH
+ else -> throw IllegalArgumentException("not a directory separator: $this")
+ }
+}
+
+private fun Buffer.startsWithVolumeLetterAndColon(slash: ByteString): Boolean {
+ if (slash != BACKSLASH) return false
+ if (size < 2) return false
+ if (get(1) != ':'.code.toByte()) return false
+ val b = get(0).toInt().toChar()
+ return b in 'a'..'z' || b in 'A'..'Z'
+}
diff --git a/okio/src/commonMain/kotlin/okio/internal/RealBufferedSink.kt b/okio/src/commonMain/kotlin/okio/internal/RealBufferedSink.kt
index 49b0c4d7..d03216ee 100644
--- a/okio/src/commonMain/kotlin/okio/internal/RealBufferedSink.kt
+++ b/okio/src/commonMain/kotlin/okio/internal/RealBufferedSink.kt
@@ -17,8 +17,11 @@
// TODO move to RealBufferedSink class: https://youtrack.jetbrains.com/issue/KT-20427
@file:Suppress("NOTHING_TO_INLINE")
+@file:JvmName("-RealBufferedSink") // A leading '-' hides this class from Java.
+
package okio.internal
+import kotlin.jvm.JvmName
import okio.Buffer
import okio.BufferedSink
import okio.ByteString
@@ -42,7 +45,7 @@ internal inline fun RealBufferedSink.commonWrite(byteString: ByteString): Buffer
internal inline fun RealBufferedSink.commonWrite(
byteString: ByteString,
offset: Int,
- byteCount: Int
+ byteCount: Int,
): BufferedSink {
check(!closed) { "closed" }
buffer.write(byteString, offset, byteCount)
@@ -58,7 +61,7 @@ internal inline fun RealBufferedSink.commonWriteUtf8(string: String): BufferedSi
internal inline fun RealBufferedSink.commonWriteUtf8(
string: String,
beginIndex: Int,
- endIndex: Int
+ endIndex: Int,
): BufferedSink {
check(!closed) { "closed" }
buffer.writeUtf8(string, beginIndex, endIndex)
@@ -80,7 +83,7 @@ internal inline fun RealBufferedSink.commonWrite(source: ByteArray): BufferedSin
internal inline fun RealBufferedSink.commonWrite(
source: ByteArray,
offset: Int,
- byteCount: Int
+ byteCount: Int,
): BufferedSink {
check(!closed) { "closed" }
buffer.write(source, offset, byteCount)
diff --git a/okio/src/commonMain/kotlin/okio/internal/RealBufferedSource.kt b/okio/src/commonMain/kotlin/okio/internal/RealBufferedSource.kt
index 4b901437..5b0d55b2 100644
--- a/okio/src/commonMain/kotlin/okio/internal/RealBufferedSource.kt
+++ b/okio/src/commonMain/kotlin/okio/internal/RealBufferedSource.kt
@@ -17,8 +17,11 @@
// TODO move to RealBufferedSource class: https://youtrack.jetbrains.com/issue/KT-20427
@file:Suppress("NOTHING_TO_INLINE")
+@file:JvmName("-RealBufferedSource") // A leading '-' hides this class from Java.
+
package okio.internal
+import kotlin.jvm.JvmName
import okio.Buffer
import okio.BufferedSource
import okio.ByteString
@@ -178,7 +181,7 @@ internal inline fun RealBufferedSource.commonReadUtf8(byteCount: Long): String {
}
internal inline fun RealBufferedSource.commonReadUtf8Line(): String? {
- val newline = indexOf('\n'.toByte())
+ val newline = indexOf('\n'.code.toByte())
return if (newline == -1L) {
if (buffer.size != 0L) {
@@ -194,11 +197,11 @@ internal inline fun RealBufferedSource.commonReadUtf8Line(): String? {
internal inline fun RealBufferedSource.commonReadUtf8LineStrict(limit: Long): String {
require(limit >= 0) { "limit < 0: $limit" }
val scanLength = if (limit == Long.MAX_VALUE) Long.MAX_VALUE else limit + 1
- val newline = indexOf('\n'.toByte(), 0, scanLength)
+ val newline = indexOf('\n'.code.toByte(), 0, scanLength)
if (newline != -1L) return buffer.readUtf8Line(newline)
if (scanLength < Long.MAX_VALUE &&
- request(scanLength) && buffer[scanLength - 1] == '\r'.toByte() &&
- request(scanLength + 1) && buffer[scanLength] == '\n'.toByte()
+ request(scanLength) && buffer[scanLength - 1] == '\r'.code.toByte() &&
+ request(scanLength + 1) && buffer[scanLength] == '\n'.code.toByte()
) {
return buffer.readUtf8Line(scanLength) // The line was 'limit' UTF-8 bytes followed by \r\n.
}
@@ -206,7 +209,7 @@ internal inline fun RealBufferedSource.commonReadUtf8LineStrict(limit: Long): St
buffer.copyTo(data, 0, okio.minOf(32, buffer.size))
throw EOFException(
"\\n not found: limit=" + minOf(buffer.size, limit) +
- " content=" + data.readByteString().hex() + '…'.toString()
+ " content=" + data.readByteString().hex() + '…'.toString(),
)
}
@@ -259,10 +262,10 @@ internal inline fun RealBufferedSource.commonReadDecimalLong(): Long {
var pos = 0L
while (request(pos + 1)) {
val b = buffer[pos]
- if ((b < '0'.toByte() || b > '9'.toByte()) && (pos != 0L || b != '-'.toByte())) {
+ if ((b < '0'.code.toByte() || b > '9'.code.toByte()) && (pos != 0L || b != '-'.code.toByte())) {
// Non-digit, or non-leading negative sign.
if (pos == 0L) {
- throw NumberFormatException("Expected leading [0-9] or '-' character but was 0x${b.toString(16)}")
+ throw NumberFormatException("Expected a digit or '-' but was 0x${b.toString(16)}")
}
break
}
@@ -278,9 +281,9 @@ internal inline fun RealBufferedSource.commonReadHexadecimalUnsignedLong(): Long
var pos = 0
while (request((pos + 1).toLong())) {
val b = buffer[pos.toLong()]
- if ((b < '0'.toByte() || b > '9'.toByte()) &&
- (b < 'a'.toByte() || b > 'f'.toByte()) &&
- (b < 'A'.toByte() || b > 'F'.toByte())
+ if ((b < '0'.code.toByte() || b > '9'.code.toByte()) &&
+ (b < 'a'.code.toByte() || b > 'f'.code.toByte()) &&
+ (b < 'A'.code.toByte() || b > 'F'.code.toByte())
) {
// Non-digit, or non-leading negative sign.
if (pos == 0) {
@@ -363,7 +366,7 @@ internal inline fun RealBufferedSource.commonRangeEquals(
offset: Long,
bytes: ByteString,
bytesOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean {
check(!closed) { "closed" }
diff --git a/okio/src/commonMain/kotlin/okio/internal/SegmentedByteString.kt b/okio/src/commonMain/kotlin/okio/internal/SegmentedByteString.kt
index f46e1389..dc3edd5b 100644
--- a/okio/src/commonMain/kotlin/okio/internal/SegmentedByteString.kt
+++ b/okio/src/commonMain/kotlin/okio/internal/SegmentedByteString.kt
@@ -17,14 +17,18 @@
// TODO move to SegmentedByteString class: https://youtrack.jetbrains.com/issue/KT-20427
@file:Suppress("NOTHING_TO_INLINE")
+@file:JvmName("-SegmentedByteString") // A leading '-' hides this class from Java.
+
package okio.internal
+import kotlin.jvm.JvmName
import okio.Buffer
import okio.ByteString
import okio.Segment
import okio.SegmentedByteString
import okio.arrayRangeEquals
import okio.checkOffsetAndCount
+import okio.resolveDefaultParameter
internal fun IntArray.binarySearch(value: Int, fromIndex: Int, toIndex: Int): Int {
var left = fromIndex
@@ -54,7 +58,7 @@ internal fun SegmentedByteString.segment(pos: Int): Int {
/** Processes all segments, invoking `action` with the ByteArray and range of valid data. */
internal inline fun SegmentedByteString.forEachSegment(
- action: (data: ByteArray, offset: Int, byteCount: Int) -> Unit
+ action: (data: ByteArray, offset: Int, byteCount: Int) -> Unit,
) {
val segmentCount = segments.size
var s = 0
@@ -76,7 +80,7 @@ internal inline fun SegmentedByteString.forEachSegment(
private inline fun SegmentedByteString.forEachSegment(
beginIndex: Int,
endIndex: Int,
- action: (data: ByteArray, offset: Int, byteCount: Int) -> Unit
+ action: (data: ByteArray, offset: Int, byteCount: Int) -> Unit,
) {
var s = segment(beginIndex)
var pos = beginIndex
@@ -97,6 +101,7 @@ private inline fun SegmentedByteString.forEachSegment(
// have to call these functions. Remove all this nonsense when expect class allow actual code.
internal inline fun SegmentedByteString.commonSubstring(beginIndex: Int, endIndex: Int): ByteString {
+ val endIndex = resolveDefaultParameter(endIndex)
require(beginIndex >= 0) { "beginIndex=$beginIndex < 0" }
require(endIndex <= size) { "endIndex=$endIndex > length($size)" }
@@ -141,8 +146,10 @@ internal inline fun SegmentedByteString.commonToByteArray(): ByteArray {
var resultPos = 0
forEachSegment { data, offset, byteCount ->
data.copyInto(
- result, destinationOffset = resultPos, startIndex = offset,
- endIndex = offset + byteCount
+ result,
+ destinationOffset = resultPos,
+ startIndex = offset,
+ endIndex = offset + byteCount,
)
resultPos += byteCount
}
@@ -167,7 +174,7 @@ internal inline fun SegmentedByteString.commonRangeEquals(
offset: Int,
other: ByteString,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean {
if (offset < 0 || offset > size - byteCount) return false
// Go segment-by-segment through this, passing arrays to other's rangeEquals().
@@ -183,7 +190,7 @@ internal inline fun SegmentedByteString.commonRangeEquals(
offset: Int,
other: ByteArray,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean {
if (offset < 0 || offset > size - byteCount ||
otherOffset < 0 || otherOffset > other.size - byteCount
@@ -199,6 +206,22 @@ internal inline fun SegmentedByteString.commonRangeEquals(
return true
}
+internal inline fun SegmentedByteString.commonCopyInto(
+ offset: Int,
+ target: ByteArray,
+ targetOffset: Int,
+ byteCount: Int,
+) {
+ checkOffsetAndCount(size.toLong(), offset.toLong(), byteCount.toLong())
+ checkOffsetAndCount(target.size.toLong(), targetOffset.toLong(), byteCount.toLong())
+ // Go segment-by-segment through this, copying ranges of arrays.
+ var targetOffset = targetOffset
+ forEachSegment(offset, offset + byteCount) { data, offset, byteCount ->
+ data.copyInto(target, targetOffset, offset, offset + byteCount)
+ targetOffset += byteCount
+ }
+}
+
internal inline fun SegmentedByteString.commonEquals(other: Any?): Boolean {
return when {
other === this -> true
diff --git a/okio/src/commonTest/kotlin/okio/AbstractBufferedSinkTest.kt b/okio/src/commonTest/kotlin/okio/AbstractBufferedSinkTest.kt
index 49bff8d9..e922d5f2 100644
--- a/okio/src/commonTest/kotlin/okio/AbstractBufferedSinkTest.kt
+++ b/okio/src/commonTest/kotlin/okio/AbstractBufferedSinkTest.kt
@@ -16,18 +16,17 @@
package okio
-import okio.ByteString.Companion.decodeHex
-import okio.ByteString.Companion.encodeUtf8
-import kotlin.math.pow
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
+import okio.ByteString.Companion.decodeHex
+import okio.ByteString.Companion.encodeUtf8
class BufferSinkTest : AbstractBufferedSinkTest(BufferedSinkFactory.BUFFER)
class RealBufferedSinkTest : AbstractBufferedSinkTest(BufferedSinkFactory.REAL_BUFFERED_SINK)
abstract class AbstractBufferedSinkTest internal constructor(
- factory: BufferedSinkFactory
+ factory: BufferedSinkFactory,
) {
private val data: Buffer = Buffer()
private val sink: BufferedSink = factory.create(data)
@@ -217,26 +216,60 @@ abstract class AbstractBufferedSinkTest internal constructor(
}
@Test fun closeEmitsBufferedBytes() {
- sink.writeByte('a'.toInt())
+ sink.writeByte('a'.code)
sink.close()
- assertEquals('a', data.readByte().toChar())
+ assertEquals('a', data.readByte().toInt().toChar())
}
+ /**
+ * This test hard codes the results of Long.toString() because that function rounds large values
+ * when using Kotlin/JS IR. https://youtrack.jetbrains.com/issue/KT-39891
+ */
@Test fun longDecimalString() {
- assertLongDecimalString(0)
- assertLongDecimalString(Long.MIN_VALUE)
- assertLongDecimalString(Long.MAX_VALUE)
-
- for (i in 1..19) {
- val value = 10.0.pow(i).toLong()
- assertLongDecimalString(value - 1)
- assertLongDecimalString(value)
- }
+ assertLongDecimalString("0", 0)
+ assertLongDecimalString("-9223372036854775808", Long.MIN_VALUE)
+ assertLongDecimalString("9223372036854775807", Long.MAX_VALUE)
+ assertLongDecimalString("9", 9L)
+ assertLongDecimalString("99", 99L)
+ assertLongDecimalString("999", 999L)
+ assertLongDecimalString("9999", 9999L)
+ assertLongDecimalString("99999", 99999L)
+ assertLongDecimalString("999999", 999999L)
+ assertLongDecimalString("9999999", 9999999L)
+ assertLongDecimalString("99999999", 99999999L)
+ assertLongDecimalString("999999999", 999999999L)
+ assertLongDecimalString("9999999999", 9999999999L)
+ assertLongDecimalString("99999999999", 99999999999L)
+ assertLongDecimalString("999999999999", 999999999999L)
+ assertLongDecimalString("9999999999999", 9999999999999L)
+ assertLongDecimalString("99999999999999", 99999999999999L)
+ assertLongDecimalString("999999999999999", 999999999999999L)
+ assertLongDecimalString("9999999999999999", 9999999999999999L)
+ assertLongDecimalString("99999999999999999", 99999999999999999L)
+ assertLongDecimalString("999999999999999999", 999999999999999999L)
+ assertLongDecimalString("10", 10L)
+ assertLongDecimalString("100", 100L)
+ assertLongDecimalString("1000", 1000L)
+ assertLongDecimalString("10000", 10000L)
+ assertLongDecimalString("100000", 100000L)
+ assertLongDecimalString("1000000", 1000000L)
+ assertLongDecimalString("10000000", 10000000L)
+ assertLongDecimalString("100000000", 100000000L)
+ assertLongDecimalString("1000000000", 1000000000L)
+ assertLongDecimalString("10000000000", 10000000000L)
+ assertLongDecimalString("100000000000", 100000000000L)
+ assertLongDecimalString("1000000000000", 1000000000000L)
+ assertLongDecimalString("10000000000000", 10000000000000L)
+ assertLongDecimalString("100000000000000", 100000000000000L)
+ assertLongDecimalString("1000000000000000", 1000000000000000L)
+ assertLongDecimalString("10000000000000000", 10000000000000000L)
+ assertLongDecimalString("100000000000000000", 100000000000000000L)
+ assertLongDecimalString("1000000000000000000", 1000000000000000000L)
}
- private fun assertLongDecimalString(value: Long) {
+ private fun assertLongDecimalString(string: String, value: Long) {
sink.writeDecimalLong(value).writeUtf8("zzz").flush()
- val expected = "${value}zzz"
+ val expected = "${string}zzz"
val actual = data.readUtf8()
assertEquals(expected, actual, "$value expected $expected but was $actual")
}
diff --git a/okio/src/commonTest/kotlin/okio/AbstractBufferedSourceTest.kt b/okio/src/commonTest/kotlin/okio/AbstractBufferedSourceTest.kt
index b15d369c..70d76cb6 100644
--- a/okio/src/commonTest/kotlin/okio/AbstractBufferedSourceTest.kt
+++ b/okio/src/commonTest/kotlin/okio/AbstractBufferedSourceTest.kt
@@ -16,14 +16,13 @@
package okio
-import okio.ByteString.Companion.decodeHex
-import okio.ByteString.Companion.encodeUtf8
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
-import kotlin.test.fail
+import okio.ByteString.Companion.decodeHex
+import okio.ByteString.Companion.encodeUtf8
class BufferSourceTest : AbstractBufferedSourceTest(BufferedSourceFactory.BUFFER)
class RealBufferedSourceTest : AbstractBufferedSourceTest(BufferedSourceFactory.REAL_BUFFERED_SOURCE)
@@ -33,7 +32,7 @@ class PeekBufferTest : AbstractBufferedSourceTest(BufferedSourceFactory.PEEK_BUF
class PeekBufferedSourceTest : AbstractBufferedSourceTest(BufferedSourceFactory.PEEK_BUFFERED_SOURCE)
abstract class AbstractBufferedSourceTest internal constructor(
- private val factory: BufferedSourceFactory
+ private val factory: BufferedSourceFactory,
) {
private val sink: BufferedSink
private val source: BufferedSource
@@ -111,8 +110,8 @@ abstract class AbstractBufferedSourceTest internal constructor(
0x87.toByte(),
0x65.toByte(),
0x43.toByte(),
- 0x21.toByte()
- )
+ 0x21.toByte(),
+ ),
)
sink.emit()
assertEquals(-0x543210ff, source.readInt().toLong())
@@ -130,8 +129,8 @@ abstract class AbstractBufferedSourceTest internal constructor(
0x87.toByte(),
0x65.toByte(),
0x43.toByte(),
- 0x21.toByte()
- )
+ 0x21.toByte(),
+ ),
)
sink.emit()
assertEquals(0x10efcdab, source.readIntLe().toLong())
@@ -184,8 +183,8 @@ abstract class AbstractBufferedSourceTest internal constructor(
0x12.toByte(),
0x23.toByte(),
0x34.toByte(),
- 0x45.toByte()
- )
+ 0x45.toByte(),
+ ),
)
sink.emit()
assertEquals(-0x543210ef789abcdfL, source.readLong())
@@ -211,8 +210,8 @@ abstract class AbstractBufferedSourceTest internal constructor(
0x12.toByte(),
0x23.toByte(),
0x34.toByte(),
- 0x45.toByte()
- )
+ 0x45.toByte(),
+ ),
)
sink.emit()
assertEquals(0x2143658710efcdabL, source.readLongLe())
@@ -231,8 +230,8 @@ abstract class AbstractBufferedSourceTest internal constructor(
0x87.toByte(),
0x65.toByte(),
0x43.toByte(),
- 0x21.toByte()
- )
+ 0x21.toByte(),
+ ),
)
sink.emit()
source.skip((Segment.SIZE - 7).toLong())
@@ -341,14 +340,14 @@ abstract class AbstractBufferedSourceTest internal constructor(
// Verify we read all that we could from the source.
assertArrayEquals(
byteArrayOf(
- 'H'.toByte(),
- 'e'.toByte(),
- 'l'.toByte(),
- 'l'.toByte(),
- 'o'.toByte(),
- 0
+ 'H'.code.toByte(),
+ 'e'.code.toByte(),
+ 'l'.code.toByte(),
+ 'l'.code.toByte(),
+ 'o'.code.toByte(),
+ 0,
),
- array
+ array,
)
}
@@ -360,11 +359,11 @@ abstract class AbstractBufferedSourceTest internal constructor(
val read = source.read(sink)
if (factory.isOneByteAtATime) {
assertEquals(1, read.toLong())
- val expected = byteArrayOf('a'.toByte(), 0, 0)
+ val expected = byteArrayOf('a'.code.toByte(), 0, 0)
assertArrayEquals(expected, sink)
} else {
assertEquals(3, read.toLong())
- val expected = byteArrayOf('a'.toByte(), 'b'.toByte(), 'c'.toByte())
+ val expected = byteArrayOf('a'.code.toByte(), 'b'.code.toByte(), 'c'.code.toByte())
assertArrayEquals(expected, sink)
}
}
@@ -377,11 +376,12 @@ abstract class AbstractBufferedSourceTest internal constructor(
val read = source.read(sink)
if (factory.isOneByteAtATime) {
assertEquals(1, read.toLong())
- val expected = byteArrayOf('a'.toByte(), 0, 0, 0, 0)
+ val expected = byteArrayOf('a'.code.toByte(), 0, 0, 0, 0)
assertArrayEquals(expected, sink)
} else {
assertEquals(4, read.toLong())
- val expected = byteArrayOf('a'.toByte(), 'b'.toByte(), 'c'.toByte(), 'd'.toByte(), 0)
+ val expected =
+ byteArrayOf('a'.code.toByte(), 'b'.code.toByte(), 'c'.code.toByte(), 'd'.code.toByte(), 0)
assertArrayEquals(expected, sink)
}
}
@@ -394,11 +394,12 @@ abstract class AbstractBufferedSourceTest internal constructor(
val read = source.read(sink, 2, 3)
if (factory.isOneByteAtATime) {
assertEquals(1, read.toLong())
- val expected = byteArrayOf(0, 0, 'a'.toByte(), 0, 0, 0, 0)
+ val expected = byteArrayOf(0, 0, 'a'.code.toByte(), 0, 0, 0, 0)
assertArrayEquals(expected, sink)
} else {
assertEquals(3, read.toLong())
- val expected = byteArrayOf(0, 0, 'a'.toByte(), 'b'.toByte(), 'c'.toByte(), 0, 0)
+ val expected =
+ byteArrayOf(0, 0, 'a'.code.toByte(), 'b'.code.toByte(), 'c'.code.toByte(), 0, 0)
assertArrayEquals(expected, sink)
}
}
@@ -491,9 +492,9 @@ abstract class AbstractBufferedSourceTest internal constructor(
sink.writeUtf8("c")
sink.emit()
source.skip(1)
- assertEquals('b'.toLong(), (source.readByte() and 0xff).toLong())
+ assertEquals('b'.code.toLong(), (source.readByte() and 0xff).toLong())
source.skip((Segment.SIZE - 2).toLong())
- assertEquals('b'.toLong(), (source.readByte() and 0xff).toLong())
+ assertEquals('b'.code.toLong(), (source.readByte() and 0xff).toLong())
source.skip(1)
assertTrue(source.exhausted())
}
@@ -501,7 +502,6 @@ abstract class AbstractBufferedSourceTest internal constructor(
@Test fun skipInsufficientData() {
sink.writeUtf8("a")
sink.emit()
-
assertFailsWith<EOFException> {
source.skip(2)
}
@@ -509,61 +509,61 @@ abstract class AbstractBufferedSourceTest internal constructor(
@Test fun indexOf() {
// The segment is empty.
- assertEquals(-1, source.indexOf('a'.toByte()))
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
// The segment has one value.
sink.writeUtf8("a") // a
sink.emit()
- assertEquals(0, source.indexOf('a'.toByte()))
- assertEquals(-1, source.indexOf('b'.toByte()))
+ assertEquals(0, source.indexOf('a'.code.toByte()))
+ assertEquals(-1, source.indexOf('b'.code.toByte()))
// The segment has lots of data.
sink.writeUtf8("b".repeat(Segment.SIZE - 2)) // ab...b
sink.emit()
- assertEquals(0, source.indexOf('a'.toByte()))
- assertEquals(1, source.indexOf('b'.toByte()))
- assertEquals(-1, source.indexOf('c'.toByte()))
+ assertEquals(0, source.indexOf('a'.code.toByte()))
+ assertEquals(1, source.indexOf('b'.code.toByte()))
+ assertEquals(-1, source.indexOf('c'.code.toByte()))
// The segment doesn't start at 0, it starts at 2.
source.skip(2) // b...b
- assertEquals(-1, source.indexOf('a'.toByte()))
- assertEquals(0, source.indexOf('b'.toByte()))
- assertEquals(-1, source.indexOf('c'.toByte()))
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('b'.code.toByte()))
+ assertEquals(-1, source.indexOf('c'.code.toByte()))
// The segment is full.
sink.writeUtf8("c") // b...bc
sink.emit()
- assertEquals(-1, source.indexOf('a'.toByte()))
- assertEquals(0, source.indexOf('b'.toByte()))
- assertEquals((Segment.SIZE - 3).toLong(), source.indexOf('c'.toByte()))
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('b'.code.toByte()))
+ assertEquals((Segment.SIZE - 3).toLong(), source.indexOf('c'.code.toByte()))
// The segment doesn't start at 2, it starts at 4.
source.skip(2) // b...bc
- assertEquals(-1, source.indexOf('a'.toByte()))
- assertEquals(0, source.indexOf('b'.toByte()))
- assertEquals((Segment.SIZE - 5).toLong(), source.indexOf('c'.toByte()))
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('b'.code.toByte()))
+ assertEquals((Segment.SIZE - 5).toLong(), source.indexOf('c'.code.toByte()))
// Two segments.
sink.writeUtf8("d") // b...bcd, d is in the 2nd segment.
sink.emit()
- assertEquals((Segment.SIZE - 4).toLong(), source.indexOf('d'.toByte()))
- assertEquals(-1, source.indexOf('e'.toByte()))
+ assertEquals((Segment.SIZE - 4).toLong(), source.indexOf('d'.code.toByte()))
+ assertEquals(-1, source.indexOf('e'.code.toByte()))
}
@Test fun indexOfByteWithStartOffset() {
sink.writeUtf8("a").writeUtf8("b".repeat(Segment.SIZE)).writeUtf8("c")
sink.emit()
- assertEquals(-1, source.indexOf('a'.toByte(), 1))
- assertEquals(15, source.indexOf('b'.toByte(), 15))
+ assertEquals(-1, source.indexOf('a'.code.toByte(), 1))
+ assertEquals(15, source.indexOf('b'.code.toByte(), 15))
}
@Test fun indexOfByteWithBothOffsets() {
if (factory.isOneByteAtATime) {
- // When run on Travis this causes out-of-memory errors.
+ // When run on CI this causes out-of-memory errors.
return
}
- val a = 'a'.toByte()
- val c = 'c'.toByte()
+ val a = 'a'.code.toByte()
+ val c = 'c'.code.toByte()
val size = Segment.SIZE * 5
val bytes = ByteArray(size) { a }
@@ -585,7 +585,7 @@ abstract class AbstractBufferedSourceTest internal constructor(
size - Segment.SIZE + 1,
size - 3,
size - 2,
- size - 1
+ size - 1,
)
// In each iteration, we write c to the known point and then search for it using different
@@ -615,17 +615,11 @@ abstract class AbstractBufferedSourceTest internal constructor(
@Test fun indexOfByteInvalidBoundsThrows() {
sink.writeUtf8("abc")
sink.emit()
-
- try {
- source.indexOf('a'.toByte(), -1)
- fail("Expected failure: fromIndex < 0")
- } catch (expected: IllegalArgumentException) {
+ assertFailsWith<IllegalArgumentException>("Expected failure: fromIndex < 0") {
+ source.indexOf('a'.code.toByte(), -1)
}
-
- try {
- source.indexOf('a'.toByte(), 10, 0)
- fail("Expected failure: fromIndex > toIndex")
- } catch (expected: IllegalArgumentException) {
+ assertFailsWith<IllegalArgumentException>("Expected failure: fromIndex > toIndex") {
+ source.indexOf('a'.code.toByte(), 10, 0)
}
}
@@ -649,55 +643,55 @@ abstract class AbstractBufferedSourceTest internal constructor(
sink.emit()
assertEquals(
(Segment.SIZE - 3).toLong(),
- source.indexOf("aabc".encodeUtf8(), (Segment.SIZE - 4).toLong())
+ source.indexOf("aabc".encodeUtf8(), (Segment.SIZE - 4).toLong()),
)
assertEquals(
(Segment.SIZE - 3).toLong(),
- source.indexOf("aabc".encodeUtf8(), (Segment.SIZE - 3).toLong())
+ source.indexOf("aabc".encodeUtf8(), (Segment.SIZE - 3).toLong()),
)
assertEquals(
(Segment.SIZE - 2).toLong(),
- source.indexOf("abcd".encodeUtf8(), (Segment.SIZE - 2).toLong())
+ source.indexOf("abcd".encodeUtf8(), (Segment.SIZE - 2).toLong()),
)
assertEquals(
(Segment.SIZE - 2).toLong(),
- source.indexOf("abc".encodeUtf8(), (Segment.SIZE - 2).toLong())
+ source.indexOf("abc".encodeUtf8(), (Segment.SIZE - 2).toLong()),
)
assertEquals(
(Segment.SIZE - 2).toLong(),
- source.indexOf("abc".encodeUtf8(), (Segment.SIZE - 2).toLong())
+ source.indexOf("abc".encodeUtf8(), (Segment.SIZE - 2).toLong()),
)
assertEquals(
(Segment.SIZE - 2).toLong(),
- source.indexOf("ab".encodeUtf8(), (Segment.SIZE - 2).toLong())
+ source.indexOf("ab".encodeUtf8(), (Segment.SIZE - 2).toLong()),
)
assertEquals(
(Segment.SIZE - 2).toLong(),
- source.indexOf("a".encodeUtf8(), (Segment.SIZE - 2).toLong())
+ source.indexOf("a".encodeUtf8(), (Segment.SIZE - 2).toLong()),
)
assertEquals(
(Segment.SIZE - 1).toLong(),
- source.indexOf("bc".encodeUtf8(), (Segment.SIZE - 2).toLong())
+ source.indexOf("bc".encodeUtf8(), (Segment.SIZE - 2).toLong()),
)
assertEquals(
(Segment.SIZE - 1).toLong(),
- source.indexOf("b".encodeUtf8(), (Segment.SIZE - 2).toLong())
+ source.indexOf("b".encodeUtf8(), (Segment.SIZE - 2).toLong()),
)
assertEquals(
Segment.SIZE.toLong(),
- source.indexOf("c".encodeUtf8(), (Segment.SIZE - 2).toLong())
+ source.indexOf("c".encodeUtf8(), (Segment.SIZE - 2).toLong()),
)
assertEquals(
Segment.SIZE.toLong(),
- source.indexOf("c".encodeUtf8(), Segment.SIZE.toLong())
+ source.indexOf("c".encodeUtf8(), Segment.SIZE.toLong()),
)
assertEquals(
(Segment.SIZE + 1).toLong(),
- source.indexOf("d".encodeUtf8(), (Segment.SIZE - 2).toLong())
+ source.indexOf("d".encodeUtf8(), (Segment.SIZE - 2).toLong()),
)
assertEquals(
(Segment.SIZE + 1).toLong(),
- source.indexOf("d".encodeUtf8(), (Segment.SIZE + 1).toLong())
+ source.indexOf("d".encodeUtf8(), (Segment.SIZE + 1).toLong()),
)
}
@@ -723,19 +717,15 @@ abstract class AbstractBufferedSourceTest internal constructor(
}
@Test fun indexOfByteStringInvalidArgumentsThrows() {
- try {
+ var e = assertFailsWith<IllegalArgumentException> {
source.indexOf(ByteString.of())
- fail()
- } catch (e: IllegalArgumentException) {
- assertEquals("bytes is empty", e.message)
}
+ assertEquals("bytes is empty", e.message)
- try {
+ e = assertFailsWith<IllegalArgumentException> {
source.indexOf("hi".encodeUtf8(), -1)
- fail()
- } catch (e: IllegalArgumentException) {
- assertEquals("fromIndex < 0: -1", e.message)
}
+ assertEquals("fromIndex < 0: -1", e.message)
}
/**
@@ -781,10 +771,10 @@ abstract class AbstractBufferedSourceTest internal constructor(
@Test fun indexOfByteWithFromIndex() {
sink.writeUtf8("aaa")
sink.emit()
- assertEquals(0, source.indexOf('a'.toByte()))
- assertEquals(0, source.indexOf('a'.toByte(), 0))
- assertEquals(1, source.indexOf('a'.toByte(), 1))
- assertEquals(2, source.indexOf('a'.toByte(), 2))
+ assertEquals(0, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('a'.code.toByte(), 0))
+ assertEquals(1, source.indexOf('a'.code.toByte(), 1))
+ assertEquals(2, source.indexOf('a'.code.toByte(), 2))
}
@Test fun indexOfByteStringWithFromIndex() {
@@ -857,35 +847,29 @@ abstract class AbstractBufferedSourceTest internal constructor(
}
@Test fun longHexStringTooLongThrows() {
- try {
- sink.writeUtf8("fffffffffffffffff")
- sink.emit()
+ sink.writeUtf8("fffffffffffffffff")
+ sink.emit()
+
+ val e = assertFailsWith<NumberFormatException> {
source.readHexadecimalUnsignedLong()
- fail()
- } catch (e: NumberFormatException) {
- assertEquals("Number too large: fffffffffffffffff", e.message)
}
+ assertEquals("Number too large: fffffffffffffffff", e.message)
}
@Test fun longHexStringTooShortThrows() {
- try {
- sink.writeUtf8(" ")
- sink.emit()
+ sink.writeUtf8(" ")
+ sink.emit()
+
+ val e = assertFailsWith<NumberFormatException> {
source.readHexadecimalUnsignedLong()
- fail()
- } catch (e: NumberFormatException) {
- assertEquals("Expected leading [0-9a-fA-F] character but was 0x20", e.message)
}
+ assertEquals("Expected leading [0-9a-fA-F] character but was 0x20", e.message)
}
@Test fun longHexEmptySourceThrows() {
- try {
- sink.writeUtf8("")
- sink.emit()
- source.readHexadecimalUnsignedLong()
- fail()
- } catch (expected: EOFException) {
- }
+ sink.writeUtf8("")
+ sink.emit()
+ assertFailsWith<EOFException> { source.readHexadecimalUnsignedLong() }
}
@Test fun longDecimalString() {
@@ -918,60 +902,74 @@ abstract class AbstractBufferedSourceTest internal constructor(
}
@Test fun longDecimalStringTooLongThrows() {
- try {
- sink.writeUtf8("12345678901234567890") // Too many digits.
- sink.emit()
+ sink.writeUtf8("12345678901234567890") // Too many digits.
+ sink.emit()
+
+ val e = assertFailsWith<NumberFormatException> {
source.readDecimalLong()
- fail()
- } catch (e: NumberFormatException) {
- assertEquals("Number too large: 12345678901234567890", e.message)
}
+ assertEquals("Number too large: 12345678901234567890", e.message)
}
@Test fun longDecimalStringTooHighThrows() {
- try {
- sink.writeUtf8("9223372036854775808") // Right size but cannot fit.
- sink.emit()
+ sink.writeUtf8("9223372036854775808") // Right size but cannot fit.
+ sink.emit()
+
+ val e = assertFailsWith<NumberFormatException> {
source.readDecimalLong()
- fail()
- } catch (e: NumberFormatException) {
- assertEquals("Number too large: 9223372036854775808", e.message)
}
+ assertEquals("Number too large: 9223372036854775808", e.message)
}
@Test fun longDecimalStringTooLowThrows() {
- try {
- sink.writeUtf8("-9223372036854775809") // Right size but cannot fit.
- sink.emit()
+ sink.writeUtf8("-9223372036854775809") // Right size but cannot fit.
+ sink.emit()
+
+ val e = assertFailsWith<NumberFormatException> {
source.readDecimalLong()
- fail()
- } catch (e: NumberFormatException) {
- assertEquals("Number too large: -9223372036854775809", e.message)
}
+ assertEquals("Number too large: -9223372036854775809", e.message)
}
@Test fun longDecimalStringTooShortThrows() {
- try {
- sink.writeUtf8(" ")
- sink.emit()
+ sink.writeUtf8(" ")
+ sink.emit()
+
+ val e = assertFailsWith<NumberFormatException> {
source.readDecimalLong()
- fail()
- } catch (e: NumberFormatException) {
- assertEquals("Expected leading [0-9] or '-' character but was 0x20", e.message)
}
+ assertEquals("Expected a digit or '-' but was 0x20", e.message)
}
@Test fun longDecimalEmptyThrows() {
- try {
- sink.writeUtf8("")
- sink.emit()
+ sink.writeUtf8("")
+ sink.emit()
+ assertFailsWith<EOFException> {
+ source.readDecimalLong()
+ }
+ }
+
+ @Test fun longDecimalLoneDashThrows() {
+ sink.writeUtf8("-")
+ sink.emit()
+ assertFailsWith<EOFException> {
+ source.readDecimalLong()
+ }
+ }
+
+ @Test fun longDecimalDashFollowedByNonDigitThrows() {
+ sink.writeUtf8("- ")
+ sink.emit()
+ assertFailsWith<NumberFormatException> {
source.readDecimalLong()
- fail()
- } catch (expected: EOFException) {
}
}
@Test fun codePoints() {
+ // TODO: remove this suppression once this issue is fixed.
+ // https://youtrack.jetbrains.com/issue/KT-60212
+ if (isWasm()) return
+
sink.write("7f".decodeHex())
sink.emit()
assertEquals(0x7f, source.readUtf8CodePoint().toLong())
@@ -1000,21 +998,24 @@ abstract class AbstractBufferedSourceTest internal constructor(
val options = Options.of(
"ROCK".encodeUtf8(),
"SCISSORS".encodeUtf8(),
- "PAPER".encodeUtf8()
+ "PAPER".encodeUtf8(),
)
sink.writeUtf8("PAPER,SCISSORS,ROCK")
sink.emit()
assertEquals(2, source.select(options).toLong())
- assertEquals(','.toLong(), source.readByte().toLong())
+ assertEquals(','.code.toLong(), source.readByte().toLong())
assertEquals(1, source.select(options).toLong())
- assertEquals(','.toLong(), source.readByte().toLong())
+ assertEquals(','.code.toLong(), source.readByte().toLong())
assertEquals(0, source.select(options).toLong())
assertTrue(source.exhausted())
}
/** Note that this test crashes the VM on Android. */
@Test fun selectSpanningMultipleSegments() {
+ if (factory.isOneByteAtATime && isBrowser()) {
+ return // This test times out on browsers.
+ }
val commonPrefix = randomBytes(Segment.SIZE + 10)
val a = Buffer().write(commonPrefix).writeUtf8("a").readByteString()
val bc = Buffer().write(commonPrefix).writeUtf8("bc").readByteString()
@@ -1036,7 +1037,7 @@ abstract class AbstractBufferedSourceTest internal constructor(
val options = Options.of(
"ROCK".encodeUtf8(),
"SCISSORS".encodeUtf8(),
- "PAPER".encodeUtf8()
+ "PAPER".encodeUtf8(),
)
sink.writeUtf8("SPOCK")
@@ -1049,7 +1050,7 @@ abstract class AbstractBufferedSourceTest internal constructor(
val options = Options.of(
"abcd".encodeUtf8(),
"abce".encodeUtf8(),
- "abcc".encodeUtf8()
+ "abcc".encodeUtf8(),
)
sink.writeUtf8("abcc").writeUtf8("abcd").writeUtf8("abce")
@@ -1063,7 +1064,7 @@ abstract class AbstractBufferedSourceTest internal constructor(
val options = Options.of(
"abcd".encodeUtf8(),
"abce".encodeUtf8(),
- "abcc".encodeUtf8()
+ "abcc".encodeUtf8(),
)
sink.writeUtf8("abc")
sink.emit()
@@ -1075,7 +1076,7 @@ abstract class AbstractBufferedSourceTest internal constructor(
val options = Options.of(
"abcd".encodeUtf8(),
"abc".encodeUtf8(),
- "abcde".encodeUtf8()
+ "abcde".encodeUtf8(),
)
sink.writeUtf8("abcdef")
sink.emit()
@@ -1086,7 +1087,7 @@ abstract class AbstractBufferedSourceTest internal constructor(
@Test fun selectFromEmptySource() {
val options = Options.of(
"abc".encodeUtf8(),
- "def".encodeUtf8()
+ "def".encodeUtf8(),
)
assertEquals(-1, source.select(options).toLong())
}
@@ -1132,6 +1133,10 @@ abstract class AbstractBufferedSourceTest internal constructor(
}
@Test fun peekLarge() {
+ if (factory.isOneByteAtATime) {
+ // When run on CI this causes out-of-memory errors.
+ return
+ }
sink.writeUtf8("abcdef")
sink.writeUtf8("g".repeat(2 * Segment.SIZE))
sink.writeUtf8("hij")
@@ -1163,12 +1168,10 @@ abstract class AbstractBufferedSourceTest internal constructor(
assertEquals("def", source.readUtf8(3))
- try {
+ val e = assertFailsWith<IllegalStateException> {
peek.readUtf8()
- fail()
- } catch (e: IllegalStateException) {
- assertEquals("Peek source is invalid because upstream source was used", e.message)
}
+ assertEquals("Peek source is invalid because upstream source was used", e.message)
}
@Test fun peekSegmentThenInvalid() {
@@ -1186,12 +1189,10 @@ abstract class AbstractBufferedSourceTest internal constructor(
// Skip the rest of the buffered data
peek.skip(peek.buffer.size)
- try {
+ val e = assertFailsWith<IllegalStateException> {
peek.readByte()
- fail()
- } catch (e: IllegalStateException) {
- assertEquals("Peek source is invalid because upstream source was used", e.message)
}
+ assertEquals("Peek source is invalid because upstream source was used", e.message)
}
@Test fun peekDoesntReadTooMuch() {
diff --git a/okio/src/commonTest/kotlin/okio/BufferCommonTest.kt b/okio/src/commonTest/kotlin/okio/BufferCommonTest.kt
index 842faffe..cc946883 100644
--- a/okio/src/commonTest/kotlin/okio/BufferCommonTest.kt
+++ b/okio/src/commonTest/kotlin/okio/BufferCommonTest.kt
@@ -15,9 +15,9 @@
*/
package okio
-import okio.ByteString.Companion.encodeUtf8
import kotlin.test.Test
import kotlin.test.assertEquals
+import okio.ByteString.Companion.encodeUtf8
class BufferCommonTest {
diff --git a/okio/src/commonTest/kotlin/okio/BufferedSourceFactory.kt b/okio/src/commonTest/kotlin/okio/BufferedSourceFactory.kt
index b9836202..173bb841 100644
--- a/okio/src/commonTest/kotlin/okio/BufferedSourceFactory.kt
+++ b/okio/src/commonTest/kotlin/okio/BufferedSourceFactory.kt
@@ -19,7 +19,7 @@ package okio
interface BufferedSourceFactory {
class Pipe(
var sink: BufferedSink,
- var source: BufferedSource
+ var source: BufferedSource,
)
val isOneByteAtATime: Boolean
@@ -36,7 +36,7 @@ interface BufferedSourceFactory {
val buffer = Buffer()
return Pipe(
buffer,
- buffer
+ buffer,
)
}
}
@@ -51,7 +51,7 @@ interface BufferedSourceFactory {
val buffer = Buffer()
return Pipe(
buffer,
- (buffer as Source).buffer()
+ (buffer as Source).buffer(),
)
}
}
@@ -79,7 +79,7 @@ interface BufferedSourceFactory {
if (result > 0L) sink.write(box.copy(), result)
return result
}
- }.buffer()
+ }.buffer(),
)
}
}
@@ -104,7 +104,7 @@ interface BufferedSourceFactory {
}
}
}.buffer(),
- buffer
+ buffer,
)
}
}
@@ -118,7 +118,7 @@ interface BufferedSourceFactory {
val buffer = Buffer()
return Pipe(
buffer,
- buffer.peek()
+ buffer.peek(),
)
}
}
@@ -133,7 +133,7 @@ interface BufferedSourceFactory {
val buffer = Buffer()
return Pipe(
buffer,
- (buffer as Source).buffer().peek()
+ (buffer as Source).buffer().peek(),
)
}
}
@@ -144,7 +144,7 @@ interface BufferedSourceFactory {
arrayOf(ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE),
arrayOf(ONE_BYTE_AT_A_TIME_BUFFER),
arrayOf(PEEK_BUFFER),
- arrayOf(PEEK_BUFFERED_SOURCE)
+ arrayOf(PEEK_BUFFERED_SOURCE),
)
}
}
diff --git a/okio/src/commonTest/kotlin/okio/ByteStringMoreTests.kt b/okio/src/commonTest/kotlin/okio/ByteStringMoreTests.kt
new file mode 100644
index 00000000..d9721d16
--- /dev/null
+++ b/okio/src/commonTest/kotlin/okio/ByteStringMoreTests.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package okio
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import okio.ByteString.Companion.toByteString
+
+class ByteStringMoreTests {
+ @Test fun arrayToByteString() {
+ val actual = byteArrayOf(1, 2, 3, 4).toByteString()
+ val expected = ByteString.of(1, 2, 3, 4)
+ assertEquals(actual, expected)
+ }
+}
diff --git a/okio/src/commonTest/kotlin/okio/ByteStringTest.kt b/okio/src/commonTest/kotlin/okio/ByteStringTest.kt
index c75c4581..9866e14b 100644
--- a/okio/src/commonTest/kotlin/okio/ByteStringTest.kt
+++ b/okio/src/commonTest/kotlin/okio/ByteStringTest.kt
@@ -16,10 +16,6 @@
package okio
-import okio.ByteString.Companion.decodeBase64
-import okio.ByteString.Companion.decodeHex
-import okio.ByteString.Companion.encodeUtf8
-import okio.internal.commonAsUtf8ToByteArray
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -29,6 +25,11 @@ import kotlin.test.assertNotEquals
import kotlin.test.assertSame
import kotlin.test.assertTrue
import kotlin.test.fail
+import okio.ByteString.Companion.decodeBase64
+import okio.ByteString.Companion.decodeHex
+import okio.ByteString.Companion.encodeUtf8
+import okio.ByteString.Companion.toByteString
+import okio.internal.commonAsUtf8ToByteArray
class ByteStringTest : AbstractByteStringTest(ByteStringFactory.BYTE_STRING)
class SegmentedByteStringTest : AbstractByteStringTest(ByteStringFactory.SEGMENTED_BYTE_STRING)
@@ -36,14 +37,14 @@ class ByteStringOneBytePerSegmentTest : AbstractByteStringTest(ByteStringFactory
class OkioEncoderTest : AbstractByteStringTest(ByteStringFactory.OKIO_ENCODER)
abstract class AbstractByteStringTest internal constructor(
- private val factory: ByteStringFactory
+ private val factory: ByteStringFactory,
) {
@Test fun get() {
val actual = factory.encodeUtf8("abc")
assertEquals(3, actual.size)
- assertEquals(actual[0], 'a'.toByte())
- assertEquals(actual[1], 'b'.toByte())
- assertEquals(actual[2], 'c'.toByte())
+ assertEquals(actual[0], 'a'.code.toByte())
+ assertEquals(actual[1], 'b'.code.toByte())
+ assertEquals(actual[2], 'c'.code.toByte())
try {
actual[-1]
fail("no index out of bounds: -1")
@@ -214,7 +215,7 @@ abstract class AbstractByteStringTest internal constructor(
(
"d09dd0b020d0b1d0b5d180d0b5d0b3d18320d0bfd183d181" +
"d182d18bd0bdd0bdd18bd18520d0b2d0bed0bbd0bd"
- ).decodeHex()
+ ).decodeHex(),
)
assertEquals(byteString.utf8(), bronzeHorseman)
}
@@ -277,7 +278,7 @@ abstract class AbstractByteStringTest internal constructor(
assertEquals("AAAA", factory.encodeUtf8("\u0000\u0000\u0000").base64())
assertEquals(
"SG93IG1hbnkgbGluZXMgb2YgY29kZSBhcmUgdGhlcmU/ICdib3V0IDIgbWlsbGlvbi4=",
- factory.encodeUtf8("How many lines of code are there? 'bout 2 million.").base64()
+ factory.encodeUtf8("How many lines of code are there? 'bout 2 million.").base64(),
)
}
@@ -288,7 +289,7 @@ abstract class AbstractByteStringTest internal constructor(
assertEquals("AAAA", factory.encodeUtf8("\u0000\u0000\u0000").base64Url())
assertEquals(
"SG93IG1hbnkgbGluZXMgb2YgY29kZSBhcmUgdGhlcmU_ICdib3V0IDIgbWlsbGlvbi4=",
- factory.encodeUtf8("How many lines of code are there? 'bout 2 million.").base64Url()
+ factory.encodeUtf8("How many lines of code are there? 'bout 2 million.").base64Url(),
)
}
@@ -313,7 +314,7 @@ abstract class AbstractByteStringTest internal constructor(
(
"V2hhdCdzIHRvIGJlIHNjYXJlZCBhYm91dD8gSXQncyBqdXN0IGEgbGl0dGxlIGhpY2" +
"N1cCBpbiB0aGUgcG93ZXIuLi4="
- ).decodeBase64()!!.utf8()
+ ).decodeBase64()!!.utf8(),
)
// Uses two encoding styles. Malformed, but supported as a side-effect.
assertEquals("ffffff".decodeHex(), "__//".decodeBase64())
@@ -358,11 +359,11 @@ abstract class AbstractByteStringTest internal constructor(
@Test fun toStringOnShortText() {
assertEquals(
"[text=Tyrannosaur]",
- factory.encodeUtf8("Tyrannosaur").toString()
+ factory.encodeUtf8("Tyrannosaur").toString(),
)
assertEquals(
"[text=təˈranəˌsôr]",
- factory.decodeHex("74c999cb8872616ec999cb8c73c3b472").toString()
+ factory.decodeHex("74c999cb8872616ec999cb8c73c3b472").toString(),
)
}
@@ -379,7 +380,7 @@ abstract class AbstractByteStringTest internal constructor(
assertEquals(
"[size=517 text=Um, I'll tell you the problem with the scientific power that " +
"you…]",
- factory.encodeUtf8(raw).toString()
+ factory.encodeUtf8(raw).toString(),
)
val war = (
"Սm, I'll 𝓽𝖾ll ᶌօ𝘂 ᴛℎ℮ 𝜚𝕣०bl𝖾m wі𝕥𝒽 𝘵𝘩𝐞 𝓼𝙘𝐢𝔢𝓷𝗍𝜄𝚏𝑖c 𝛠𝝾w𝚎𝑟 𝕥h⍺𝞃 𝛄𝓸𝘂'𝒓𝗲 υ𝖘𝓲𝗇ɡ 𝕙𝚎𝑟e, " +
@@ -392,7 +393,7 @@ abstract class AbstractByteStringTest internal constructor(
assertEquals(
"[size=1496 text=Սm, I'll 𝓽𝖾ll ᶌօ𝘂 ᴛℎ℮ 𝜚𝕣०bl𝖾m wі𝕥𝒽 𝘵𝘩𝐞 𝓼𝙘𝐢𝔢𝓷𝗍𝜄𝚏𝑖c 𝛠𝝾w𝚎𝑟 𝕥h⍺𝞃 " +
"𝛄𝓸𝘂…]",
- factory.encodeUtf8(war).toString()
+ factory.encodeUtf8(war).toString(),
)
}
@@ -400,7 +401,7 @@ abstract class AbstractByteStringTest internal constructor(
// Instead of emitting a literal newline in the toString(), these are escaped as "\n".
assertEquals(
"[text=a\\r\\nb\\nc\\rd\\\\e]",
- factory.encodeUtf8("a\r\nb\nc\rd\\e").toString()
+ factory.encodeUtf8("a\r\nb\nc\rd\\e").toString(),
)
}
@@ -408,13 +409,13 @@ abstract class AbstractByteStringTest internal constructor(
val byteString = factory.decodeHex(
"" +
"60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55" +
- "4bf0b54023c29b624de9ef9c2f931efc580f9afb"
+ "4bf0b54023c29b624de9ef9c2f931efc580f9afb",
)
assertEquals(
"[hex=" +
"60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55" +
"4bf0b54023c29b624de9ef9c2f931efc580f9afb]",
- byteString.toString()
+ byteString.toString(),
)
}
@@ -422,13 +423,13 @@ abstract class AbstractByteStringTest internal constructor(
val byteString = factory.decodeHex(
"" +
"60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55" +
- "4bf0b54023c29b624de9ef9c2f931efc580f9afba1"
+ "4bf0b54023c29b624de9ef9c2f931efc580f9afba1",
)
assertEquals(
"[size=65 hex=" +
"60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55" +
"4bf0b54023c29b624de9ef9c2f931efc580f9afb…]",
- byteString.toString()
+ byteString.toString(),
)
}
@@ -441,7 +442,7 @@ abstract class AbstractByteStringTest internal constructor(
factory.decodeHex("80"),
factory.decodeHex("81"),
factory.decodeHex("fe"),
- factory.decodeHex("ff")
+ factory.decodeHex("ff"),
)
val sortedByteStrings = originalByteStrings.toMutableList()
@@ -479,7 +480,7 @@ abstract class AbstractByteStringTest internal constructor(
factory.decodeHex("010101"),
factory.decodeHex("7f0000"),
factory.decodeHex("7f0000ffff"),
- factory.decodeHex("ffffff")
+ factory.decodeHex("ffffff"),
)
val sortedByteStrings = originalByteStrings.toMutableList()
@@ -496,4 +497,101 @@ abstract class AbstractByteStringTest internal constructor(
assertEquals("0e4dd66217fc8d2e298b78c8cd9392870dcd065d0ff675d0edff5bcd227837e9", sha256().hex())
assertEquals("483676b93c4417198b465083d196ec6a9fab8d004515874b8ff47e041f5f56303cc08179625030b8b5b721c09149a18f0f59e64e7ae099518cea78d3d83167e1", sha512().hex())
}
+
+ @Test fun copyInto() {
+ val byteString = factory.encodeUtf8("abcdefgh")
+ val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray()
+ byteString.copyInto(target = byteArray, byteCount = 5)
+ assertEquals("abcdexxxYyyyZzzz", byteArray.decodeToString())
+ }
+
+ @Test fun copyIntoFullRange() {
+ val byteString = factory.encodeUtf8("abcdefghijklmnop")
+ val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray()
+ byteString.copyInto(target = byteArray, byteCount = 16)
+ assertEquals("abcdefghijklmnop", byteArray.decodeToString())
+ }
+
+ @Test fun copyIntoWithTargetOffset() {
+ val byteString = factory.encodeUtf8("abcdefgh")
+ val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray()
+ byteString.copyInto(target = byteArray, targetOffset = 11, byteCount = 5)
+ assertEquals("WwwwXxxxYyyabcde", byteArray.decodeToString())
+ }
+
+ @Test fun copyIntoWithSourceOffset() {
+ val byteString = factory.encodeUtf8("abcdefgh")
+ val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray()
+ byteString.copyInto(offset = 3, target = byteArray, byteCount = 5)
+ assertEquals("defghxxxYyyyZzzz", byteArray.decodeToString())
+ }
+
+ @Test fun copyIntoWithAllParameters() {
+ val byteString = factory.encodeUtf8("abcdefgh")
+ val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray()
+ byteString.copyInto(offset = 3, target = byteArray, targetOffset = 11, byteCount = 5)
+ assertEquals("WwwwXxxxYyydefgh", byteArray.decodeToString())
+ }
+
+ @Test fun copyIntoBoundsChecks() {
+ val byteString = factory.encodeUtf8("abcdefgh")
+ val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray()
+ assertFailsWith<IndexOutOfBoundsException> {
+ byteString.copyInto(offset = -1, target = byteArray, targetOffset = 1, byteCount = 1)
+ }
+ assertFailsWith<IndexOutOfBoundsException> {
+ byteString.copyInto(offset = 9, target = byteArray, targetOffset = 0, byteCount = 0)
+ }
+ assertFailsWith<IndexOutOfBoundsException> {
+ byteString.copyInto(offset = 1, target = byteArray, targetOffset = -1, byteCount = 1)
+ }
+ assertFailsWith<IndexOutOfBoundsException> {
+ byteString.copyInto(offset = 1, target = byteArray, targetOffset = 17, byteCount = 1)
+ }
+ assertFailsWith<IndexOutOfBoundsException> {
+ byteString.copyInto(offset = 7, target = byteArray, targetOffset = 1, byteCount = 2)
+ }
+ assertFailsWith<IndexOutOfBoundsException> {
+ byteString.copyInto(offset = 1, target = byteArray, targetOffset = 15, byteCount = 2)
+ }
+ }
+
+ @Test fun copyEmptyAtBounds() {
+ val byteString = factory.encodeUtf8("abcdefgh")
+ val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray()
+ byteString.copyInto(offset = 0, target = byteArray, targetOffset = 0, byteCount = 0)
+ assertEquals("WwwwXxxxYyyyZzzz", byteArray.decodeToString())
+ byteString.copyInto(offset = 0, target = byteArray, targetOffset = 16, byteCount = 0)
+ assertEquals("WwwwXxxxYyyyZzzz", byteArray.decodeToString())
+ byteString.copyInto(offset = 8, target = byteArray, targetOffset = 0, byteCount = 0)
+ assertEquals("WwwwXxxxYyyyZzzz", byteArray.decodeToString())
+ }
+
+ @Test
+ fun ofCopy() {
+ val bytes = "Hello, World!".encodeToByteArray()
+ val byteString = ByteString.of(*bytes)
+ // Verify that the bytes were copied out.
+ bytes[4] = 'a'.code.toByte()
+ assertEquals("Hello, World!", byteString.utf8())
+ }
+
+ @Test
+ fun ofCopyRange() {
+ val bytes = "Hello, World!".encodeToByteArray()
+ val byteString: ByteString = bytes.toByteString(2, 9)
+ // Verify that the bytes were copied out.
+ bytes[4] = 'a'.code.toByte()
+ assertEquals("llo, Worl", byteString.utf8())
+ }
+
+ @Test
+ fun getByteOutOfBounds() {
+ val byteString = factory.decodeHex("ab12")
+ try {
+ byteString[2]
+ fail()
+ } catch (expected: IndexOutOfBoundsException) {
+ }
+ }
}
diff --git a/okio/src/commonTest/kotlin/okio/CommonBufferTest.kt b/okio/src/commonTest/kotlin/okio/CommonBufferTest.kt
index 292e3c5c..0dbaf021 100644
--- a/okio/src/commonTest/kotlin/okio/CommonBufferTest.kt
+++ b/okio/src/commonTest/kotlin/okio/CommonBufferTest.kt
@@ -15,13 +15,13 @@
*/
package okio
-import okio.ByteString.Companion.decodeHex
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
+import okio.ByteString.Companion.decodeHex
/**
* Tests solely for the behavior of Buffer's implementation. For generic BufferedSink or
@@ -48,22 +48,22 @@ class CommonBufferTest {
assertEquals("[size=0]", Buffer().toString())
assertEquals(
"[text=a\\r\\nb\\nc\\rd\\\\e]",
- Buffer().writeUtf8("a\r\nb\nc\rd\\e").toString()
+ Buffer().writeUtf8("a\r\nb\nc\rd\\e").toString(),
)
assertEquals(
"[text=Tyrannosaur]",
- Buffer().writeUtf8("Tyrannosaur").toString()
+ Buffer().writeUtf8("Tyrannosaur").toString(),
)
assertEquals(
"[text=təˈranəˌsôr]",
Buffer()
.write("74c999cb8872616ec999cb8c73c3b472".decodeHex())
- .toString()
+ .toString(),
)
assertEquals(
"[hex=0000000000000000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000]",
- Buffer().write(ByteArray(64)).toString()
+ Buffer().write(ByteArray(64)).toString(),
)
}
@@ -127,10 +127,16 @@ class CommonBufferTest {
val segmentSizes = moveBytesBetweenBuffers('a'.repeat(size), 'b'.repeat(size))
assertEquals(
listOf(
- Segment.SIZE, Segment.SIZE, Segment.SIZE, 1,
- Segment.SIZE, Segment.SIZE, Segment.SIZE, 1
+ Segment.SIZE,
+ Segment.SIZE,
+ Segment.SIZE,
+ 1,
+ Segment.SIZE,
+ Segment.SIZE,
+ Segment.SIZE,
+ 1,
),
- segmentSizes
+ segmentSizes,
)
}
@@ -240,14 +246,14 @@ class CommonBufferTest {
buffer.writeUtf8('b'.repeat(halfSegment))
buffer.writeUtf8('c'.repeat(halfSegment))
buffer.writeUtf8('d'.repeat(halfSegment))
- assertEquals(0, buffer.indexOf('a'.toByte(), 0))
- assertEquals((halfSegment - 1).toLong(), buffer.indexOf('a'.toByte(), (halfSegment - 1).toLong()))
- assertEquals(halfSegment.toLong(), buffer.indexOf('b'.toByte(), (halfSegment - 1).toLong()))
- assertEquals((halfSegment * 2).toLong(), buffer.indexOf('c'.toByte(), (halfSegment - 1).toLong()))
- assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.toByte(), (halfSegment - 1).toLong()))
- assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.toByte(), (halfSegment * 2).toLong()))
- assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.toByte(), (halfSegment * 3).toLong()))
- assertEquals((halfSegment * 4 - 1).toLong(), buffer.indexOf('d'.toByte(), (halfSegment * 4 - 1).toLong()))
+ assertEquals(0, buffer.indexOf('a'.code.toByte(), 0))
+ assertEquals((halfSegment - 1).toLong(), buffer.indexOf('a'.code.toByte(), (halfSegment - 1).toLong()))
+ assertEquals(halfSegment.toLong(), buffer.indexOf('b'.code.toByte(), (halfSegment - 1).toLong()))
+ assertEquals((halfSegment * 2).toLong(), buffer.indexOf('c'.code.toByte(), (halfSegment - 1).toLong()))
+ assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.code.toByte(), (halfSegment - 1).toLong()))
+ assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.code.toByte(), (halfSegment * 2).toLong()))
+ assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.code.toByte(), (halfSegment * 3).toLong()))
+ assertEquals((halfSegment * 4 - 1).toLong(), buffer.indexOf('d'.code.toByte(), (halfSegment * 4 - 1).toLong()))
}
@Test fun byteAt() {
@@ -255,11 +261,11 @@ class CommonBufferTest {
buffer.writeUtf8("a")
buffer.writeUtf8('b'.repeat(Segment.SIZE))
buffer.writeUtf8("c")
- assertEquals('a'.toLong(), buffer[0].toLong())
- assertEquals('a'.toLong(), buffer[0].toLong()) // getByte doesn't mutate!
- assertEquals('c'.toLong(), buffer[buffer.size - 1].toLong())
- assertEquals('b'.toLong(), buffer[buffer.size - 2].toLong())
- assertEquals('b'.toLong(), buffer[buffer.size - 3].toLong())
+ assertEquals('a'.code.toLong(), buffer[0].toLong())
+ assertEquals('a'.code.toLong(), buffer[0].toLong()) // getByte doesn't mutate!
+ assertEquals('c'.code.toLong(), buffer[buffer.size - 1].toLong())
+ assertEquals('b'.code.toLong(), buffer[buffer.size - 2].toLong())
+ assertEquals('b'.code.toLong(), buffer[buffer.size - 3].toLong())
}
@Test fun getByteOfEmptyBuffer() {
@@ -279,7 +285,8 @@ class CommonBufferTest {
}
@Suppress("ReplaceAssertBooleanWithAssertEquality")
- @Test fun equalsAndHashCodeEmpty() {
+ @Test
+ fun equalsAndHashCodeEmpty() {
val a = Buffer()
val b = Buffer()
assertTrue(a == b)
@@ -287,7 +294,8 @@ class CommonBufferTest {
}
@Suppress("ReplaceAssertBooleanWithAssertEquality")
- @Test fun equalsAndHashCode() {
+ @Test
+ fun equalsAndHashCode() {
val a = Buffer().writeUtf8("dog")
val b = Buffer().writeUtf8("hotdog")
assertFalse(a == b)
@@ -299,7 +307,8 @@ class CommonBufferTest {
}
@Suppress("ReplaceAssertBooleanWithAssertEquality")
- @Test fun equalsAndHashCodeSpanningSegments() {
+ @Test
+ fun equalsAndHashCodeSpanningSegments() {
val data = ByteArray(1024 * 1024)
val dice = Random(0)
dice.nextBytes(data)
@@ -323,13 +332,13 @@ class CommonBufferTest {
val write1 = Buffer().writeUtf8(
'a'.repeat(Segment.SIZE) +
'b'.repeat(Segment.SIZE) +
- 'c'.repeat(Segment.SIZE)
+ 'c'.repeat(Segment.SIZE),
)
val source = Buffer().writeUtf8(
'a'.repeat(Segment.SIZE) +
'b'.repeat(Segment.SIZE) +
- 'c'.repeat(Segment.SIZE)
+ 'c'.repeat(Segment.SIZE),
)
val mockSink = MockSink()
diff --git a/okio/src/commonTest/kotlin/okio/CommonOptionsTest.kt b/okio/src/commonTest/kotlin/okio/CommonOptionsTest.kt
index bb45321d..ae8156d6 100644
--- a/okio/src/commonTest/kotlin/okio/CommonOptionsTest.kt
+++ b/okio/src/commonTest/kotlin/okio/CommonOptionsTest.kt
@@ -15,11 +15,11 @@
*/
package okio
-import okio.ByteString.Companion.encodeUtf8
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.fail
+import okio.ByteString.Companion.encodeUtf8
class CommonOptionsTest {
/** Confirm that options prefers the first-listed option, not the longest or shortest one. */
@@ -32,12 +32,13 @@ class CommonOptionsTest {
assertEquals(
utf8Options("hotdog", "hoth", "hot").trieString(),
"""
- |hot
- | -> 2
- | d
- | og -> 0
- | h -> 1
- |""".trimMargin()
+ |hot
+ | -> 2
+ | d
+ | og -> 0
+ | h -> 1
+ |
+ """.trimMargin(),
)
}
@@ -70,81 +71,82 @@ class CommonOptionsTest {
"connectTimeout",
"readTimeout",
"writeTimeout",
- "pingInterval"
+ "pingInterval",
)
assertEquals(
options.trieString(),
"""
- |a
- | uthenticator -> 17
- |c
- | a
- | che -> 9
- | e
- | rtificate
- | C
- | hainCleaner -> 13
- | P
- | inner -> 15
- | o
- | n
- | nect
- | T
- | imeout -> 23
- | i
- | on
- | P
- | ool -> 18
- | S
- | pecs -> 3
- | o
- | kieJar -> 8
- |d
- | i
- | spatcher -> 0
- | n
- | s -> 19
- |e
- | ventListenerFactory -> 6
- |f
- | ollow
- | R
- | edirects -> 21
- | S
- | slRedirects -> 20
- |h
- | ostnameVerifier -> 14
- |i
- | nter
- | c
- | eptors -> 4
- | n
- | alCache -> 10
- |n
- | etworkInterceptors -> 5
- |p
- | i
- | ngInterval -> 26
- | r
- | o
- | t
- | ocols -> 2
- | x
- | y -> 1
- |r
- | e
- | a
- | dTimeout -> 24
- | t
- | ryOnConnectionFailure -> 22
- |s
- | o
- | cketFactory -> 11
- | s
- | lSocketFactory -> 12
- |w
- | riteTimeout -> 25
- |""".trimMargin()
+ |a
+ | uthenticator -> 17
+ |c
+ | a
+ | che -> 9
+ | e
+ | rtificate
+ | C
+ | hainCleaner -> 13
+ | P
+ | inner -> 15
+ | o
+ | n
+ | nect
+ | T
+ | imeout -> 23
+ | i
+ | on
+ | P
+ | ool -> 18
+ | S
+ | pecs -> 3
+ | o
+ | kieJar -> 8
+ |d
+ | i
+ | spatcher -> 0
+ | n
+ | s -> 19
+ |e
+ | ventListenerFactory -> 6
+ |f
+ | ollow
+ | R
+ | edirects -> 21
+ | S
+ | slRedirects -> 20
+ |h
+ | ostnameVerifier -> 14
+ |i
+ | nter
+ | c
+ | eptors -> 4
+ | n
+ | alCache -> 10
+ |n
+ | etworkInterceptors -> 5
+ |p
+ | i
+ | ngInterval -> 26
+ | r
+ | o
+ | t
+ | ocols -> 2
+ | x
+ | y -> 1
+ |r
+ | e
+ | a
+ | dTimeout -> 24
+ | t
+ | ryOnConnectionFailure -> 22
+ |s
+ | o
+ | cketFactory -> 11
+ | s
+ | lSocketFactory -> 12
+ |w
+ | riteTimeout -> 25
+ |
+ """.trimMargin(),
)
assertSelect("", -1, options)
assertSelect("a", -1, options)
@@ -212,10 +214,11 @@ class CommonOptionsTest {
assertEquals(
options.trieString(),
"""
- |abc
- | -> 1
- | A -> 0
- |""".trimMargin()
+ |abc
+ | -> 1
+ | A -> 0
+ |
+ """.trimMargin(),
)
assertSelect("abc", 1, options)
assertSelect("abcA", 0, options)
@@ -228,35 +231,39 @@ class CommonOptionsTest {
assertEquals(
utf8Options("a", "ab", "abc", "abcd", "abcde").trieString(),
"""
- |a -> 0
- |""".trimMargin()
+ |a -> 0
+ |
+ """.trimMargin(),
)
assertEquals(
utf8Options("abc", "a", "ab", "abe", "abcd", "abcf").trieString(),
"""
- |a
- | -> 1
- | bc -> 0
- |""".trimMargin()
+ |a
+ | -> 1
+ | bc -> 0
+ |
+ """.trimMargin(),
)
assertEquals(
utf8Options("abc", "ab", "a").trieString(),
"""
- |a
- | -> 2
- | b
- | -> 1
- | c -> 0
- |""".trimMargin()
+ |a
+ | -> 2
+ | b
+ | -> 1
+ | c -> 0
+ |
+ """.trimMargin(),
)
assertEquals(
utf8Options("abcd", "abce", "abc", "abcf", "abcg").trieString(),
"""
- |abc
- | -> 2
- | d -> 0
- | e -> 1
- |""".trimMargin()
+ |abc
+ | -> 2
+ | d -> 0
+ | e -> 1
+ |
+ """.trimMargin(),
)
}
diff --git a/okio/src/commonTest/kotlin/okio/CommonRealBufferedSinkTest.kt b/okio/src/commonTest/kotlin/okio/CommonRealBufferedSinkTest.kt
index abbbae8b..eec7607a 100644
--- a/okio/src/commonTest/kotlin/okio/CommonRealBufferedSinkTest.kt
+++ b/okio/src/commonTest/kotlin/okio/CommonRealBufferedSinkTest.kt
@@ -47,7 +47,7 @@ class CommonRealBufferedSinkTest {
@Test fun bufferedSinkFlush() {
val sink = Buffer()
val bufferedSink = (sink as Sink).buffer()
- bufferedSink.writeByte('a'.toInt())
+ bufferedSink.writeByte('a'.code)
assertEquals(0, sink.size)
bufferedSink.flush()
assertEquals(0, bufferedSink.buffer.size)
@@ -93,9 +93,9 @@ class CommonRealBufferedSinkTest {
@Test fun closeWithExceptionWhenWriting() {
val mockSink = MockSink()
- mockSink.scheduleThrow(0, IOException())
+ mockSink.scheduleThrow(0, IOException("boom"))
val bufferedSink = mockSink.buffer()
- bufferedSink.writeByte('a'.toInt())
+ bufferedSink.writeByte('a'.code)
assertFailsWith<IOException> {
bufferedSink.close()
}
@@ -105,9 +105,9 @@ class CommonRealBufferedSinkTest {
@Test fun closeWithExceptionWhenClosing() {
val mockSink = MockSink()
- mockSink.scheduleThrow(1, IOException())
+ mockSink.scheduleThrow(1, IOException("boom"))
val bufferedSink = mockSink.buffer()
- bufferedSink.writeByte('a'.toInt())
+ bufferedSink.writeByte('a'.code)
assertFailsWith<IOException> {
bufferedSink.close()
}
@@ -120,7 +120,7 @@ class CommonRealBufferedSinkTest {
mockSink.scheduleThrow(0, IOException("first"))
mockSink.scheduleThrow(1, IOException("second"))
val bufferedSink = mockSink.buffer()
- bufferedSink.writeByte('a'.toInt())
+ bufferedSink.writeByte('a'.code)
try {
bufferedSink.close()
fail()
@@ -134,12 +134,12 @@ class CommonRealBufferedSinkTest {
@Test fun operationsAfterClose() {
val mockSink = MockSink()
val bufferedSink = mockSink.buffer()
- bufferedSink.writeByte('a'.toInt())
+ bufferedSink.writeByte('a'.code)
bufferedSink.close()
// Test a sample set of methods.
assertFailsWith<IllegalStateException> {
- bufferedSink.writeByte('a'.toInt())
+ bufferedSink.writeByte('a'.code)
}
assertFailsWith<IllegalStateException> {
@@ -186,7 +186,7 @@ class CommonRealBufferedSinkTest {
val write3 = Buffer().writeUtf8("c".repeat(Segment.SIZE))
val source = Buffer().writeUtf8(
- "${"a".repeat(Segment.SIZE)}${"b".repeat(Segment.SIZE)}${"c".repeat(Segment.SIZE)}"
+ "${"a".repeat(Segment.SIZE)}${"b".repeat(Segment.SIZE)}${"c".repeat(Segment.SIZE)}",
)
val mockSink = MockSink()
@@ -196,7 +196,7 @@ class CommonRealBufferedSinkTest {
mockSink.assertLog(
"write($write1, ${write1.size})",
"write($write2, ${write2.size})",
- "write($write3, ${write3.size})"
+ "write($write3, ${write3.size})",
)
}
}
diff --git a/okio/src/commonTest/kotlin/okio/CommonRealBufferedSourceTest.kt b/okio/src/commonTest/kotlin/okio/CommonRealBufferedSourceTest.kt
index 4663919a..6756c5a5 100644
--- a/okio/src/commonTest/kotlin/okio/CommonRealBufferedSourceTest.kt
+++ b/okio/src/commonTest/kotlin/okio/CommonRealBufferedSourceTest.kt
@@ -36,7 +36,7 @@ class CommonRealBufferedSourceTest {
).buffer()
assertEquals(6, buffer.size)
- assertEquals(-1, bufferedSource.indexOf('e'.toByte(), 0, 4))
+ assertEquals(-1, bufferedSource.indexOf('e'.code.toByte(), 0, 4))
assertEquals(2, buffer.size)
}
@@ -141,7 +141,7 @@ class CommonRealBufferedSourceTest {
val write3 = Buffer().writeUtf8("c".repeat(Segment.SIZE))
val source = Buffer().writeUtf8(
- "${"a".repeat(Segment.SIZE)}${"b".repeat(Segment.SIZE)}${"c".repeat(Segment.SIZE)}"
+ "${"a".repeat(Segment.SIZE)}${"b".repeat(Segment.SIZE)}${"c".repeat(Segment.SIZE)}",
)
val mockSink = MockSink()
@@ -150,7 +150,7 @@ class CommonRealBufferedSourceTest {
mockSink.assertLog(
"write($write1, ${write1.size})",
"write($write2, ${write2.size})",
- "write($write3, ${write3.size})"
+ "write($write3, ${write3.size})",
)
}
}
diff --git a/okio/src/commonTest/kotlin/okio/ForwardingSourceTest.kt b/okio/src/commonTest/kotlin/okio/ForwardingSourceTest.kt
new file mode 100644
index 00000000..70c3fa45
--- /dev/null
+++ b/okio/src/commonTest/kotlin/okio/ForwardingSourceTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class ForwardingSourceTest {
+ val source = Buffer().writeUtf8("Delegate")
+
+ @Test
+ fun testForwardingSourceOverrides() {
+ val forwarder = "Forwarder"
+ val newSource = Buffer().writeUtf8(forwarder)
+ val forwardingSource = object : ForwardingSource(source) {
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ return newSource.read(sink, byteCount)
+ }
+ }
+
+ assertEquals("Forwarder", forwardingSource.buffer().readUtf8())
+ }
+
+ @Test
+ fun testForwardingSourceDelegates() {
+ val forwardingSource = object : ForwardingSource(source) {
+ }
+
+ assertEquals("Delegate", forwardingSource.buffer().readUtf8())
+ }
+
+ @Test
+ fun testToString() {
+ val forwardingSource = object : ForwardingSource(source) {
+ }
+
+ assertTrue(forwardingSource.toString().endsWith("([text=Delegate])"))
+ }
+}
diff --git a/okio/src/commonTest/kotlin/okio/HashingSinkTest.kt b/okio/src/commonTest/kotlin/okio/HashingSinkTest.kt
index db1aeeec..bb88be7a 100644
--- a/okio/src/commonTest/kotlin/okio/HashingSinkTest.kt
+++ b/okio/src/commonTest/kotlin/okio/HashingSinkTest.kt
@@ -15,14 +15,14 @@
*/
package okio
+import kotlin.test.Test
+import kotlin.test.assertEquals
import okio.HashingSink.Companion.hmacSha1
import okio.HashingSink.Companion.hmacSha256
import okio.HashingSink.Companion.hmacSha512
import okio.HashingSink.Companion.sha1
import okio.HashingSink.Companion.sha256
import okio.HashingSink.Companion.sha512
-import kotlin.test.Test
-import kotlin.test.assertEquals
class HashingSinkTest {
private val source = Buffer()
diff --git a/okio/src/commonTest/kotlin/okio/HashingSourceTest.kt b/okio/src/commonTest/kotlin/okio/HashingSourceTest.kt
index 83e2e264..7111edf4 100644
--- a/okio/src/commonTest/kotlin/okio/HashingSourceTest.kt
+++ b/okio/src/commonTest/kotlin/okio/HashingSourceTest.kt
@@ -15,15 +15,15 @@
*/
package okio
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.fail
import okio.HashingSource.Companion.hmacSha1
import okio.HashingSource.Companion.hmacSha256
import okio.HashingSource.Companion.hmacSha512
import okio.HashingSource.Companion.md5
import okio.HashingSource.Companion.sha1
import okio.HashingSource.Companion.sha256
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.fail
class HashingSourceTest {
private val source = Buffer()
@@ -82,11 +82,11 @@ class HashingSourceTest {
val hashingSource = sha256(source)
val bufferedSource = hashingSource.buffer()
source.writeUtf8("a")
- assertEquals('a'.toLong(), bufferedSource.readUtf8CodePoint().toLong())
+ assertEquals('a'.code.toLong(), bufferedSource.readUtf8CodePoint().toLong())
source.writeUtf8("b")
- assertEquals('b'.toLong(), bufferedSource.readUtf8CodePoint().toLong())
+ assertEquals('b'.code.toLong(), bufferedSource.readUtf8CodePoint().toLong())
source.writeUtf8("c")
- assertEquals('c'.toLong(), bufferedSource.readUtf8CodePoint().toLong())
+ assertEquals('c'.code.toLong(), bufferedSource.readUtf8CodePoint().toLong())
assertEquals(HashingTest.SHA256_abc, hashingSource.hash)
}
diff --git a/okio/src/commonTest/kotlin/okio/HashingTest.kt b/okio/src/commonTest/kotlin/okio/HashingTest.kt
index 1cae58d0..d290dc0c 100644
--- a/okio/src/commonTest/kotlin/okio/HashingTest.kt
+++ b/okio/src/commonTest/kotlin/okio/HashingTest.kt
@@ -15,10 +15,10 @@
*/
package okio
-import okio.ByteString.Companion.decodeHex
-import okio.ByteString.Companion.encodeUtf8
import kotlin.test.Test
import kotlin.test.assertEquals
+import okio.ByteString.Companion.decodeHex
+import okio.ByteString.Companion.encodeUtf8
class HashingTest {
@Test fun byteStringMd5() {
diff --git a/okio/src/commonTest/kotlin/okio/util.kt b/okio/src/commonTest/kotlin/okio/OkioTesting.kt
index 953eef88..8dbdd2a1 100644
--- a/okio/src/commonTest/kotlin/okio/util.kt
+++ b/okio/src/commonTest/kotlin/okio/OkioTesting.kt
@@ -16,11 +16,6 @@
package okio
import kotlin.random.Random
-import kotlin.test.assertEquals
-
-fun Char.repeat(count: Int): String {
- return toString().repeat(count)
-}
fun segmentSizes(buffer: Buffer): List<Int> {
var segment = buffer.head ?: return emptyList()
@@ -34,17 +29,6 @@ fun segmentSizes(buffer: Buffer): List<Int> {
return sizes
}
-fun assertArrayEquals(a: ByteArray, b: ByteArray) {
- assertEquals(a.contentToString(), b.contentToString())
-}
-
-fun randomBytes(length: Int): ByteString {
- val random = Random(0)
- val randomBytes = ByteArray(length)
- random.nextBytes(randomBytes)
- return ByteString.of(*randomBytes)
-}
-
fun bufferWithRandomSegmentLayout(dice: Random, data: ByteArray): Buffer {
val result = Buffer()
@@ -92,3 +76,24 @@ fun makeSegments(source: ByteString): ByteString {
}
return buffer.snapshot()
}
+
+/**
+ * Returns a string with all '\' slashes replaced with '/' slashes. This is useful for test
+ * assertions that intend to ignore slashes.
+ */
+fun Path.withUnixSlashes(): String {
+ return toString().replace('\\', '/')
+}
+
+expect fun assertRelativeTo(
+ a: Path,
+ b: Path,
+ bRelativeToA: Path,
+ sameAsNio: Boolean = true,
+)
+
+expect fun assertRelativeToFails(
+ a: Path,
+ b: Path,
+ sameAsNio: Boolean = true,
+): IllegalArgumentException
diff --git a/okio/src/commonTest/kotlin/okio/PathTest.kt b/okio/src/commonTest/kotlin/okio/PathTest.kt
new file mode 100644
index 00000000..cb2920d7
--- /dev/null
+++ b/okio/src/commonTest/kotlin/okio/PathTest.kt
@@ -0,0 +1,777 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import okio.Path.Companion.toPath
+
+class PathTest {
+ @Test
+ fun unixRoot() {
+ val path = "/".toPath()
+ assertEquals(path, path.normalized())
+ assertEquals(path, path.root)
+ assertEquals(listOf(), path.segments)
+ assertEquals("/", path.toString())
+ assertNull(path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("", path.name)
+ assertTrue(path.isAbsolute)
+ assertTrue(path.isRoot)
+ }
+
+ @Test
+ fun unixAbsolutePath() {
+ val path = "/home/jesse/todo.txt".toPath()
+ assertEquals(path, path.normalized())
+ assertEquals("/".toPath(), path.root)
+ assertEquals(listOf("home", "jesse", "todo.txt"), path.segments)
+ assertEquals("/home/jesse/todo.txt", path.toString())
+ assertEquals("/home/jesse".toPath(), path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("todo.txt", path.name)
+ assertTrue(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun unixRelativePath() {
+ val path = "project/todo.txt".toPath()
+ assertEquals(path, path.normalized())
+ assertNull(path.root)
+ assertEquals(listOf("project", "todo.txt"), path.segments)
+ assertEquals("project/todo.txt", path.toString())
+ assertEquals("project".toPath(), path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("todo.txt", path.name)
+ assertFalse(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun unixRelativePathWithDots() {
+ val path = "../../project/todo.txt".toPath()
+ assertEquals(path, path.normalized())
+ assertNull(path.root)
+ assertEquals(listOf("..", "..", "project", "todo.txt"), path.segments)
+ assertEquals("../../project/todo.txt", path.toString())
+ assertEquals("../../project".toPath(), path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("todo.txt", path.name)
+ assertFalse(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun unixRelativeSeriesOfDotDots() {
+ val path = "../../..".toPath()
+ assertEquals(path, path.normalized())
+ assertNull(path.root)
+ assertEquals(listOf("..", "..", ".."), path.segments)
+ assertEquals("../../..", path.toString())
+ assertNull(path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("..", path.name)
+ assertFalse(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun unixAbsoluteSeriesOfDotDots() {
+ val path = "/../../..".toPath()
+ assertEquals(path, path.normalized())
+ assertEquals("/".toPath(), path.root)
+ assertEquals(listOf(), path.segments)
+ assertEquals("/", path.toString())
+ assertNull(path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("", path.name)
+ assertTrue(path.isAbsolute)
+ assertTrue(path.isRoot)
+ }
+
+ @Test
+ fun unixAbsoluteSingleDot() {
+ val path = "/.".toPath()
+ assertEquals(path, path.normalized())
+ assertEquals("/".toPath(), path.root)
+ assertEquals(listOf(), path.segments)
+ assertEquals("/", path.toString())
+ assertNull(path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("", path.name)
+ assertTrue(path.isAbsolute)
+ assertTrue(path.isRoot)
+ }
+
+ @Test
+ fun unixRelativeDoubleDots() {
+ val path = "..".toPath()
+ assertEquals(path, path.normalized())
+ assertNull(path.root)
+ assertEquals(listOf(".."), path.segments)
+ assertEquals("..", path.toString())
+ assertNull(path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("..", path.name)
+ assertFalse(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun unixRelativeSingleDot() {
+ val path = ".".toPath()
+ assertEquals(path, path.normalized())
+ assertNull(path.root)
+ assertEquals(listOf("."), path.segments)
+ assertEquals(".", path.toString())
+ assertNull(path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals(".", path.name)
+ assertFalse(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun windowsVolumeLetter() {
+ val path = "C:\\".toPath()
+ assertEquals(path, path.normalized())
+ assertEquals("C:\\".toPath(), path.root)
+ assertEquals(listOf(), path.segments)
+ assertEquals("C:\\", path.toString())
+ assertNull(path.parent)
+ assertEquals('C', path.volumeLetter)
+ assertEquals("", path.name)
+ assertTrue(path.isAbsolute)
+ assertTrue(path.isRoot)
+ }
+
+ @Test
+ fun windowsAbsolutePathWithVolumeLetter() {
+ val path = "C:\\Windows\\notepad.exe".toPath()
+ assertEquals(path, path.normalized())
+ assertEquals("C:\\".toPath(), path.root)
+ assertEquals(listOf("Windows", "notepad.exe"), path.segments)
+ assertEquals("C:\\Windows\\notepad.exe", path.toString())
+ assertEquals("C:\\Windows".toPath(), path.parent)
+ assertEquals('C', path.volumeLetter)
+ assertEquals("notepad.exe", path.name)
+ assertTrue(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun windowsAbsolutePath() {
+ val path = "\\".toPath()
+ assertEquals(path, path.normalized())
+ assertEquals("\\".toPath(), path.root)
+ assertEquals(listOf(), path.segments)
+ assertEquals("\\", path.toString())
+ assertEquals(null, path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("", path.name)
+ assertTrue(path.isAbsolute)
+ assertTrue(path.isRoot)
+ }
+
+ @Test
+ fun windowsAbsolutePathWithoutVolumeLetter() {
+ val path = "\\Windows\\notepad.exe".toPath()
+ assertEquals(path, path.normalized())
+ assertEquals("\\".toPath(), path.root)
+ assertEquals(listOf("Windows", "notepad.exe"), path.segments)
+ assertEquals("\\Windows\\notepad.exe", path.toString())
+ assertEquals("\\Windows".toPath(), path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("notepad.exe", path.name)
+ assertTrue(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun windowsRelativePathWithVolumeLetter() {
+ val path = "C:Windows\\notepad.exe".toPath()
+ assertEquals(path, path.normalized())
+ assertNull(path.root)
+ assertEquals(listOf("C:Windows", "notepad.exe"), path.segments)
+ assertEquals("C:Windows\\notepad.exe", path.toString())
+ assertEquals("C:Windows".toPath(), path.parent)
+ assertEquals('C', path.volumeLetter)
+ assertEquals("notepad.exe", path.name)
+ assertFalse(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun windowsVolumeLetterRelative() {
+ val path = "C:".toPath()
+ assertEquals(path, path.normalized())
+ assertNull(path.root)
+ assertEquals(listOf("C:"), path.segments)
+ assertEquals("C:", path.toString())
+ assertNull(path.parent)
+ assertEquals('C', path.volumeLetter)
+ assertEquals("", path.name)
+ assertFalse(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun windowsRelativePath() {
+ val path = "Windows\\notepad.exe".toPath()
+ assertEquals(path, path.normalized())
+ assertNull(path.root)
+ assertEquals(listOf("Windows", "notepad.exe"), path.segments)
+ assertEquals("Windows\\notepad.exe", path.toString())
+ assertEquals("Windows".toPath(), path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("notepad.exe", path.name)
+ assertFalse(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun windowsUncServer() {
+ val path = "\\\\server".toPath()
+ assertEquals(path, path.normalized())
+ assertEquals("\\\\server".toPath(), path.root)
+ assertEquals(listOf(), path.segments)
+ assertEquals("\\\\server", path.toString())
+ assertNull(path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("server", path.name)
+ assertTrue(path.isAbsolute)
+ assertTrue(path.isRoot)
+ }
+
+ @Test
+ fun windowsUncAbsolutePath() {
+ val path = "\\\\server\\project\\notes.txt".toPath()
+ assertEquals(path, path.normalized())
+ assertEquals("\\\\server".toPath(), path.root)
+ assertEquals(listOf("project", "notes.txt"), path.segments)
+ assertEquals("\\\\server\\project\\notes.txt", path.toString())
+ assertEquals("\\\\server\\project".toPath(), path.parent)
+ assertNull(path.volumeLetter)
+ assertEquals("notes.txt", path.name)
+ assertTrue(path.isAbsolute)
+ assertFalse(path.isRoot)
+ }
+
+ @Test
+ fun absolutePathTraversalWithDivOperator() {
+ val root = "/".toPath()
+ assertEquals("/home".toPath(), root / "home")
+ assertEquals("/home/jesse".toPath(), root / "home" / "jesse")
+ assertEquals("/home/jesse/..".toPath(), root / "home" / "jesse" / "..")
+ assertEquals("/home/jesse/../jake".toPath(), root / "home" / "jesse" / ".." / "jake")
+ }
+
+ @Test
+ fun relativePathTraversalWithDivOperator() {
+ val slash = Path.DIRECTORY_SEPARATOR
+ val cwd = ".".toPath()
+ assertEquals("home".toPath(), cwd / "home")
+ assertEquals("home${slash}jesse".toPath(), cwd / "home" / "jesse")
+ assertEquals("home${slash}jesse$slash..".toPath(), cwd / "home" / "jesse" / "..")
+ assertEquals(
+ "home${slash}jesse$slash..${slash}jake".toPath(),
+ cwd / "home" / "jesse" / ".." / "jake",
+ )
+ }
+
+ @Test
+ fun relativePathTraversalWithDots() {
+ val slash = Path.DIRECTORY_SEPARATOR
+ val cwd = ".".toPath()
+ assertEquals("..".toPath(), cwd / "..")
+ assertEquals("..$slash..".toPath(), cwd / ".." / "..")
+ assertEquals("..$slash..${slash}etc".toPath(), cwd / ".." / ".." / "etc")
+ assertEquals(
+ "..$slash..${slash}etc${slash}passwd".toPath(),
+ cwd / ".." / ".." / "etc" / "passwd",
+ )
+ }
+
+ @Test
+ fun pathTraversalBaseIgnoredIfChildIsAnAbsolutePath() {
+ assertEquals("/home".toPath(), "".toPath() / "/home")
+ assertEquals("/home".toPath(), "relative".toPath() / "/home")
+ assertEquals("/home".toPath(), "/base".toPath() / "/home")
+ assertEquals("/home".toPath(), "/".toPath() / "/home")
+ }
+
+ @Test
+ fun stringToAbsolutePath() {
+ assertEquals("/", "/".toPath().toString())
+ assertEquals("/a", "/a".toPath().toString())
+ assertEquals("/a", "/a/".toPath().toString())
+ assertEquals("/a/b/c", "/a/b/c".toPath().toString())
+ assertEquals("/a/b/c", "/a/b/c/".toPath().toString())
+ assertEquals("/", "/".toPath(normalize = true).toString())
+ assertEquals("/a", "/a".toPath(normalize = true).toString())
+ assertEquals("/a", "/a/".toPath(normalize = true).toString())
+ assertEquals("/a/b/c", "/a/b/c".toPath(normalize = true).toString())
+ assertEquals("/a/b/c", "/a/b/c/".toPath(normalize = true).toString())
+ }
+
+ @Test
+ fun stringToAbsolutePathWithTraversal() {
+ assertEquals("/", "/..".toPath().toString())
+ assertEquals("/", "/../".toPath().toString())
+ assertEquals("/", "/../..".toPath().toString())
+ assertEquals("/", "/../../".toPath().toString())
+ assertEquals("/", "/..".toPath(normalize = true).toString())
+ assertEquals("/", "/../".toPath(normalize = true).toString())
+ assertEquals("/", "/../..".toPath(normalize = true).toString())
+ assertEquals("/", "/../../".toPath(normalize = true).toString())
+ }
+
+ @Test
+ fun stringToAbsolutePathWithEmptySegments() {
+ assertEquals("/", "//".toPath().toString())
+ assertEquals("/a", "//a".toPath().toString())
+ assertEquals("/a", "/a//".toPath().toString())
+ assertEquals("/a", "//a//".toPath().toString())
+ assertEquals("/a/b", "/a/b//".toPath().toString())
+ assertEquals("/", "//".toPath(normalize = true).toString())
+ assertEquals("/a", "//a".toPath(normalize = true).toString())
+ assertEquals("/a", "/a//".toPath(normalize = true).toString())
+ assertEquals("/a", "//a//".toPath(normalize = true).toString())
+ assertEquals("/a/b", "/a/b//".toPath(normalize = true).toString())
+ }
+
+ @Test
+ fun stringToAbsolutePathWithDots() {
+ assertEquals("/", "/./".toPath().toString())
+ assertEquals("/a", "/./a".toPath().toString())
+ assertEquals("/a", "/a/./".toPath().toString())
+ assertEquals("/a", "/a//.".toPath().toString())
+ assertEquals("/a", "/./a//".toPath().toString())
+ assertEquals("/a", "/a/.".toPath().toString())
+ assertEquals("/a", "//a/./".toPath().toString())
+ assertEquals("/a", "//a/./.".toPath().toString())
+ assertEquals("/a/b", "/a/./b/".toPath().toString())
+ assertEquals("/", "/./".toPath(normalize = true).toString())
+ assertEquals("/a", "/./a".toPath(normalize = true).toString())
+ assertEquals("/a", "/a/./".toPath(normalize = true).toString())
+ assertEquals("/a", "/a//.".toPath(normalize = true).toString())
+ assertEquals("/a", "/./a//".toPath(normalize = true).toString())
+ assertEquals("/a", "/a/.".toPath(normalize = true).toString())
+ assertEquals("/a", "//a/./".toPath(normalize = true).toString())
+ assertEquals("/a", "//a/./.".toPath(normalize = true).toString())
+ assertEquals("/a/b", "/a/./b/".toPath(normalize = true).toString())
+ }
+
+ @Test
+ fun stringToRelativePath() {
+ assertEquals(".", "".toPath().toString())
+ assertEquals(".", ".".toPath().toString())
+ assertEquals("a", "a/".toPath().toString())
+ assertEquals("a/b", "a/b".toPath().toString())
+ assertEquals("a/b", "a/b/".toPath().toString())
+ assertEquals("a/b/c/d", "a/b/c/d".toPath().toString())
+ assertEquals("a/b/c/d", "a/b/c/d/".toPath().toString())
+ assertEquals(".", "".toPath(normalize = true).toString())
+ assertEquals(".", ".".toPath(normalize = true).toString())
+ assertEquals("a", "a/".toPath(normalize = true).toString())
+ assertEquals("a/b", "a/b".toPath(normalize = true).toString())
+ assertEquals("a/b", "a/b/".toPath(normalize = true).toString())
+ assertEquals("a/b/c/d", "a/b/c/d".toPath(normalize = true).toString())
+ assertEquals("a/b/c/d", "a/b/c/d/".toPath(normalize = true).toString())
+ }
+
+ @Test
+ fun stringToRelativePathWithTraversal() {
+ assertEquals("..", "..".toPath().toString())
+ assertEquals("..", "../".toPath().toString())
+ assertEquals("a/..", "a/..".toPath().toString())
+ assertEquals("a/..", "a/../".toPath().toString())
+ assertEquals("a/../..", "a/../..".toPath().toString())
+ assertEquals("a/../..", "a/../../".toPath().toString())
+ assertEquals("a/../../..", "a/../../..".toPath().toString())
+ assertEquals("../../b", "../../b".toPath().toString())
+ assertEquals("a/../../../b", "a/../../../b".toPath().toString())
+ assertEquals("a/../../../b/../c", "a/../../../b/../c".toPath().toString())
+ assertEquals("..", "..".toPath(normalize = true).toString())
+ assertEquals("..", "../".toPath(normalize = true).toString())
+ assertEquals(".", "a/..".toPath(normalize = true).toString())
+ assertEquals(".", "a/../".toPath(normalize = true).toString())
+ assertEquals("..", "a/../..".toPath(normalize = true).toString())
+ assertEquals("..", "a/../../".toPath(normalize = true).toString())
+ assertEquals("../..", "a/../../..".toPath(normalize = true).toString())
+ assertEquals("../../b", "../../b".toPath(normalize = true).toString())
+ assertEquals("../../b", "a/../../../b".toPath(normalize = true).toString())
+ assertEquals("../../c", "a/../../../b/../c".toPath(normalize = true).toString())
+ }
+
+ @Test
+ fun stringToRelativePathWithEmptySegments() {
+ assertEquals("a", "a//".toPath().toString())
+ assertEquals("a/b", "a//b".toPath().toString())
+ assertEquals("a/b", "a/b//".toPath().toString())
+ assertEquals("a/b", "a//b//".toPath().toString())
+ assertEquals("a/b/c", "a/b/c//".toPath().toString())
+ assertEquals("a", "a//".toPath(normalize = true).toString())
+ assertEquals("a/b", "a//b".toPath(normalize = true).toString())
+ assertEquals("a/b", "a/b//".toPath(normalize = true).toString())
+ assertEquals("a/b", "a//b//".toPath(normalize = true).toString())
+ assertEquals("a/b/c", "a/b/c//".toPath(normalize = true).toString())
+ }
+
+ @Test
+ fun stringToRelativePathWithDots() {
+ assertEquals(".", ".".toPath().toString())
+ assertEquals(".", "./".toPath().toString())
+ assertEquals(".", "././".toPath().toString())
+ assertEquals("a/..", "././a/..".toPath().toString())
+ assertEquals("a", "a/./".toPath().toString())
+ assertEquals("a/b", "a/./b".toPath().toString())
+ assertEquals("a/b", "a/b/./".toPath().toString())
+ assertEquals("a/b", "a/b//.".toPath().toString())
+ assertEquals("a/b", "a/./b//".toPath().toString())
+ assertEquals("a/b", "a/b/.".toPath().toString())
+ assertEquals("a/b", "a//b/./".toPath().toString())
+ assertEquals("a/b", "a//b/./.".toPath().toString())
+ assertEquals("a/b/c", "a/b/./c/".toPath().toString())
+ assertEquals(".", ".".toPath(normalize = true).toString())
+ assertEquals(".", "./".toPath(normalize = true).toString())
+ assertEquals(".", "././".toPath(normalize = true).toString())
+ assertEquals(".", "././a/..".toPath(normalize = true).toString())
+ assertEquals("a", "a/./".toPath(normalize = true).toString())
+ assertEquals("a/b", "a/./b".toPath(normalize = true).toString())
+ assertEquals("a/b", "a/b/./".toPath(normalize = true).toString())
+ assertEquals("a/b", "a/b//.".toPath(normalize = true).toString())
+ assertEquals("a/b", "a/./b//".toPath(normalize = true).toString())
+ assertEquals("a/b", "a/b/.".toPath(normalize = true).toString())
+ assertEquals("a/b", "a//b/./".toPath(normalize = true).toString())
+ assertEquals("a/b", "a//b/./.".toPath(normalize = true).toString())
+ assertEquals("a/b/c", "a/b/./c/".toPath(normalize = true).toString())
+ }
+
+ @Test
+ fun composingWindowsPath() {
+ assertEquals("C:\\Windows\\notepad.exe".toPath(), "C:\\".toPath() / "Windows" / "notepad.exe")
+ }
+
+ @Test
+ fun windowsResolveAbsolutePath() {
+ assertEquals("\\Users".toPath(), "C:\\Windows".toPath() / "\\Users")
+ }
+
+ @Test
+ fun windowsPathTraversalUp() {
+ assertEquals("C:\\x\\y\\..\\..\\..\\z".toPath(), "C:\\x\\y\\..\\..\\..\\z".toPath())
+ assertEquals("C:x\\y\\..\\..\\..\\z".toPath(), "C:x\\y\\..\\..\\..\\z".toPath())
+ assertEquals("C:\\z".toPath(), "C:\\x\\y\\..\\..\\..\\z".toPath(normalize = true))
+ assertEquals("C:..\\z".toPath(), "C:x\\y\\..\\..\\..\\z".toPath(normalize = true))
+ }
+
+ @Test
+ fun samePathDifferentSlashesAreNotEqual() {
+ assertNotEquals("/a".toPath(), "\\b".toPath())
+ assertNotEquals("a/b".toPath(), "a\\b".toPath())
+ }
+
+ @Test
+ fun samePathNoSlashesAreEqual() {
+ assertEquals("a".toPath().parent!!, "a".toPath().parent!!)
+ assertEquals("a/b".toPath().parent!!, "a\\b".toPath().parent!!)
+ }
+
+ @Test
+ fun relativeToWindowsPaths() {
+ val a = "C:\\Windows\\notepad.exe".toPath()
+ val b = "C:\\".toPath()
+ assertRelativeTo(a, b, "..\\..".toPath(), sameAsNio = false)
+ assertRelativeTo(b, a, "Windows\\notepad.exe".toPath(), sameAsNio = false)
+
+ val c = "C:\\Windows\\".toPath()
+ val d = "C:\\Windows".toPath()
+ assertRelativeTo(c, d, ".".toPath())
+ assertRelativeTo(d, c, ".".toPath())
+
+ val e = "C:\\Windows\\Downloads\\".toPath()
+ val f = "C:\\Windows\\Documents\\Hello.txt".toPath()
+ assertRelativeTo(e, f, "..\\Documents\\Hello.txt".toPath(), sameAsNio = false)
+ assertRelativeTo(f, e, "..\\..\\Downloads".toPath(), sameAsNio = false)
+
+ val g = "C:\\Windows\\".toPath()
+ val h = "D:\\Windows\\".toPath()
+ assertRelativeToFails(g, h, sameAsNio = false)
+ assertRelativeToFails(h, g, sameAsNio = false)
+ }
+
+ @Test
+ fun relativeToWindowsUncPaths() {
+ val a = "\\\\localhost\\c$\\development\\schema.proto".toPath()
+ val b = "\\\\localhost\\c$\\project\\notes.txt".toPath()
+ assertRelativeTo(a, b, "..\\..\\project\\notes.txt".toPath(), sameAsNio = false)
+ assertRelativeTo(b, a, "..\\..\\development\\schema.proto".toPath(), sameAsNio = false)
+
+ val c = "C:\\Windows\\".toPath()
+ val d = "\\\\localhost\\c$\\project\\notes.txt".toPath()
+ assertRelativeToFails(c, d, sameAsNio = false)
+ assertRelativeToFails(d, c, sameAsNio = false)
+ }
+
+ @Test
+ fun absoluteUnixRoot() {
+ val a = "/Users/jesse/hello.txt".toPath()
+ val b = "/".toPath()
+ assertRelativeTo(a, b, "../../..".toPath())
+ assertRelativeTo(b, a, "Users/jesse/hello.txt".toPath())
+
+ val c = "/Users/jesse/hello.txt".toPath()
+ val d = "/Admin/Secret".toPath()
+ assertRelativeTo(c, d, "../../../Admin/Secret".toPath())
+ assertRelativeTo(d, c, "../../Users/jesse/hello.txt".toPath())
+
+ val e = "/Users/".toPath()
+ val f = "/Users".toPath()
+ assertRelativeTo(e, f, ".".toPath())
+ assertRelativeTo(f, e, ".".toPath())
+ }
+
+ // Note that we handle the normalized version of the paths when computing relative paths.
+ @Test
+ fun relativeToUnnormalizedPath() {
+ val a = "Users/../a".toPath() // `a` if normalized.
+ val b = "Users/b/../c".toPath() // `Users/c` if normalized.
+ assertRelativeToFails(a, b, sameAsNio = false)
+ assertRelativeToFails(b, a, sameAsNio = false)
+ assertRelativeTo(a.normalized(), b.normalized(), "../Users/c".toPath())
+ assertRelativeTo(b.normalized(), a.normalized(), "../../a".toPath())
+ }
+
+ @Test
+ fun relativeToNormalizedPath() {
+ val a = "Users/../a".toPath(normalize = true) // results to `a`.
+ val b = "Users/b/../c".toPath(normalize = true) // results to `Users/c`.
+ assertRelativeTo(a, b, "../Users/c".toPath())
+ assertRelativeTo(b, a, "../../a".toPath())
+ }
+
+ @Test
+ fun absoluteToRelative() {
+ val a = "/Users/jesse/hello.txt".toPath()
+ val b = "Desktop/goodbye.txt".toPath()
+
+ var exception = assertRelativeToFails(a, b)
+ assertEquals(
+ "Paths of different roots cannot be relative to each other: " +
+ "Desktop/goodbye.txt and /Users/jesse/hello.txt",
+ exception.message,
+ )
+
+ exception = assertRelativeToFails(b, a)
+ assertEquals(
+ "Paths of different roots cannot be relative to each other: " +
+ "/Users/jesse/hello.txt and Desktop/goodbye.txt",
+ exception.message,
+ )
+ }
+
+ @Test
+ fun absoluteToAbsolute() {
+ val a = "/Users/jesse/hello.txt".toPath()
+ val b = "/Users/benoit/Desktop/goodbye.txt".toPath()
+ assertRelativeTo(a, b, "../../benoit/Desktop/goodbye.txt".toPath())
+ assertRelativeTo(b, a, "../../../jesse/hello.txt".toPath())
+ }
+
+ @Test
+ fun absoluteToSelf() {
+ val a = "/Users/jesse/hello.txt".toPath()
+ assertRelativeTo(a, a, ".".toPath())
+
+ val b = "/Users/benoit/../jesse/hello.txt".toPath()
+ // NIO normalizes.
+ assertRelativeTo(a, b, "../../benoit/../jesse/hello.txt".toPath(), sameAsNio = false)
+ assertRelativeToFails(b, a, sameAsNio = false)
+ assertRelativeTo(b.normalized(), a, ".".toPath())
+ assertRelativeTo(a, b.normalized(), ".".toPath())
+ }
+
+ @Test
+ fun relativeToSelf() {
+ val a = "Desktop/hello.txt".toPath()
+ assertRelativeTo(a, a, ".".toPath())
+
+ val b = "Documents/../Desktop/hello.txt".toPath()
+ // NIO normalizes.
+ assertRelativeTo(a, b, "../../Documents/../Desktop/hello.txt".toPath(), sameAsNio = false)
+ assertRelativeToFails(b, a, sameAsNio = false)
+ assertRelativeTo(a, b.normalized(), ".".toPath())
+ assertRelativeTo(b.normalized(), a, ".".toPath())
+ }
+
+ @Test
+ fun relativeToRelative() {
+ val a = "Desktop/documents/resume.txt".toPath()
+ val b = "Desktop/documents/2021/taxes.txt".toPath()
+ assertRelativeTo(a, b, "../2021/taxes.txt".toPath(), sameAsNio = false)
+ assertRelativeTo(b, a, "../../resume.txt".toPath(), sameAsNio = false)
+
+ val c = "documents/resume.txt".toPath()
+ val d = "downloads/2021/taxes.txt".toPath()
+ assertRelativeTo(c, d, "../../downloads/2021/taxes.txt".toPath(), sameAsNio = false)
+ assertRelativeTo(d, c, "../../../documents/resume.txt".toPath(), sameAsNio = false)
+ }
+
+ @Test
+ fun relativeToRelativeWithMiddleDots() {
+ val a = "Desktop/documents/a...n".toPath()
+ val b = "Desktop/documents/m...z".toPath()
+ assertRelativeTo(a, b, "../m...z".toPath())
+ assertRelativeTo(b, a, "../a...n".toPath())
+ }
+
+ @Test
+ fun relativeToRelativeWithMiddleDotsInCommonPrefix() {
+ val a = "Desktop/documents/a...n/red".toPath()
+ val b = "Desktop/documents/a...m/blue".toPath()
+ assertRelativeTo(a, b, "../../a...m/blue".toPath())
+ assertRelativeTo(b, a, "../../a...n/red".toPath())
+ }
+
+ @Test
+ fun relativeToRelativeWithUpNavigationPrefix() {
+ // We can't navigate from 'taxes' to 'resumes' because we don't know the name of 'Documents'.
+ // /Users/jwilson/Documents/2021/Current
+ // /Users/jwilson/Documents/resumes
+ // /Users/jwilson/taxes
+ val a = "../../resumes".toPath()
+ val b = "../../../taxes".toPath()
+ assertRelativeTo(a, b, "../../taxes".toPath())
+ assertRelativeToFails(b, a, sameAsNio = false)
+ }
+
+ @Test
+ fun relativeToRelativeDifferentSlashes() {
+ val a = "Desktop/documents/resume.txt".toPath()
+ val b = "Desktop\\documents\\2021\\taxes.txt".toPath()
+ assertRelativeTo(a, b, "../2021/taxes.txt".toPath(), sameAsNio = false)
+ assertRelativeTo(b, a, "..\\..\\resume.txt".toPath(), sameAsNio = false)
+
+ val c = "documents/resume.txt".toPath()
+ val d = "downloads\\2021\\taxes.txt".toPath()
+ assertRelativeTo(c, d, "../../downloads/2021/taxes.txt".toPath(), sameAsNio = false)
+ assertRelativeTo(d, c, "..\\..\\..\\documents\\resume.txt".toPath(), sameAsNio = false)
+ }
+
+ @Test
+ fun windowsUncPathsDoNotDotDot() {
+ assertEquals(
+ """\\localhost\c$\Windows""",
+ """\\localhost\c$\Windows""".toPath().toString(),
+ )
+ assertEquals(
+ """\\127.0.0.1\c$\Windows""",
+ """\\127.0.0.1\c$\Windows""".toPath().toString(),
+ )
+ assertEquals(
+ """\\127.0.0.1\c$\Windows\..\Windows""",
+ """\\127.0.0.1\c$\Windows\..\Windows""".toPath().toString(),
+ )
+ assertEquals(
+ """\\127.0.0.1\..\localhost\c$\Windows""",
+ """\\127.0.0.1\..\localhost\c$\Windows""".toPath().toString(),
+ )
+ assertEquals(
+ """\\127.0.0.1\c$\..\d$""",
+ """\\127.0.0.1\c$\..\d$""".toPath().toString(),
+ )
+
+ assertEquals(
+ """\\localhost\c$\Windows""",
+ """\\localhost\c$\Windows""".toPath(normalize = true).toString(),
+ )
+ assertEquals(
+ """\\127.0.0.1\c$\Windows""",
+ """\\127.0.0.1\c$\Windows""".toPath(normalize = true).toString(),
+ )
+ assertEquals(
+ """\\127.0.0.1\c$\Windows""",
+ """\\127.0.0.1\c$\Windows\..\Windows""".toPath(normalize = true).toString(),
+ )
+ assertEquals(
+ """\\127.0.0.1\localhost\c$\Windows""",
+ """\\127.0.0.1\..\localhost\c$\Windows""".toPath(normalize = true).toString(),
+ )
+ assertEquals(
+ """\\127.0.0.1\d$""",
+ """\\127.0.0.1\c$\..\d$""".toPath(normalize = true).toString(),
+ )
+ assertEquals(
+ """\\127.0.0.1\c$""",
+ """\\..\127.0.0.1\..\c$""".toPath(normalize = true).toString(),
+ )
+ }
+
+ @Test fun normalizeAbsolute() {
+ assertEquals("/", "/.".toPath(normalize = true).toString())
+ assertEquals("/", "/.".toPath(normalize = false).toString())
+ assertEquals("/", "/..".toPath(normalize = true).toString())
+ assertEquals("/", "/..".toPath(normalize = false).toString())
+ assertEquals("/", "/../..".toPath(normalize = true).toString())
+ assertEquals("/", "/../..".toPath(normalize = false).toString())
+
+ assertEquals("/a/b", "/a/./b".toPath(normalize = true).toString())
+ assertEquals("/a/b", "/a/./b".toPath(normalize = false).toString())
+ assertEquals("/a/.../b", "/a/..././b".toPath(normalize = true).toString())
+ assertEquals("/a/.../b", "/a/..././b".toPath(normalize = false).toString())
+ assertEquals("/", "/a/..".toPath(normalize = true).toString())
+ assertEquals("/a/..", "/a/..".toPath(normalize = false).toString())
+ assertEquals("/b", "/../a/../b".toPath(normalize = true).toString())
+ assertEquals("/a/../b", "/../a/../b".toPath(normalize = false).toString())
+ }
+
+ @Test fun normalizeRelative() {
+ assertEquals(".", ".".toPath(normalize = true).toString())
+ assertEquals(".", ".".toPath(normalize = false).toString())
+ assertEquals("..", "..".toPath(normalize = true).toString())
+ assertEquals("..", "..".toPath(normalize = false).toString())
+ assertEquals("../..", "../..".toPath(normalize = true).toString())
+ assertEquals("../..", "../..".toPath(normalize = false).toString())
+
+ assertEquals("a/b", "a/./b".toPath(normalize = true).toString())
+ assertEquals("a/b", "a/./b".toPath(normalize = false).toString())
+ assertEquals("a/.../b", "a/..././b".toPath(normalize = true).toString())
+ assertEquals("a/.../b", "a/..././b".toPath(normalize = false).toString())
+ assertEquals(".", "a/..".toPath(normalize = true).toString())
+ assertEquals("a/..", "a/..".toPath(normalize = false).toString())
+ assertEquals("../b", "../a/../b".toPath(normalize = true).toString())
+ assertEquals("../a/../b", "../a/../b".toPath(normalize = false).toString())
+ }
+
+ @Test fun normalized() {
+ val normalizedRoot = "/".toPath()
+ assertEquals(normalizedRoot, "/a/..".toPath(normalize = true))
+ assertEquals(normalizedRoot, "/a/..".toPath(normalize = false).normalized())
+ assertEquals(normalizedRoot, "/a/..".toPath(normalize = true).normalized())
+
+ val normalizedRelative = "../b".toPath()
+ assertEquals(normalizedRelative, "../a/../b".toPath(normalize = true))
+ assertEquals(normalizedRelative, "../a/../b".toPath(normalize = false).normalized())
+ assertEquals(normalizedRelative, "../a/../b".toPath(normalize = true).normalized())
+ }
+}
diff --git a/okio/src/commonTest/kotlin/okio/UnsafeCursorTest.kt b/okio/src/commonTest/kotlin/okio/UnsafeCursorTest.kt
index 680ebdc2..10290eac 100644
--- a/okio/src/commonTest/kotlin/okio/UnsafeCursorTest.kt
+++ b/okio/src/commonTest/kotlin/okio/UnsafeCursorTest.kt
@@ -17,6 +17,7 @@ package okio
import kotlin.test.Test
import kotlin.test.assertEquals
+import kotlin.test.assertTrue
class UnsafeCursorTest {
@Test fun acquireForRead() {
@@ -43,7 +44,7 @@ class UnsafeCursorTest {
val cursor = buffer.readAndWriteUnsafe()
try {
while (cursor.next() != -1) {
- cursor.data!!.fill('z'.toByte(), cursor.start, cursor.end)
+ cursor.data!!.fill('z'.code.toByte(), cursor.start, cursor.end)
}
} finally {
cursor.close()
@@ -58,7 +59,7 @@ class UnsafeCursorTest {
val cursor = buffer.readAndWriteUnsafe()
try {
cursor.expandBuffer(100)
- cursor.data!!.fill('z'.toByte(), cursor.start, cursor.start + 100)
+ cursor.data!!.fill('z'.code.toByte(), cursor.start, cursor.start + 100)
cursor.resizeBuffer(100L)
} finally {
cursor.close()
@@ -77,11 +78,15 @@ class UnsafeCursorTest {
val cursor = buffer.readAndWriteUnsafe()
try {
cursor.resizeBuffer(100L)
- cursor.data!!.fill('z'.toByte(), cursor.start, cursor.end)
+ cursor.data!!.fill('z'.code.toByte(), cursor.start, cursor.end)
} finally {
cursor.close()
}
assertEquals("z".repeat(100), buffer.readUtf8())
}
+
+ @Test fun testUnsafeCursorIsClosable() {
+ assertTrue(Closeable::class.isInstance(Buffer.UnsafeCursor()))
+ }
}
diff --git a/okio/src/commonTest/kotlin/okio/Utf8KotlinTest.kt b/okio/src/commonTest/kotlin/okio/Utf8KotlinTest.kt
index 1ade4179..337fe83a 100644
--- a/okio/src/commonTest/kotlin/okio/Utf8KotlinTest.kt
+++ b/okio/src/commonTest/kotlin/okio/Utf8KotlinTest.kt
@@ -16,17 +16,17 @@
package okio
-import okio.ByteString.Companion.decodeHex
-import okio.internal.commonAsUtf8ToByteArray
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
+import okio.ByteString.Companion.decodeHex
+import okio.internal.commonAsUtf8ToByteArray
class Utf8KotlinTest {
@Test fun oneByteCharacters() {
assertEncoded("00", 0x00) // Smallest 1-byte character.
- assertEncoded("20", ' '.toInt())
- assertEncoded("7e", '~'.toInt())
+ assertEncoded("20", ' '.code)
+ assertEncoded("7e", '~'.code)
assertEncoded("7f", 0x7f) // Largest 1-byte character.
}
@@ -90,10 +90,10 @@ class Utf8KotlinTest {
@Test fun highSurrogateFollowedByNonSurrogate() {
assertStringEncoded("3fee8080", "\ud800\ue000") // "?\ue000": Following character is too high.
- assertCodePointDecoded("f090ee8080", REPLACEMENT_CODE_POINT, '\ue000'.toInt())
+ assertCodePointDecoded("f090ee8080", REPLACEMENT_CODE_POINT, '\ue000'.code)
assertStringEncoded("3f61", "\ud800\u0061") // "?a": Following character is too low.
- assertCodePointDecoded("f09061", REPLACEMENT_CODE_POINT, 'a'.toInt())
+ assertCodePointDecoded("f09061", REPLACEMENT_CODE_POINT, 'a'.code)
}
@Test fun doubleLowSurrogate() {
@@ -113,7 +113,7 @@ class Utf8KotlinTest {
@Test fun writeSurrogateCodePoint() {
assertStringEncoded("ed9fbf", "\ud7ff") // Below lowest surrogate is okay.
- assertCodePointDecoded("ed9fbf", '\ud7ff'.toInt())
+ assertCodePointDecoded("ed9fbf", '\ud7ff'.code)
assertStringEncoded("3f", "\ud800") // Lowest surrogate gets '?'.
assertCodePointDecoded("eda080", REPLACEMENT_CODE_POINT)
@@ -122,7 +122,7 @@ class Utf8KotlinTest {
assertCodePointDecoded("edbfbf", REPLACEMENT_CODE_POINT)
assertStringEncoded("ee8080", "\ue000") // Above highest surrogate is okay.
- assertCodePointDecoded("ee8080", '\ue000'.toInt())
+ assertCodePointDecoded("ee8080", '\ue000'.code)
}
@Test fun size() {
diff --git a/okio/src/hashFunctions/kotlin/okio/internal/HashFunction.kt b/okio/src/hashFunctions/kotlin/okio/internal/HashFunction.kt
index 5948ab0f..870d0a68 100644
--- a/okio/src/hashFunctions/kotlin/okio/internal/HashFunction.kt
+++ b/okio/src/hashFunctions/kotlin/okio/internal/HashFunction.kt
@@ -20,7 +20,7 @@ internal interface HashFunction {
fun update(
input: ByteArray,
offset: Int = 0,
- byteCount: Int = input.size
+ byteCount: Int = input.size,
)
fun digest(): ByteArray
diff --git a/okio/src/hashFunctions/kotlin/okio/internal/Hmac.kt b/okio/src/hashFunctions/kotlin/okio/internal/Hmac.kt
index 95e3c5dd..b8c7231b 100644
--- a/okio/src/hashFunctions/kotlin/okio/internal/Hmac.kt
+++ b/okio/src/hashFunctions/kotlin/okio/internal/Hmac.kt
@@ -20,7 +20,7 @@ import okio.xor
internal class Hmac private constructor(
private val hashFunction: HashFunction,
- private val outerKey: ByteArray
+ private val outerKey: ByteArray,
) : HashFunction {
override fun update(input: ByteArray, offset: Int, byteCount: Int) {
hashFunction.update(input, offset, byteCount)
@@ -51,7 +51,7 @@ internal class Hmac private constructor(
private fun create(
key: ByteString,
hashFunction: HashFunction,
- blockLength: Int
+ blockLength: Int,
): Hmac {
val keySize = key.size
val paddedKey = when {
@@ -68,7 +68,7 @@ internal class Hmac private constructor(
return Hmac(
hashFunction,
- outerKey
+ outerKey,
)
}
}
diff --git a/okio/src/hashFunctions/kotlin/okio/internal/Md5.kt b/okio/src/hashFunctions/kotlin/okio/internal/Md5.kt
index e43e4476..678643d7 100644
--- a/okio/src/hashFunctions/kotlin/okio/internal/Md5.kt
+++ b/okio/src/hashFunctions/kotlin/okio/internal/Md5.kt
@@ -31,7 +31,7 @@ internal class Md5 : HashFunction {
override fun update(
input: ByteArray,
offset: Int,
- byteCount: Int
+ byteCount: Int,
) {
messageLength += byteCount
var pos = offset
@@ -188,7 +188,7 @@ internal class Md5 : HashFunction {
private val s = intArrayOf(
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9,
14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15,
- 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
+ 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
)
private val k = intArrayOf(
@@ -200,7 +200,7 @@ internal class Md5 : HashFunction {
-155497632, -1094730640, 681279174, -358537222, -722521979, 76029189, -640364487, -421815835,
530742520, -995338651, -198630844, 1126891415, -1416354905, -57434055, 1700485571,
-1894986606, -1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649,
- -145523070, -1120210379, 718787259, -343485551
+ -145523070, -1120210379, 718787259, -343485551,
)
}
}
diff --git a/okio/src/hashFunctions/kotlin/okio/internal/Sha1.kt b/okio/src/hashFunctions/kotlin/okio/internal/Sha1.kt
index e9a8de16..b1fc5b7d 100644
--- a/okio/src/hashFunctions/kotlin/okio/internal/Sha1.kt
+++ b/okio/src/hashFunctions/kotlin/okio/internal/Sha1.kt
@@ -32,7 +32,7 @@ internal class Sha1 : HashFunction {
override fun update(
input: ByteArray,
offset: Int,
- byteCount: Int
+ byteCount: Int,
) {
messageLength += byteCount
var pos = offset
diff --git a/okio/src/hashFunctions/kotlin/okio/internal/Sha256.kt b/okio/src/hashFunctions/kotlin/okio/internal/Sha256.kt
index aa0d24d0..550ab2d8 100644
--- a/okio/src/hashFunctions/kotlin/okio/internal/Sha256.kt
+++ b/okio/src/hashFunctions/kotlin/okio/internal/Sha256.kt
@@ -35,7 +35,7 @@ internal class Sha256 : HashFunction {
override fun update(
input: ByteArray,
offset: Int,
- byteCount: Int
+ byteCount: Int,
) {
messageLength += byteCount
var pos = offset
@@ -100,7 +100,7 @@ internal class Sha256 : HashFunction {
}
private fun hash(
- words: IntArray
+ words: IntArray,
) {
val localK = k
var a = h0
@@ -247,7 +247,7 @@ internal class Sha256 : HashFunction {
1986661051, -2117940946, -1838011259, -1564481375, -1474664885, -1035236496, -949202525,
-778901479, -694614492, -200395387, 275423344, 430227734, 506948616, 659060556, 883997877,
958139571, 1322822218, 1537002063, 1747873779, 1955562222, 2024104815, -2067236844,
- -1933114872, -1866530822, -1538233109, -1090935817, -965641998
+ -1933114872, -1866530822, -1538233109, -1090935817, -965641998,
)
}
}
diff --git a/okio/src/hashFunctions/kotlin/okio/internal/Sha512.kt b/okio/src/hashFunctions/kotlin/okio/internal/Sha512.kt
index 390a50a1..86a314b2 100644
--- a/okio/src/hashFunctions/kotlin/okio/internal/Sha512.kt
+++ b/okio/src/hashFunctions/kotlin/okio/internal/Sha512.kt
@@ -35,7 +35,7 @@ internal class Sha512 : HashFunction {
override fun update(
input: ByteArray,
offset: Int,
- byteCount: Int
+ byteCount: Int,
) {
messageLength += byteCount
var pos = offset
@@ -285,7 +285,7 @@ internal class Sha512 : HashFunction {
-3880063495543823972L, -3348786107499101689L, -1523767162380948706L, -757361751448694408L,
500013540394364858L, 748580250866718886L, 1242879168328830382L, 1977374033974150939L,
2944078676154940804L, 3659926193048069267L, 4368137639120453308L, 4836135668995329356L,
- 5532061633213252278L, 6448918945643986474L, 6902733635092675308L, 7801388544844847127L
+ 5532061633213252278L, 6448918945643986474L, 6902733635092675308L, 7801388544844847127L,
)
}
}
diff --git a/okio/src/jsMain/kotlin/okio/FileSystem.kt b/okio/src/jsMain/kotlin/okio/FileSystem.kt
new file mode 100644
index 00000000..ea1bb4bf
--- /dev/null
+++ b/okio/src/jsMain/kotlin/okio/FileSystem.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import okio.Path.Companion.toPath
+import okio.internal.commonCopy
+import okio.internal.commonCreateDirectories
+import okio.internal.commonDeleteRecursively
+import okio.internal.commonExists
+import okio.internal.commonListRecursively
+import okio.internal.commonMetadata
+
+actual abstract class FileSystem {
+ actual abstract fun canonicalize(path: Path): Path
+
+ actual fun metadata(path: Path): FileMetadata = commonMetadata(path)
+
+ actual abstract fun metadataOrNull(path: Path): FileMetadata?
+
+ actual fun exists(path: Path): Boolean = commonExists(path)
+
+ actual abstract fun list(dir: Path): List<Path>
+
+ actual abstract fun listOrNull(dir: Path): List<Path>?
+
+ actual open fun listRecursively(dir: Path, followSymlinks: Boolean): Sequence<Path> =
+ commonListRecursively(dir, followSymlinks)
+
+ actual abstract fun openReadOnly(file: Path): FileHandle
+
+ actual abstract fun openReadWrite(
+ file: Path,
+ mustCreate: Boolean,
+ mustExist: Boolean,
+ ): FileHandle
+
+ actual abstract fun source(file: Path): Source
+
+ actual inline fun <T> read(file: Path, readerAction: BufferedSource.() -> T): T {
+ return source(file).buffer().use {
+ it.readerAction()
+ }
+ }
+
+ actual abstract fun sink(file: Path, mustCreate: Boolean): Sink
+
+ actual inline fun <T> write(
+ file: Path,
+ mustCreate: Boolean,
+ writerAction: BufferedSink.() -> T,
+ ): T {
+ return sink(file, mustCreate).buffer().use {
+ it.writerAction()
+ }
+ }
+
+ actual abstract fun appendingSink(file: Path, mustExist: Boolean): Sink
+
+ actual abstract fun createDirectory(dir: Path, mustCreate: Boolean)
+
+ actual fun createDirectories(dir: Path, mustCreate: Boolean): Unit = commonCreateDirectories(dir, mustCreate)
+
+ actual abstract fun atomicMove(source: Path, target: Path)
+
+ actual open fun copy(source: Path, target: Path): Unit = commonCopy(source, target)
+
+ actual abstract fun delete(path: Path, mustExist: Boolean)
+
+ actual open fun deleteRecursively(fileOrDirectory: Path, mustExist: Boolean): Unit =
+ commonDeleteRecursively(fileOrDirectory, mustExist)
+
+ actual abstract fun createSymlink(source: Path, target: Path)
+
+ actual companion object {
+ actual val SYSTEM_TEMPORARY_DIRECTORY: Path = tmpdir.toPath()
+ }
+}
diff --git a/okio/src/jsMain/kotlin/okio/JsPlatform.kt b/okio/src/jsMain/kotlin/okio/JsPlatform.kt
new file mode 100644
index 00000000..04350d15
--- /dev/null
+++ b/okio/src/jsMain/kotlin/okio/JsPlatform.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+/*
+ * This file exposes Node.js `os` and `path` APIs to Kotlin/JS, using reasonable default behaviors
+ * if those symbols aren't available.
+ *
+ * This was originally implemented using a Kotlin/JS [JsModule], but that broke browser builds
+ * because modules weren't available to browsers.
+ *
+ * https://nodejs.org/api/os.html
+ * https://github.com/browserify/path-browserify/blob/master/index.js
+ * https://github.com/CoderPuppy/os-browserify/blob/master/browser.js
+ */
+
+internal actual val PLATFORM_DIRECTORY_SEPARATOR: String
+ get() = (path?.sep) as? String ?: "/"
+
+private val os: dynamic
+ get() {
+ return try {
+ js("require('os')")
+ } catch (t: Throwable) {
+ null
+ }
+ }
+
+private val path: dynamic
+ get() {
+ return try {
+ js("require('path')")
+ } catch (t: Throwable) {
+ null
+ }
+ }
+
+internal val tmpdir: String
+ get() = os?.tmpdir() as? String ?: "/tmp"
diff --git a/okio/src/jvmMain/kotlin/okio/-DeprecatedOkio.kt b/okio/src/jvmMain/kotlin/okio/-DeprecatedOkio.kt
index 7b6835e1..db94f869 100644
--- a/okio/src/jvmMain/kotlin/okio/-DeprecatedOkio.kt
+++ b/okio/src/jvmMain/kotlin/okio/-DeprecatedOkio.kt
@@ -21,7 +21,7 @@ import java.io.InputStream
import java.io.OutputStream
import java.net.Socket
import java.nio.file.OpenOption
-import java.nio.file.Path
+import java.nio.file.Path as NioPath
@Deprecated(message = "changed in Okio 2.x")
object `-DeprecatedOkio` {
@@ -29,9 +29,9 @@ object `-DeprecatedOkio` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "file.appendingSink()",
- imports = ["okio.appendingSink"]
+ imports = ["okio.appendingSink"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun appendingSink(file: File) = file.appendingSink()
@@ -39,9 +39,9 @@ object `-DeprecatedOkio` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "sink.buffer()",
- imports = ["okio.buffer"]
+ imports = ["okio.buffer"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun buffer(sink: Sink) = sink.buffer()
@@ -49,9 +49,9 @@ object `-DeprecatedOkio` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "source.buffer()",
- imports = ["okio.buffer"]
+ imports = ["okio.buffer"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun buffer(source: Source) = source.buffer()
@@ -59,9 +59,9 @@ object `-DeprecatedOkio` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "file.sink()",
- imports = ["okio.sink"]
+ imports = ["okio.sink"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun sink(file: File) = file.sink()
@@ -69,9 +69,9 @@ object `-DeprecatedOkio` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "outputStream.sink()",
- imports = ["okio.sink"]
+ imports = ["okio.sink"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun sink(outputStream: OutputStream) = outputStream.sink()
@@ -79,19 +79,19 @@ object `-DeprecatedOkio` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "path.sink(*options)",
- imports = ["okio.sink"]
+ imports = ["okio.sink"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
- fun sink(path: Path, vararg options: OpenOption) = path.sink(*options)
+ fun sink(path: NioPath, vararg options: OpenOption) = path.sink(*options)
@Deprecated(
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "socket.sink()",
- imports = ["okio.sink"]
+ imports = ["okio.sink"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun sink(socket: Socket) = socket.sink()
@@ -99,9 +99,9 @@ object `-DeprecatedOkio` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "file.source()",
- imports = ["okio.source"]
+ imports = ["okio.source"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun source(file: File) = file.source()
@@ -109,9 +109,9 @@ object `-DeprecatedOkio` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "inputStream.source()",
- imports = ["okio.source"]
+ imports = ["okio.source"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun source(inputStream: InputStream) = inputStream.source()
@@ -119,19 +119,19 @@ object `-DeprecatedOkio` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "path.source(*options)",
- imports = ["okio.source"]
+ imports = ["okio.source"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
- fun source(path: Path, vararg options: OpenOption) = path.source(*options)
+ fun source(path: NioPath, vararg options: OpenOption) = path.source(*options)
@Deprecated(
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "socket.source()",
- imports = ["okio.source"]
+ imports = ["okio.source"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun source(socket: Socket) = socket.source()
@@ -139,9 +139,9 @@ object `-DeprecatedOkio` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "blackholeSink()",
- imports = ["okio.blackholeSink"]
+ imports = ["okio.blackholeSink"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun blackhole() = blackholeSink()
}
diff --git a/okio/src/jvmMain/kotlin/okio/-DeprecatedUtf8.kt b/okio/src/jvmMain/kotlin/okio/-DeprecatedUtf8.kt
index b4bc7574..3f123c8e 100644
--- a/okio/src/jvmMain/kotlin/okio/-DeprecatedUtf8.kt
+++ b/okio/src/jvmMain/kotlin/okio/-DeprecatedUtf8.kt
@@ -22,9 +22,9 @@ object `-DeprecatedUtf8` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "string.utf8Size()",
- imports = ["okio.utf8Size"]
+ imports = ["okio.utf8Size"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun size(string: String) = string.utf8Size()
@@ -32,9 +32,9 @@ object `-DeprecatedUtf8` {
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "string.utf8Size(beginIndex, endIndex)",
- imports = ["okio.utf8Size"]
+ imports = ["okio.utf8Size"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun size(string: String, beginIndex: Int, endIndex: Int) = string.utf8Size(beginIndex, endIndex)
}
diff --git a/okio/src/jvmMain/kotlin/okio/-Platform.kt b/okio/src/jvmMain/kotlin/okio/-JvmPlatform.kt
index 4edb3ce0..cf263c45 100644
--- a/okio/src/jvmMain/kotlin/okio/-Platform.kt
+++ b/okio/src/jvmMain/kotlin/okio/-JvmPlatform.kt
@@ -1,3 +1,4 @@
+// ktlint-disable filename
/*
* Copyright (C) 2018 Square, Inc.
*
@@ -13,10 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-@file:JvmName("-Platform")
package okio
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock as jvmWithLock
+
internal actual fun ByteArray.toUtf8String(): String = String(this, Charsets.UTF_8)
internal actual fun String.asUtf8ToByteArray(): ByteArray = toByteArray(Charsets.UTF_8)
@@ -24,12 +26,16 @@ internal actual fun String.asUtf8ToByteArray(): ByteArray = toByteArray(Charsets
// TODO remove if https://youtrack.jetbrains.com/issue/KT-20641 provides a better solution
actual typealias ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException
-internal actual inline fun <R> synchronized(lock: Any, block: () -> R): R {
- return kotlin.synchronized(lock, block)
-}
+actual typealias Lock = ReentrantLock
+
+internal actual fun newLock(): Lock = ReentrantLock()
+
+actual inline fun <T> Lock.withLock(action: () -> T): T = jvmWithLock(action)
actual typealias IOException = java.io.IOException
+actual typealias ProtocolException = java.net.ProtocolException
+
actual typealias EOFException = java.io.EOFException
actual typealias FileNotFoundException = java.io.FileNotFoundException
diff --git a/okio/src/jvmMain/kotlin/okio/AsyncTimeout.kt b/okio/src/jvmMain/kotlin/okio/AsyncTimeout.kt
index 20778327..a9af3ba8 100644
--- a/okio/src/jvmMain/kotlin/okio/AsyncTimeout.kt
+++ b/okio/src/jvmMain/kotlin/okio/AsyncTimeout.kt
@@ -18,6 +18,9 @@ package okio
import java.io.IOException
import java.io.InterruptedIOException
import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
/**
* This timeout uses a background thread to take action exactly when the timeout occurs. Use this to
@@ -25,19 +28,18 @@ import java.util.concurrent.TimeUnit
* writing.
*
* Subclasses should override [timedOut] to take action when a timeout occurs. This method will be
- * invoked by the shared watchdog thread so it should not do any long-running operations. Otherwise
+ * invoked by the shared watchdog thread so it should not do any long-running operations. Otherwise,
* we risk starving other timeouts from being triggered.
*
* Use [sink] and [source] to apply this timeout to a stream. The returned value will apply the
* timeout to each operation on the wrapped stream.
*
- * Callers should call [enter] before doing work that is subject to timeouts, and [exit] afterwards.
+ * Callers should call [enter] before doing work that is subject to timeouts, and [exit] afterward.
* The return value of [exit] indicates whether a timeout was triggered. Note that the call to
* [timedOut] is asynchronous, and may be called after [exit].
*/
open class AsyncTimeout : Timeout() {
- /** True if this node is currently in the queue. */
- private var inQueue = false
+ private var state = STATE_IDLE
/** The next node in the linked list. */
private var next: AsyncTimeout? = null
@@ -51,12 +53,38 @@ open class AsyncTimeout : Timeout() {
if (timeoutNanos == 0L && !hasDeadline) {
return // No timeout and no deadline? Don't bother with the queue.
}
- scheduleTimeout(this, timeoutNanos, hasDeadline)
+
+ lock.withLock {
+ check(state == STATE_IDLE) { "Unbalanced enter/exit" }
+ state = STATE_IN_QUEUE
+ insertIntoQueue(this, timeoutNanos, hasDeadline)
+ }
}
/** Returns true if the timeout occurred. */
fun exit(): Boolean {
- return cancelScheduledTimeout(this)
+ lock.withLock {
+ val oldState = this.state
+ state = STATE_IDLE
+
+ if (oldState == STATE_IN_QUEUE) {
+ removeFromQueue(this)
+ return false
+ } else {
+ return oldState == STATE_TIMED_OUT
+ }
+ }
+ }
+
+ override fun cancel() {
+ super.cancel()
+
+ lock.withLock {
+ if (state == STATE_IN_QUEUE) {
+ removeFromQueue(this)
+ state = STATE_CANCELED
+ }
+ }
}
/**
@@ -170,7 +198,7 @@ open class AsyncTimeout : Timeout() {
return e
}
- private class Watchdog internal constructor() : Thread("Okio Watchdog") {
+ private class Watchdog : Thread("Okio Watchdog") {
init {
isDaemon = true
}
@@ -178,8 +206,8 @@ open class AsyncTimeout : Timeout() {
override fun run() {
while (true) {
try {
- var timedOut: AsyncTimeout? = null
- synchronized(AsyncTimeout::class.java) {
+ var timedOut: AsyncTimeout?
+ lock.withLock {
timedOut = awaitTimeout()
// The queue is completely empty. Let this thread exit and let another watchdog thread
@@ -198,7 +226,10 @@ open class AsyncTimeout : Timeout() {
}
}
- companion object {
+ private companion object {
+ val lock: ReentrantLock = ReentrantLock()
+ val condition: Condition = lock.newCondition()
+
/**
* Don't write more than 64 KiB of data at a time, give or take a segment. Otherwise slow
* connections may suffer timeouts even when they're making (slow) progress. Without this,
@@ -210,6 +241,43 @@ open class AsyncTimeout : Timeout() {
private val IDLE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60)
private val IDLE_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(IDLE_TIMEOUT_MILLIS)
+ /*
+ * .-------------.
+ * | |
+ * .------------ exit() ------| CANCELED |
+ * | | |
+ * | '-------------'
+ * | ^
+ * | | cancel()
+ * v |
+ * .-------------. .-------------.
+ * | |---- enter() ----->| |
+ * | IDLE | | IN QUEUE |
+ * | |<---- exit() ------| |
+ * '-------------' '-------------'
+ * ^ |
+ * | | time out
+ * | v
+ * | .-------------.
+ * | | |
+ * '------------ exit() ------| TIMED OUT |
+ * | |
+ * '-------------'
+ *
+ * Notes:
+ * * enter() crashes if called from a state other than IDLE.
+ * * If there's no timeout (ie. wait forever), then enter() is a no-op. There's no state to
+ * track entered but not in the queue.
+ * * exit() is a no-op from IDLE. This is probably too lenient, but it made it simpler for
+ * early implementations to support cases where enter() as a no-op.
+ * * cancel() is a no-op from every state but IN QUEUE.
+ */
+
+ private const val STATE_IDLE = 0
+ private const val STATE_IN_QUEUE = 1
+ private const val STATE_TIMED_OUT = 2
+ private const val STATE_CANCELED = 3
+
/**
* The watchdog thread processes a linked list of pending timeouts, sorted in the order to be
* triggered. This class synchronizes on AsyncTimeout.class. This lock guards the queue.
@@ -220,86 +288,74 @@ open class AsyncTimeout : Timeout() {
*/
private var head: AsyncTimeout? = null
- private fun scheduleTimeout(node: AsyncTimeout, timeoutNanos: Long, hasDeadline: Boolean) {
- synchronized(AsyncTimeout::class.java) {
- check(!node.inQueue) { "Unbalanced enter/exit" }
- node.inQueue = true
-
- // Start the watchdog thread and create the head node when the first timeout is scheduled.
- if (head == null) {
- head = AsyncTimeout()
- Watchdog().start()
- }
+ private fun insertIntoQueue(node: AsyncTimeout, timeoutNanos: Long, hasDeadline: Boolean) {
+ // Start the watchdog thread and create the head node when the first timeout is scheduled.
+ if (head == null) {
+ head = AsyncTimeout()
+ Watchdog().start()
+ }
- val now = System.nanoTime()
- if (timeoutNanos != 0L && hasDeadline) {
- // Compute the earliest event; either timeout or deadline. Because nanoTime can wrap
- // around, minOf() is undefined for absolute values, but meaningful for relative ones.
- node.timeoutAt = now + minOf(timeoutNanos, node.deadlineNanoTime() - now)
- } else if (timeoutNanos != 0L) {
- node.timeoutAt = now + timeoutNanos
- } else if (hasDeadline) {
- node.timeoutAt = node.deadlineNanoTime()
- } else {
- throw AssertionError()
- }
+ val now = System.nanoTime()
+ if (timeoutNanos != 0L && hasDeadline) {
+ // Compute the earliest event; either timeout or deadline. Because nanoTime can wrap
+ // around, minOf() is undefined for absolute values, but meaningful for relative ones.
+ node.timeoutAt = now + minOf(timeoutNanos, node.deadlineNanoTime() - now)
+ } else if (timeoutNanos != 0L) {
+ node.timeoutAt = now + timeoutNanos
+ } else if (hasDeadline) {
+ node.timeoutAt = node.deadlineNanoTime()
+ } else {
+ throw AssertionError()
+ }
- // Insert the node in sorted order.
- val remainingNanos = node.remainingNanos(now)
- var prev = head!!
- while (true) {
- if (prev.next == null || remainingNanos < prev.next!!.remainingNanos(now)) {
- node.next = prev.next
- prev.next = node
- if (prev === head) {
- // Wake up the watchdog when inserting at the front.
- (AsyncTimeout::class.java as Object).notify()
- }
- break
+ // Insert the node in sorted order.
+ val remainingNanos = node.remainingNanos(now)
+ var prev = head!!
+ while (true) {
+ if (prev.next == null || remainingNanos < prev.next!!.remainingNanos(now)) {
+ node.next = prev.next
+ prev.next = node
+ if (prev === head) {
+ // Wake up the watchdog when inserting at the front.
+ condition.signal()
}
- prev = prev.next!!
+ break
}
+ prev = prev.next!!
}
}
/** Returns true if the timeout occurred. */
- private fun cancelScheduledTimeout(node: AsyncTimeout): Boolean {
- synchronized(AsyncTimeout::class.java) {
- if (!node.inQueue) return false
- node.inQueue = false
-
- // Remove the node from the linked list.
- var prev = head
- while (prev != null) {
- if (prev.next === node) {
- prev.next = node.next
- node.next = null
- return false
- }
- prev = prev.next
+ private fun removeFromQueue(node: AsyncTimeout) {
+ var prev = head
+ while (prev != null) {
+ if (prev.next === node) {
+ prev.next = node.next
+ node.next = null
+ return
}
-
- // The node wasn't found in the linked list: it must have timed out!
- return true
+ prev = prev.next
}
+
+ error("node was not found in the queue")
}
/**
* Removes and returns the node at the head of the list, waiting for it to time out if
* necessary. This returns [head] if there was no node at the head of the list when starting,
* and there continues to be no node after waiting [IDLE_TIMEOUT_NANOS]. It returns null if a
- * new node was inserted while waiting. Otherwise this returns the node being waited on that has
- * been removed.
+ * new node was inserted while waiting. Otherwise, this returns the node being waited on that
+ * has been removed.
*/
@Throws(InterruptedException::class)
- internal fun awaitTimeout(): AsyncTimeout? {
+ fun awaitTimeout(): AsyncTimeout? {
// Get the next eligible node.
val node = head!!.next
// The queue is empty. Wait until either something is enqueued or the idle timeout elapses.
if (node == null) {
val startNanos = System.nanoTime()
- (AsyncTimeout::class.java as Object).wait(IDLE_TIMEOUT_MILLIS)
+ condition.await(IDLE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
return if (head!!.next == null && System.nanoTime() - startNanos >= IDLE_TIMEOUT_NANOS) {
head // The idle timeout elapsed.
} else {
@@ -307,21 +363,18 @@ open class AsyncTimeout : Timeout() {
}
}
- var waitNanos = node.remainingNanos(System.nanoTime())
+ val waitNanos = node.remainingNanos(System.nanoTime())
// The head of the queue hasn't timed out yet. Await that.
if (waitNanos > 0) {
- // Waiting is made complicated by the fact that we work in nanoseconds,
- // but the API wants (millis, nanos) in two arguments.
- val waitMillis = waitNanos / 1000000L
- waitNanos -= waitMillis * 1000000L
- (AsyncTimeout::class.java as Object).wait(waitMillis, waitNanos.toInt())
+ condition.await(waitNanos, TimeUnit.NANOSECONDS)
return null
}
// The head of the queue has timed out. Remove it.
head!!.next = node.next
node.next = null
+ node.state = STATE_TIMED_OUT
return node
}
}
diff --git a/okio/src/jvmMain/kotlin/okio/Buffer.kt b/okio/src/jvmMain/kotlin/okio/Buffer.kt
index 857c983e..88dfaba7 100644
--- a/okio/src/jvmMain/kotlin/okio/Buffer.kt
+++ b/okio/src/jvmMain/kotlin/okio/Buffer.kt
@@ -15,6 +15,18 @@
*/
package okio
+import java.io.Closeable
+import java.io.EOFException
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import java.nio.ByteBuffer
+import java.nio.channels.ByteChannel
+import java.nio.charset.Charset
+import java.security.InvalidKeyException
+import java.security.MessageDigest
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
import okio.internal.commonClear
import okio.internal.commonClose
import okio.internal.commonCompleteSegmentByteCount
@@ -60,18 +72,6 @@ import okio.internal.commonWriteLong
import okio.internal.commonWriteShort
import okio.internal.commonWriteUtf8
import okio.internal.commonWriteUtf8CodePoint
-import java.io.Closeable
-import java.io.EOFException
-import java.io.IOException
-import java.io.InputStream
-import java.io.OutputStream
-import java.nio.ByteBuffer
-import java.nio.channels.ByteChannel
-import java.nio.charset.Charset
-import java.security.InvalidKeyException
-import java.security.MessageDigest
-import javax.crypto.Mac
-import javax.crypto.spec.SecretKeySpec
actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
@JvmField internal actual var head: Segment? = null
@@ -147,7 +147,7 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
fun copyTo(
out: OutputStream,
offset: Long = 0L,
- byteCount: Long = size - offset
+ byteCount: Long = size - offset,
): Buffer {
var offset = offset
var byteCount = byteCount
@@ -177,15 +177,15 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
actual fun copyTo(
out: Buffer,
offset: Long,
- byteCount: Long
+ byteCount: Long,
): Buffer = commonCopyTo(out, offset, byteCount)
actual fun copyTo(
out: Buffer,
- offset: Long
+ offset: Long,
): Buffer = copyTo(out, offset, size - offset)
- /** Write `byteCount` bytes from this to `out`. */
+ /** Write `byteCount` bytes from this to `out`. */
@Throws(IOException::class)
@JvmOverloads
fun writeTo(out: OutputStream, byteCount: Long = size): Buffer {
@@ -212,14 +212,14 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
return this
}
- /** Read and exhaust bytes from `input` into this. */
+ /** Read and exhaust bytes from `input` into this. */
@Throws(IOException::class)
fun readFrom(input: InputStream): Buffer {
readFrom(input, Long.MAX_VALUE, true)
return this
}
- /** Read `byteCount` bytes from `input` into this. */
+ /** Read `byteCount` bytes from `input` into this. */
@Throws(IOException::class)
fun readFrom(input: InputStream, byteCount: Long): Buffer {
require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
@@ -387,15 +387,17 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
commonWriteUtf8CodePoint(codePoint)
override fun writeString(string: String, charset: Charset) = writeString(
- string, 0, string.length,
- charset
+ string,
+ 0,
+ string.length,
+ charset,
)
override fun writeString(
string: String,
beginIndex: Int,
endIndex: Int,
- charset: Charset
+ charset: Charset,
): Buffer {
require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" }
require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" }
@@ -410,7 +412,7 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
actual override fun write(
source: ByteArray,
offset: Int,
- byteCount: Int
+ byteCount: Int,
): Buffer = commonWrite(source, offset, byteCount)
@Throws(IOException::class)
@@ -492,7 +494,7 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
offset: Long,
bytes: ByteString,
bytesOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, bytes, bytesOffset, byteCount)
override fun flush() {}
@@ -503,16 +505,24 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
override fun timeout() = Timeout.NONE
- /** Returns the 128-bit MD5 hash of this buffer. */
+ /**
+ * Returns the 128-bit MD5 hash of this buffer.
+ *
+ * MD5 has been vulnerable to collisions since 2004. It should not be used in new code.
+ */
actual fun md5() = digest("MD5")
- /** Returns the 160-bit SHA-1 hash of this buffer. */
+ /**
+ * Returns the 160-bit SHA-1 hash of this buffer.
+ *
+ * SHA-1 has been vulnerable to collisions since 2017. It should not be used in new code.
+ */
actual fun sha1() = digest("SHA-1")
- /** Returns the 256-bit SHA-256 hash of this buffer. */
+ /** Returns the 256-bit SHA-256 hash of this buffer. */
actual fun sha256() = digest("SHA-256")
- /** Returns the 512-bit SHA-512 hash of this buffer. */
+ /** Returns the 512-bit SHA-512 hash of this buffer. */
actual fun sha512() = digest("SHA-512")
private fun digest(algorithm: String): ByteString {
@@ -528,13 +538,13 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
return ByteString(messageDigest.digest())
}
- /** Returns the 160-bit SHA-1 HMAC of this buffer. */
+ /** Returns the 160-bit SHA-1 HMAC of this buffer. */
actual fun hmacSha1(key: ByteString) = hmac("HmacSHA1", key)
- /** Returns the 256-bit SHA-256 HMAC of this buffer. */
+ /** Returns the 256-bit SHA-256 HMAC of this buffer. */
actual fun hmacSha256(key: ByteString) = hmac("HmacSHA256", key)
- /** Returns the 512-bit SHA-512 HMAC of this buffer. */
+ /** Returns the 512-bit SHA-512 HMAC of this buffer. */
actual fun hmacSha512(key: ByteString) = hmac("HmacSHA512", key)
private fun hmac(algorithm: String, key: ByteString): ByteString {
@@ -567,7 +577,10 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
actual fun copy(): Buffer = commonCopy()
- /** Returns a deep copy of this buffer. */
+ /**
+ * Returns a deep copy of this buffer. This is the same as [copy] but allows [Buffer] to implement
+ * the [Cloneable] interface on the JVM.
+ */
public override fun clone(): Buffer = copy()
actual fun snapshot(): ByteString = commonSnapshot()
@@ -585,7 +598,7 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
@Deprecated(
message = "moved to operator function",
replaceWith = ReplaceWith(expression = "this[index]"),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun getByte(index: Long) = this[index]
@@ -593,18 +606,23 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "size"),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun size() = size
actual class UnsafeCursor : Closeable {
@JvmField actual var buffer: Buffer? = null
+
@JvmField actual var readWrite: Boolean = false
internal actual var segment: Segment? = null
+
@JvmField actual var offset = -1L
+
@JvmField actual var data: ByteArray? = null
+
@JvmField actual var start = -1
+
@JvmField actual var end = -1
actual fun next(): Int = commonNext()
diff --git a/okio/src/jvmMain/kotlin/okio/BufferedSink.kt b/okio/src/jvmMain/kotlin/okio/BufferedSink.kt
index 4aa1bb06..fd124a4e 100644
--- a/okio/src/jvmMain/kotlin/okio/BufferedSink.kt
+++ b/okio/src/jvmMain/kotlin/okio/BufferedSink.kt
@@ -20,12 +20,12 @@ import java.io.OutputStream
import java.nio.channels.WritableByteChannel
import java.nio.charset.Charset
-actual interface BufferedSink : Sink, WritableByteChannel {
+actual sealed interface BufferedSink : Sink, WritableByteChannel {
/** Returns this sink's internal buffer. */
@Deprecated(
message = "moved to val: use getBuffer() instead",
replaceWith = ReplaceWith(expression = "buffer"),
- level = DeprecationLevel.WARNING
+ level = DeprecationLevel.WARNING,
)
fun buffer(): Buffer
diff --git a/okio/src/jvmMain/kotlin/okio/BufferedSource.kt b/okio/src/jvmMain/kotlin/okio/BufferedSource.kt
index b312d569..ca6b94bf 100644
--- a/okio/src/jvmMain/kotlin/okio/BufferedSource.kt
+++ b/okio/src/jvmMain/kotlin/okio/BufferedSource.kt
@@ -20,12 +20,12 @@ import java.io.InputStream
import java.nio.channels.ReadableByteChannel
import java.nio.charset.Charset
-actual interface BufferedSource : Source, ReadableByteChannel {
+actual sealed interface BufferedSource : Source, ReadableByteChannel {
/** Returns this source's internal buffer. */
@Deprecated(
message = "moved to val: use getBuffer() instead",
replaceWith = ReplaceWith(expression = "buffer"),
- level = DeprecationLevel.WARNING
+ level = DeprecationLevel.WARNING,
)
fun buffer(): Buffer
diff --git a/okio/src/jvmMain/kotlin/okio/ByteString.kt b/okio/src/jvmMain/kotlin/okio/ByteString.kt
index 0edad9cb..37922adf 100644
--- a/okio/src/jvmMain/kotlin/okio/ByteString.kt
+++ b/okio/src/jvmMain/kotlin/okio/ByteString.kt
@@ -15,9 +15,23 @@
*/
package okio
+import java.io.EOFException
+import java.io.IOException
+import java.io.InputStream
+import java.io.ObjectInputStream
+import java.io.ObjectOutputStream
+import java.io.OutputStream
+import java.io.Serializable
+import java.nio.ByteBuffer
+import java.nio.charset.Charset
+import java.security.InvalidKeyException
+import java.security.MessageDigest
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
import okio.internal.commonBase64
import okio.internal.commonBase64Url
import okio.internal.commonCompareTo
+import okio.internal.commonCopyInto
import okio.internal.commonDecodeBase64
import okio.internal.commonDecodeHex
import okio.internal.commonEncodeUtf8
@@ -41,23 +55,10 @@ import okio.internal.commonToByteString
import okio.internal.commonToString
import okio.internal.commonUtf8
import okio.internal.commonWrite
-import java.io.EOFException
-import java.io.IOException
-import java.io.InputStream
-import java.io.ObjectInputStream
-import java.io.ObjectOutputStream
-import java.io.OutputStream
-import java.io.Serializable
-import java.nio.ByteBuffer
-import java.nio.charset.Charset
-import java.security.InvalidKeyException
-import java.security.MessageDigest
-import javax.crypto.Mac
-import javax.crypto.spec.SecretKeySpec
actual open class ByteString
internal actual constructor(
- internal actual val data: ByteArray
+ internal actual val data: ByteArray,
) : Serializable, Comparable<ByteString> {
@Transient internal actual var hashCode: Int = 0 // Lazily computed; 0 if unknown.
@Transient internal actual var utf8: String? = null // Lazily computed.
@@ -122,7 +123,8 @@ internal actual constructor(
actual operator fun get(index: Int): Byte = internalGet(index)
actual val size
- @JvmName("size") get() = getSize()
+ @JvmName("size")
+ get() = getSize()
internal actual open fun getSize() = commonGetSize()
@@ -146,16 +148,23 @@ internal actual constructor(
offset: Int,
other: ByteString,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
actual open fun rangeEquals(
offset: Int,
other: ByteArray,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
+ actual open fun copyInto(
+ offset: Int,
+ target: ByteArray,
+ targetOffset: Int,
+ byteCount: Int,
+ ) = commonCopyInto(offset, target, targetOffset, byteCount)
+
actual fun startsWith(prefix: ByteString) = commonStartsWith(prefix)
actual fun startsWith(prefix: ByteArray) = commonStartsWith(prefix)
@@ -203,7 +212,7 @@ internal actual constructor(
@Deprecated(
message = "moved to operator function",
replaceWith = ReplaceWith(expression = "this[index]"),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun getByte(index: Int) = this[index]
@@ -211,7 +220,7 @@ internal actual constructor(
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "size"),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun size() = size
@@ -279,9 +288,9 @@ internal actual constructor(
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "string.decodeBase64()",
- imports = ["okio.ByteString.Companion.decodeBase64"]
+ imports = ["okio.ByteString.Companion.decodeBase64"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun decodeBase64(string: String) = string.decodeBase64()
@@ -290,9 +299,9 @@ internal actual constructor(
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "string.decodeHex()",
- imports = ["okio.ByteString.Companion.decodeHex"]
+ imports = ["okio.ByteString.Companion.decodeHex"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun decodeHex(string: String) = string.decodeHex()
@@ -301,9 +310,9 @@ internal actual constructor(
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "string.encode(charset)",
- imports = ["okio.ByteString.Companion.encode"]
+ imports = ["okio.ByteString.Companion.encode"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun encodeString(string: String, charset: Charset) = string.encode(charset)
@@ -312,9 +321,9 @@ internal actual constructor(
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "string.encodeUtf8()",
- imports = ["okio.ByteString.Companion.encodeUtf8"]
+ imports = ["okio.ByteString.Companion.encodeUtf8"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun encodeUtf8(string: String) = string.encodeUtf8()
@@ -323,9 +332,9 @@ internal actual constructor(
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "buffer.toByteString()",
- imports = ["okio.ByteString.Companion.toByteString"]
+ imports = ["okio.ByteString.Companion.toByteString"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun of(buffer: ByteBuffer) = buffer.toByteString()
@@ -334,9 +343,9 @@ internal actual constructor(
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "array.toByteString(offset, byteCount)",
- imports = ["okio.ByteString.Companion.toByteString"]
+ imports = ["okio.ByteString.Companion.toByteString"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun of(array: ByteArray, offset: Int, byteCount: Int) = array.toByteString(offset, byteCount)
@@ -345,9 +354,9 @@ internal actual constructor(
message = "moved to extension function",
replaceWith = ReplaceWith(
expression = "inputstream.readByteString(byteCount)",
- imports = ["okio.ByteString.Companion.readByteString"]
+ imports = ["okio.ByteString.Companion.readByteString"],
),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun read(inputstream: InputStream, byteCount: Int) = inputstream.readByteString(byteCount)
}
diff --git a/okio/src/jvmMain/kotlin/okio/CipherSink.kt b/okio/src/jvmMain/kotlin/okio/CipherSink.kt
index aa482842..9b5c7a92 100644
--- a/okio/src/jvmMain/kotlin/okio/CipherSink.kt
+++ b/okio/src/jvmMain/kotlin/okio/CipherSink.kt
@@ -20,7 +20,7 @@ import javax.crypto.Cipher
class CipherSink(
private val sink: BufferedSink,
- val cipher: Cipher
+ val cipher: Cipher,
) : Sink {
private val blockSize = cipher.blockSize
private var closed = false
@@ -50,7 +50,13 @@ class CipherSink(
// Shorten input until output is guaranteed to fit within a segment
var outputSize = cipher.getOutputSize(size)
while (outputSize > Segment.SIZE) {
- check(size > blockSize) { "Unexpected output size $outputSize for input size $size" }
+ if (size <= blockSize) {
+ // Bug: For AES-GCM on Android `update` method never outputs any data
+ // As a consequence, `getOutputSize` just keeps increasing indefinitely after each update
+ // When that happens, the fallback is to perform the update operation without using a pre-allocated segment
+ sink.write(cipher.update(source.readByteArray(remaining)))
+ return remaining.toInt()
+ }
size -= blockSize
outputSize = cipher.getOutputSize(size)
}
@@ -104,6 +110,18 @@ class CipherSink(
val outputSize = cipher.getOutputSize(0)
if (outputSize == 0) return null
+ if (outputSize > Segment.SIZE) {
+ // Bug: For AES-GCM on Android `update` method never outputs any data
+ // As a consequence, `doFinal` returns the fully encrypted data, which may be arbitrarily large
+ // When that happens, the fallback is to perform the `doFinal` operation without using a pre-allocated segment
+ try {
+ sink.write(cipher.doFinal())
+ } catch (t: Throwable) {
+ return t
+ }
+ return null
+ }
+
var thrown: Throwable? = null
val buffer = sink.buffer
diff --git a/okio/src/jvmMain/kotlin/okio/CipherSource.kt b/okio/src/jvmMain/kotlin/okio/CipherSource.kt
index 154371f3..aeb49e66 100644
--- a/okio/src/jvmMain/kotlin/okio/CipherSource.kt
+++ b/okio/src/jvmMain/kotlin/okio/CipherSource.kt
@@ -20,7 +20,7 @@ import javax.crypto.Cipher
class CipherSource(
private val source: BufferedSource,
- val cipher: Cipher
+ val cipher: Cipher,
) : Source {
private val blockSize = cipher.blockSize
private val buffer = Buffer()
@@ -37,7 +37,6 @@ class CipherSource(
require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
check(!closed) { "closed" }
if (byteCount == 0L) return 0L
- if (final) return buffer.read(sink, byteCount)
refill()
@@ -45,7 +44,7 @@ class CipherSource(
}
private fun refill() {
- while (buffer.size == 0L) {
+ while (buffer.size == 0L && !final) {
if (source.exhausted()) {
final = true
doFinal()
@@ -63,7 +62,14 @@ class CipherSource(
// Shorten input until output is guaranteed to fit within a segment
var outputSize = cipher.getOutputSize(size)
while (outputSize > Segment.SIZE) {
- check(size > blockSize) { "Unexpected output size $outputSize for input size $size" }
+ if (size <= blockSize) {
+ // Bug: For AES-GCM on Android `update` method never outputs any data
+ // As a consequence, `getOutputSize` just keeps increasing indefinitely after each update
+ // When that happens, the fallback is to break the streaming implementation and just cipher the rest of the data all at once
+ final = true
+ buffer.write(cipher.doFinal(source.readByteArray()))
+ return
+ }
size -= blockSize
outputSize = cipher.getOutputSize(size)
}
diff --git a/okio/src/jvmMain/kotlin/okio/DeflaterSink.kt b/okio/src/jvmMain/kotlin/okio/DeflaterSink.kt
index e71cdff8..6fcf7d83 100644
--- a/okio/src/jvmMain/kotlin/okio/DeflaterSink.kt
+++ b/okio/src/jvmMain/kotlin/okio/DeflaterSink.kt
@@ -19,8 +19,6 @@
package okio
-import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-import java.io.IOException
import java.util.zip.Deflater
/**
@@ -75,7 +73,6 @@ internal constructor(private val sink: BufferedSink, private val deflater: Defla
}
}
- @IgnoreJRERequirement
private fun deflate(syncFlush: Boolean) {
val buffer = sink.buffer
while (true) {
@@ -85,10 +82,14 @@ internal constructor(private val sink: BufferedSink, private val deflater: Defla
// Java 1.7, and is public (although with @hide) on Android since 2.3.
// The @hide tag means that this code won't compile against the Android
// 2.3 SDK, but it will run fine there.
- val deflated = if (syncFlush) {
- deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH)
- } else {
- deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit)
+ val deflated = try {
+ if (syncFlush) {
+ deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH)
+ } else {
+ deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit)
+ }
+ } catch (npe: NullPointerException) {
+ throw IOException("Deflater already closed", npe)
}
if (deflated > 0) {
diff --git a/okio/src/jvmMain/kotlin/okio/-DeprecatedUpgrade.kt b/okio/src/jvmMain/kotlin/okio/DeprecatedUpgrade.kt
index 5f955470..199b22fa 100644
--- a/okio/src/jvmMain/kotlin/okio/-DeprecatedUpgrade.kt
+++ b/okio/src/jvmMain/kotlin/okio/DeprecatedUpgrade.kt
@@ -14,6 +14,7 @@
* limitations under the License.
*/
@file:JvmName("-DeprecatedUpgrade")
+
package okio
val Okio = `-DeprecatedOkio`
diff --git a/okio/src/jvmMain/kotlin/okio/FileSystem.kt b/okio/src/jvmMain/kotlin/okio/FileSystem.kt
new file mode 100644
index 00000000..7a552cc5
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/FileSystem.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.nio.file.FileSystem as JavaNioFileSystem
+import okio.Path.Companion.toPath
+import okio.internal.ResourceFileSystem
+import okio.internal.commonCopy
+import okio.internal.commonCreateDirectories
+import okio.internal.commonDeleteRecursively
+import okio.internal.commonExists
+import okio.internal.commonListRecursively
+import okio.internal.commonMetadata
+
+actual abstract class FileSystem {
+ @Throws(IOException::class)
+ actual abstract fun canonicalize(path: Path): Path
+
+ @Throws(IOException::class)
+ actual fun metadata(path: Path): FileMetadata = commonMetadata(path)
+
+ @Throws(IOException::class)
+ actual abstract fun metadataOrNull(path: Path): FileMetadata?
+
+ @Throws(IOException::class)
+ actual fun exists(path: Path): Boolean = commonExists(path)
+
+ @Throws(IOException::class)
+ actual abstract fun list(dir: Path): List<Path>
+
+ actual abstract fun listOrNull(dir: Path): List<Path>?
+
+ actual open fun listRecursively(dir: Path, followSymlinks: Boolean): Sequence<Path> =
+ commonListRecursively(dir, followSymlinks)
+
+ fun listRecursively(dir: Path): Sequence<Path> = listRecursively(dir, followSymlinks = false)
+
+ @Throws(IOException::class)
+ actual abstract fun openReadOnly(file: Path): FileHandle
+
+ @Throws(IOException::class)
+ actual abstract fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle
+
+ @Throws(IOException::class)
+ fun openReadWrite(file: Path): FileHandle =
+ openReadWrite(file, mustCreate = false, mustExist = false)
+
+ @Throws(IOException::class)
+ actual abstract fun source(file: Path): Source
+
+ @Throws(IOException::class)
+ @JvmName("-read")
+ actual inline fun <T> read(file: Path, readerAction: BufferedSource.() -> T): T {
+ return source(file).buffer().use {
+ it.readerAction()
+ }
+ }
+
+ @Throws(IOException::class)
+ actual abstract fun sink(file: Path, mustCreate: Boolean): Sink
+
+ @Throws(IOException::class)
+ fun sink(file: Path): Sink = sink(file, mustCreate = false)
+
+ @Throws(IOException::class)
+ @JvmName("-write")
+ actual inline fun <T> write(file: Path, mustCreate: Boolean, writerAction: BufferedSink.() -> T): T {
+ return sink(file, mustCreate = mustCreate).buffer().use {
+ it.writerAction()
+ }
+ }
+
+ @Throws(IOException::class)
+ actual abstract fun appendingSink(file: Path, mustExist: Boolean): Sink
+
+ @Throws(IOException::class)
+ fun appendingSink(file: Path): Sink = appendingSink(file, mustExist = false)
+
+ @Throws(IOException::class)
+ actual abstract fun createDirectory(dir: Path, mustCreate: Boolean)
+
+ @Throws(IOException::class)
+ fun createDirectory(dir: Path) = createDirectory(dir, mustCreate = false)
+
+ @Throws(IOException::class)
+ actual fun createDirectories(dir: Path, mustCreate: Boolean): Unit =
+ commonCreateDirectories(dir, mustCreate)
+
+ @Throws(IOException::class)
+ fun createDirectories(dir: Path): Unit = createDirectories(dir, mustCreate = false)
+
+ @Throws(IOException::class)
+ actual abstract fun atomicMove(source: Path, target: Path)
+
+ @Throws(IOException::class)
+ actual open fun copy(source: Path, target: Path): Unit = commonCopy(source, target)
+
+ @Throws(IOException::class)
+ actual abstract fun delete(path: Path, mustExist: Boolean)
+
+ @Throws(IOException::class)
+ fun delete(path: Path) = delete(path, mustExist = false)
+
+ @Throws(IOException::class)
+ actual open fun deleteRecursively(fileOrDirectory: Path, mustExist: Boolean): Unit =
+ commonDeleteRecursively(fileOrDirectory, mustExist)
+
+ @Throws(IOException::class)
+ fun deleteRecursively(fileOrDirectory: Path): Unit =
+ deleteRecursively(fileOrDirectory, mustExist = false)
+
+ @Throws(IOException::class)
+ actual abstract fun createSymlink(source: Path, target: Path)
+
+ actual companion object {
+ /**
+ * The current process's host file system. Use this instance directly, or dependency inject a
+ * [FileSystem] to make code testable.
+ */
+ @JvmField
+ val SYSTEM: FileSystem = run {
+ try {
+ Class.forName("java.nio.file.Files")
+ return@run NioSystemFileSystem()
+ } catch (e: ClassNotFoundException) {
+ return@run JvmSystemFileSystem()
+ }
+ }
+
+ @JvmField
+ actual val SYSTEM_TEMPORARY_DIRECTORY: Path = System.getProperty("java.io.tmpdir").toPath()
+
+ /**
+ * A read-only file system holding the classpath resources of the current process. If a resource
+ * is available with [ClassLoader.getResource], it is also available via this file system.
+ *
+ * In applications that compose multiple class loaders, this holds only the resources of
+ * whichever class loader includes Okio classes. Use [ClassLoader.asResourceFileSystem] for the
+ * resources of a specific class loader.
+ */
+ @JvmField
+ val RESOURCES: FileSystem = ResourceFileSystem(
+ classLoader = ResourceFileSystem::class.java.classLoader,
+ indexEagerly = false,
+ )
+
+ @JvmName("get")
+ @JvmStatic
+ fun JavaNioFileSystem.asOkioFileSystem(): FileSystem = NioFileSystemWrappingFileSystem(this)
+ }
+}
diff --git a/okio/src/jvmMain/kotlin/okio/ForwardingSink.kt b/okio/src/jvmMain/kotlin/okio/ForwardingSink.kt
index 8f0eb2f1..060aa4ce 100644
--- a/okio/src/jvmMain/kotlin/okio/ForwardingSink.kt
+++ b/okio/src/jvmMain/kotlin/okio/ForwardingSink.kt
@@ -21,7 +21,7 @@ import java.io.IOException
abstract class ForwardingSink(
/** [Sink] to which this instance is delegating. */
@get:JvmName("delegate")
- val delegate: Sink
+ val delegate: Sink,
) : Sink {
// TODO 'Sink by delegate' once https://youtrack.jetbrains.com/issue/KT-23935 is fixed.
@@ -42,7 +42,7 @@ abstract class ForwardingSink(
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "delegate"),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun delegate() = delegate
}
diff --git a/okio/src/jvmMain/kotlin/okio/ForwardingSource.kt b/okio/src/jvmMain/kotlin/okio/ForwardingSource.kt
index 30a47f6c..5be29d32 100644
--- a/okio/src/jvmMain/kotlin/okio/ForwardingSource.kt
+++ b/okio/src/jvmMain/kotlin/okio/ForwardingSource.kt
@@ -17,29 +17,27 @@ package okio
import java.io.IOException
-/** A [Source] which forwards calls to another. Useful for subclassing. */
-abstract class ForwardingSource(
- /** [Source] to which this instance is delegating. */
+actual abstract class ForwardingSource actual constructor(
@get:JvmName("delegate")
- val delegate: Source
+ actual val delegate: Source,
) : Source {
// TODO 'Source by delegate' once https://youtrack.jetbrains.com/issue/KT-23935 is fixed.
@Throws(IOException::class)
- override fun read(sink: Buffer, byteCount: Long): Long = delegate.read(sink, byteCount)
+ actual override fun read(sink: Buffer, byteCount: Long): Long = delegate.read(sink, byteCount)
- override fun timeout() = delegate.timeout()
+ actual override fun timeout() = delegate.timeout()
@Throws(IOException::class)
- override fun close() = delegate.close()
+ actual override fun close() = delegate.close()
- override fun toString() = "${javaClass.simpleName}($delegate)"
+ actual override fun toString() = "${javaClass.simpleName}($delegate)"
@JvmName("-deprecated_delegate")
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "delegate"),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun delegate() = delegate
}
diff --git a/okio/src/jvmMain/kotlin/okio/ForwardingTimeout.kt b/okio/src/jvmMain/kotlin/okio/ForwardingTimeout.kt
index 23d83aab..4516a51d 100644
--- a/okio/src/jvmMain/kotlin/okio/ForwardingTimeout.kt
+++ b/okio/src/jvmMain/kotlin/okio/ForwardingTimeout.kt
@@ -17,12 +17,13 @@ package okio
import java.io.IOException
import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.Condition
/** A [Timeout] which forwards calls to another. Useful for subclassing. */
open class ForwardingTimeout(
@get:JvmName("delegate")
@set:JvmSynthetic // So .java callers get the setter that returns this.
- var delegate: Timeout
+ var delegate: Timeout,
) : Timeout() {
// For backwards compatibility with Okio 1.x, this exists so it can return `ForwardingTimeout`.
@@ -40,7 +41,7 @@ open class ForwardingTimeout(
override fun deadlineNanoTime() = delegate.deadlineNanoTime()
override fun deadlineNanoTime(deadlineNanoTime: Long) = delegate.deadlineNanoTime(
- deadlineNanoTime
+ deadlineNanoTime,
)
override fun clearTimeout() = delegate.clearTimeout()
@@ -49,4 +50,10 @@ open class ForwardingTimeout(
@Throws(IOException::class)
override fun throwIfReached() = delegate.throwIfReached()
+
+ override fun cancel() = delegate.cancel()
+
+ override fun awaitSignal(condition: Condition) = delegate.awaitSignal(condition)
+
+ override fun waitUntilNotified(monitor: Any) = delegate.waitUntilNotified(monitor)
}
diff --git a/okio/src/jvmMain/kotlin/okio/GzipSink.kt b/okio/src/jvmMain/kotlin/okio/GzipSink.kt
index db87dafa..1b5cbc63 100644
--- a/okio/src/jvmMain/kotlin/okio/GzipSink.kt
+++ b/okio/src/jvmMain/kotlin/okio/GzipSink.kt
@@ -139,7 +139,7 @@ class GzipSink(sink: Sink) : Sink {
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "deflater"),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun deflater() = deflater
}
diff --git a/okio/src/jvmMain/kotlin/okio/GzipSource.kt b/okio/src/jvmMain/kotlin/okio/GzipSource.kt
index ff1e3d32..1cc4172a 100644
--- a/okio/src/jvmMain/kotlin/okio/GzipSource.kt
+++ b/okio/src/jvmMain/kotlin/okio/GzipSource.kt
@@ -117,7 +117,7 @@ class GzipSource(source: Source) : Source {
if (flags.getBit(FEXTRA)) {
source.require(2)
if (fhcrc) updateCrc(source.buffer, 0, 2)
- val xlen = source.buffer.readShortLe().toLong()
+ val xlen = (source.buffer.readShortLe().toInt() and 0xffff).toLong()
source.require(xlen)
if (fhcrc) updateCrc(source.buffer, 0, xlen)
source.skip(xlen)
diff --git a/okio/src/jvmMain/kotlin/okio/HashingSink.kt b/okio/src/jvmMain/kotlin/okio/HashingSink.kt
index 36bfd2b8..0c097d20 100644
--- a/okio/src/jvmMain/kotlin/okio/HashingSink.kt
+++ b/okio/src/jvmMain/kotlin/okio/HashingSink.kt
@@ -61,7 +61,7 @@ actual class HashingSink : ForwardingSink, Sink { // Need to explicitly declare
}
} catch (e: InvalidKeyException) {
throw IllegalArgumentException(e)
- }
+ },
)
@Throws(IOException::class)
@@ -103,16 +103,24 @@ actual class HashingSink : ForwardingSink, Sink { // Need to explicitly declare
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "hash"),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun hash() = hash
actual companion object {
- /** Returns a sink that uses the obsolete MD5 hash algorithm to produce 128-bit hashes. */
+ /**
+ * Returns a sink that uses the obsolete MD5 hash algorithm to produce 128-bit hashes.
+ *
+ * MD5 has been vulnerable to collisions since 2004. It should not be used in new code.
+ */
@JvmStatic
actual fun md5(sink: Sink) = HashingSink(sink, "MD5")
- /** Returns a sink that uses the obsolete SHA-1 hash algorithm to produce 160-bit hashes. */
+ /**
+ * Returns a sink that uses the obsolete SHA-1 hash algorithm to produce 160-bit hashes.
+ *
+ * SHA-1 has been vulnerable to collisions since 2017. It should not be used in new code.
+ */
@JvmStatic
actual fun sha1(sink: Sink) = HashingSink(sink, "SHA-1")
diff --git a/okio/src/jvmMain/kotlin/okio/HashingSource.kt b/okio/src/jvmMain/kotlin/okio/HashingSource.kt
index 25b695d7..e3d9191b 100644
--- a/okio/src/jvmMain/kotlin/okio/HashingSource.kt
+++ b/okio/src/jvmMain/kotlin/okio/HashingSource.kt
@@ -62,7 +62,7 @@ actual class HashingSource : ForwardingSource, Source { // Need to explicitly de
}
} catch (e: InvalidKeyException) {
throw IllegalArgumentException(e)
- }
+ },
)
@Throws(IOException::class)
@@ -114,16 +114,24 @@ actual class HashingSource : ForwardingSource, Source { // Need to explicitly de
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "hash"),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun hash() = hash
actual companion object {
- /** Returns a source that uses the obsolete MD5 hash algorithm to produce 128-bit hashes. */
+ /**
+ * Returns a source that uses the obsolete MD5 hash algorithm to produce 128-bit hashes.
+ *
+ * MD5 has been vulnerable to collisions since 2004. It should not be used in new code.
+ */
@JvmStatic
actual fun md5(source: Source) = HashingSource(source, "MD5")
- /** Returns a source that uses the obsolete SHA-1 hash algorithm to produce 160-bit hashes. */
+ /**
+ * Returns a source that uses the obsolete SHA-1 hash algorithm to produce 160-bit hashes.
+ *
+ * SHA-1 has been vulnerable to collisions since 2017. It should not be used in new code.
+ */
@JvmStatic
actual fun sha1(source: Source) = HashingSource(source, "SHA-1")
diff --git a/okio/src/jvmMain/kotlin/okio/InflaterSource.kt b/okio/src/jvmMain/kotlin/okio/InflaterSource.kt
index 6fe1feb6..1d72d9d4 100644
--- a/okio/src/jvmMain/kotlin/okio/InflaterSource.kt
+++ b/okio/src/jvmMain/kotlin/okio/InflaterSource.kt
@@ -88,7 +88,7 @@ internal constructor(private val source: BufferedSource, private val inflater: I
return bytesInflated.toLong()
}
- // We allocated a tail segment but didn't end up needing it. Recycle!
+ // We allocated a tail segment, but didn't end up needing it. Recycle!
if (tail.pos == tail.limit) {
sink.head = tail.pop()
SegmentPool.recycle(tail)
diff --git a/okio/src/jvmMain/kotlin/okio/JvmFileHandle.kt b/okio/src/jvmMain/kotlin/okio/JvmFileHandle.kt
new file mode 100644
index 00000000..5a6c3b73
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/JvmFileHandle.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.RandomAccessFile
+
+internal class JvmFileHandle(
+ readWrite: Boolean,
+ private val randomAccessFile: RandomAccessFile,
+) : FileHandle(readWrite) {
+
+ @Synchronized
+ override fun protectedResize(size: Long) {
+ val currentSize = size()
+ val delta = size - currentSize
+ if (delta > 0) {
+ protectedWrite(currentSize, ByteArray(delta.toInt()), 0, delta.toInt())
+ } else {
+ randomAccessFile.setLength(size)
+ }
+ }
+
+ @Synchronized
+ override fun protectedSize(): Long {
+ return randomAccessFile.length()
+ }
+
+ @Synchronized
+ override fun protectedRead(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ): Int {
+ randomAccessFile.seek(fileOffset)
+ var bytesRead = 0
+ while (bytesRead < byteCount) {
+ val readResult = randomAccessFile.read(array, arrayOffset, byteCount - bytesRead)
+ if (readResult == -1) {
+ if (bytesRead == 0) return -1
+ break
+ }
+ bytesRead += readResult
+ }
+ return bytesRead
+ }
+
+ @Synchronized
+ override fun protectedWrite(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ) {
+ randomAccessFile.seek(fileOffset)
+ randomAccessFile.write(array, arrayOffset, byteCount)
+ }
+
+ @Synchronized
+ override fun protectedFlush() {
+ randomAccessFile.fd.sync()
+ }
+
+ @Synchronized
+ override fun protectedClose() {
+ randomAccessFile.close()
+ }
+}
diff --git a/okio/src/jvmMain/kotlin/okio/JvmOkio.kt b/okio/src/jvmMain/kotlin/okio/JvmOkio.kt
index 25d55166..614c48b9 100644
--- a/okio/src/jvmMain/kotlin/okio/JvmOkio.kt
+++ b/okio/src/jvmMain/kotlin/okio/JvmOkio.kt
@@ -20,7 +20,6 @@
package okio
-import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
@@ -31,19 +30,20 @@ import java.net.Socket
import java.net.SocketTimeoutException
import java.nio.file.Files
import java.nio.file.OpenOption
-import java.nio.file.Path
+import java.nio.file.Path as NioPath
import java.security.MessageDigest
import java.util.logging.Level
import java.util.logging.Logger
import javax.crypto.Cipher
import javax.crypto.Mac
+import okio.internal.ResourceFileSystem
/** Returns a sink that writes to `out`. */
fun OutputStream.sink(): Sink = OutputStreamSink(this, Timeout())
private class OutputStreamSink(
private val out: OutputStream,
- private val timeout: Timeout
+ private val timeout: Timeout,
) : Sink {
override fun write(source: Buffer, byteCount: Long) {
@@ -78,9 +78,9 @@ private class OutputStreamSink(
/** Returns a source that reads from `in`. */
fun InputStream.source(): Source = InputStreamSource(this, Timeout())
-private class InputStreamSource(
+private open class InputStreamSource(
private val input: InputStream,
- private val timeout: Timeout
+ private val timeout: Timeout,
) : Source {
override fun read(sink: Buffer, byteCount: Long): Long {
@@ -178,18 +178,16 @@ fun File.appendingSink(): Sink = FileOutputStream(this, true).sink()
/** Returns a source that reads from `file`. */
@Throws(FileNotFoundException::class)
-fun File.source(): Source = inputStream().source()
+fun File.source(): Source = InputStreamSource(inputStream(), Timeout.NONE)
-/** Returns a source that reads from `path`. */
+/** Returns a sink that writes to `path`. */
@Throws(IOException::class)
-@IgnoreJRERequirement // Can only be invoked on Java 7+.
-fun Path.sink(vararg options: OpenOption): Sink =
+fun NioPath.sink(vararg options: OpenOption): Sink =
Files.newOutputStream(this, *options).sink()
-/** Returns a sink that writes to `path`. */
+/** Returns a source that reads from `path`. */
@Throws(IOException::class)
-@IgnoreJRERequirement // Can only be invoked on Java 7+.
-fun Path.source(vararg options: OpenOption): Source =
+fun NioPath.source(vararg options: OpenOption): Source =
Files.newInputStream(this, *options).source()
/**
@@ -226,6 +224,11 @@ fun Sink.hashingSink(digest: MessageDigest): HashingSink = HashingSink(this, dig
*/
fun Source.hashingSource(digest: MessageDigest): HashingSource = HashingSource(this, digest)
+@Throws(IOException::class)
+fun FileSystem.openZip(zipPath: Path): FileSystem = okio.internal.openZip(zipPath, this)
+
+fun ClassLoader.asResourceFileSystem(): FileSystem = ResourceFileSystem(this, indexEagerly = true)
+
/**
* Returns true if this error is due to a firmware bug fixed after Android 4.2.2.
* https://code.google.com/p/android/issues/detail?id=54072
diff --git a/okio/src/jvmMain/kotlin/okio/JvmSystemFileSystem.kt b/okio/src/jvmMain/kotlin/okio/JvmSystemFileSystem.kt
new file mode 100644
index 00000000..8584dfb5
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/JvmSystemFileSystem.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.InterruptedIOException
+import java.io.RandomAccessFile
+import okio.Path.Companion.toOkioPath
+
+/**
+ * A file system that adapts `java.io`.
+ *
+ * This base class is used on Android API levels 15 (our minimum supported API) through 26
+ * (the first release that includes java.nio.file).
+ */
+internal open class JvmSystemFileSystem : FileSystem() {
+ override fun canonicalize(path: Path): Path {
+ val canonicalFile = path.toFile().canonicalFile
+ if (!canonicalFile.exists()) throw FileNotFoundException("no such file")
+ return canonicalFile.toOkioPath()
+ }
+
+ override fun metadataOrNull(path: Path): FileMetadata? {
+ val file = path.toFile()
+ val isRegularFile = file.isFile
+ val isDirectory = file.isDirectory
+ val lastModifiedAtMillis = file.lastModified()
+ val size = file.length()
+
+ if (!isRegularFile &&
+ !isDirectory &&
+ lastModifiedAtMillis == 0L &&
+ size == 0L &&
+ !file.exists()
+ ) {
+ return null
+ }
+
+ return FileMetadata(
+ isRegularFile = isRegularFile,
+ isDirectory = isDirectory,
+ symlinkTarget = null,
+ size = size,
+ createdAtMillis = null,
+ lastModifiedAtMillis = lastModifiedAtMillis,
+ lastAccessedAtMillis = null,
+ )
+ }
+
+ override fun list(dir: Path): List<Path> = list(dir, throwOnFailure = true)!!
+
+ override fun listOrNull(dir: Path): List<Path>? = list(dir, throwOnFailure = false)
+
+ private fun list(dir: Path, throwOnFailure: Boolean): List<Path>? {
+ val file = dir.toFile()
+ val entries = file.list()
+ if (entries == null) {
+ if (throwOnFailure) {
+ if (!file.exists()) throw FileNotFoundException("no such file: $dir")
+ throw IOException("failed to list $dir")
+ } else {
+ return null
+ }
+ }
+ val result = entries.mapTo(mutableListOf()) { dir / it }
+ result.sort()
+ return result
+ }
+
+ override fun openReadOnly(file: Path): FileHandle {
+ return JvmFileHandle(readWrite = false, randomAccessFile = RandomAccessFile(file.toFile(), "r"))
+ }
+
+ override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
+ require(!mustCreate || !mustExist) {
+ "Cannot require mustCreate and mustExist at the same time."
+ }
+ if (mustCreate) file.requireCreate()
+ if (mustExist) file.requireExist()
+ return JvmFileHandle(readWrite = true, randomAccessFile = RandomAccessFile(file.toFile(), "rw"))
+ }
+
+ override fun source(file: Path): Source {
+ return file.toFile().source()
+ }
+
+ override fun sink(file: Path, mustCreate: Boolean): Sink {
+ if (mustCreate) file.requireCreate()
+ return file.toFile().sink()
+ }
+
+ override fun appendingSink(file: Path, mustExist: Boolean): Sink {
+ if (mustExist) file.requireExist()
+ return file.toFile().sink(append = true)
+ }
+
+ override fun createDirectory(dir: Path, mustCreate: Boolean) {
+ if (!dir.toFile().mkdir()) {
+ val alreadyExist = metadataOrNull(dir)?.isDirectory == true
+ if (alreadyExist) {
+ if (mustCreate) {
+ throw IOException("$dir already exists.")
+ } else {
+ return
+ }
+ }
+ throw IOException("failed to create directory: $dir")
+ }
+ }
+
+ override fun atomicMove(source: Path, target: Path) {
+ // Note that on Windows, this will fail if [target] already exists.
+ val renamed = source.toFile().renameTo(target.toFile())
+ if (!renamed) throw IOException("failed to move $source to $target")
+ }
+
+ override fun delete(path: Path, mustExist: Boolean) {
+ if (Thread.interrupted()) {
+ // If the current thread has been interrupted.
+ throw InterruptedIOException("interrupted")
+ }
+ val file = path.toFile()
+ val deleted = file.delete()
+ if (!deleted) {
+ if (file.exists()) throw IOException("failed to delete $path")
+ if (mustExist) throw FileNotFoundException("no such file: $path")
+ }
+ }
+
+ override fun createSymlink(source: Path, target: Path) {
+ throw IOException("unsupported")
+ }
+
+ override fun toString() = "JvmSystemFileSystem"
+
+ // We have to implement existence verification non-atomically on the JVM because there's no API
+ // to do so.
+ private fun Path.requireExist() {
+ if (!exists(this)) throw IOException("$this doesn't exist.")
+ }
+
+ private fun Path.requireCreate() {
+ if (exists(this)) throw IOException("$this already exists.")
+ }
+}
diff --git a/okio/src/jvmMain/kotlin/okio/NioFileSystemFileHandle.kt b/okio/src/jvmMain/kotlin/okio/NioFileSystemFileHandle.kt
new file mode 100644
index 00000000..480f41a4
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/NioFileSystemFileHandle.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.nio.ByteBuffer
+import java.nio.channels.FileChannel
+
+internal class NioFileSystemFileHandle(
+ readWrite: Boolean,
+ private val fileChannel: FileChannel,
+) : FileHandle(readWrite) {
+
+ @Synchronized
+ override fun protectedResize(size: Long) {
+ val currentSize = size()
+ val delta = size - currentSize
+ if (delta > 0) {
+ protectedWrite(currentSize, ByteArray(delta.toInt()), 0, delta.toInt())
+ } else {
+ fileChannel.truncate(size)
+ }
+ }
+
+ @Synchronized
+ override fun protectedSize(): Long {
+ return fileChannel.size()
+ }
+
+ @Synchronized
+ override fun protectedRead(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ): Int {
+ fileChannel.position(fileOffset)
+ val byteBuffer = ByteBuffer.wrap(array, arrayOffset, byteCount)
+ var bytesRead = 0
+ while (bytesRead < byteCount) {
+ val readResult = fileChannel.read(byteBuffer)
+ if (readResult == -1) {
+ if (bytesRead == 0) return -1
+ break
+ }
+ bytesRead += readResult
+ }
+ return bytesRead
+ }
+
+ @Synchronized
+ override fun protectedWrite(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ) {
+ fileChannel.position(fileOffset)
+ val byteBuffer = ByteBuffer.wrap(array, arrayOffset, byteCount)
+ fileChannel.write(byteBuffer)
+ }
+
+ @Synchronized
+ override fun protectedFlush() {
+ fileChannel.force(true)
+ }
+
+ @Synchronized
+ override fun protectedClose() {
+ fileChannel.close()
+ }
+}
diff --git a/okio/src/jvmMain/kotlin/okio/NioFileSystemWrappingFileSystem.kt b/okio/src/jvmMain/kotlin/okio/NioFileSystemWrappingFileSystem.kt
new file mode 100644
index 00000000..ddf11d81
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/NioFileSystemWrappingFileSystem.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.InterruptedIOException
+import java.nio.channels.FileChannel
+import java.nio.file.FileSystem as NioFileSystem
+import java.nio.file.NoSuchFileException
+import java.nio.file.Path as NioPath
+import java.nio.file.StandardCopyOption
+import java.nio.file.StandardOpenOption
+import kotlin.io.path.createDirectory
+import kotlin.io.path.createSymbolicLinkPointingTo
+import kotlin.io.path.deleteExisting
+import kotlin.io.path.exists
+import kotlin.io.path.inputStream
+import kotlin.io.path.listDirectoryEntries
+import kotlin.io.path.moveTo
+import kotlin.io.path.outputStream
+import okio.Path.Companion.toOkioPath
+
+/**
+ * A file system that wraps a `java.nio.file.FileSystem` and executes all operations in the context of the wrapped file
+ * system.
+ */
+internal class NioFileSystemWrappingFileSystem(private val nioFileSystem: NioFileSystem) : NioSystemFileSystem() {
+ /**
+ * On a [java.nio.file.FileSystem], paths are stateful and hold a reference to the file system they got provided from.
+ * Using [getPath][NioFileSystem.getPath], we ask [nioFileSystem] to wrap the [Path]'s value in order to set itself as
+ * its provider which is needed for operations on the nio file system to work properly.
+ */
+ private fun Path.resolve(): NioPath {
+ return nioFileSystem.getPath(toString())
+ }
+
+ override fun canonicalize(path: Path): Path {
+ try {
+ return path.resolve().toRealPath().toOkioPath()
+ } catch (e: NoSuchFileException) {
+ throw FileNotFoundException("no such file: $path")
+ }
+ }
+
+ override fun metadataOrNull(path: Path): FileMetadata? {
+ return metadataOrNull(path.resolve())
+ }
+
+ override fun list(dir: Path): List<Path> = list(dir, throwOnFailure = true)!!
+
+ override fun listOrNull(dir: Path): List<Path>? = list(dir, throwOnFailure = false)
+
+ private fun list(dir: Path, throwOnFailure: Boolean): List<Path>? {
+ val nioDir = dir.resolve()
+ val entries = try {
+ nioDir.listDirectoryEntries()
+ } catch (e: Exception) {
+ if (throwOnFailure) {
+ if (!nioDir.exists()) throw FileNotFoundException("no such file: $dir")
+ throw IOException("failed to list $dir")
+ } else {
+ return null
+ }
+ }
+ val result = entries.mapTo(mutableListOf()) { entry -> entry.toOkioPath() }
+ result.sort()
+ return result
+ }
+
+ override fun openReadOnly(file: Path): FileHandle {
+ val channel = try {
+ FileChannel.open(file.resolve(), StandardOpenOption.READ)
+ } catch (e: NoSuchFileException) {
+ throw FileNotFoundException("no such file: $file")
+ }
+ return NioFileSystemFileHandle(readWrite = false, fileChannel = channel)
+ }
+
+ override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
+ require(!mustCreate || !mustExist) { "Cannot require mustCreate and mustExist at the same time." }
+ val openOptions = buildList {
+ add(StandardOpenOption.READ)
+ add(StandardOpenOption.WRITE)
+ if (mustCreate) {
+ add(StandardOpenOption.CREATE_NEW)
+ } else if (!mustExist) {
+ add(StandardOpenOption.CREATE)
+ }
+ }
+
+ val channel = try {
+ FileChannel.open(file.resolve(), *openOptions.toTypedArray())
+ } catch (e: NoSuchFileException) {
+ throw FileNotFoundException("no such file: $file")
+ }
+ return NioFileSystemFileHandle(readWrite = true, fileChannel = channel)
+ }
+
+ override fun source(file: Path): Source {
+ try {
+ return file.resolve().inputStream().source()
+ } catch (e: NoSuchFileException) {
+ throw FileNotFoundException("no such file: $file")
+ }
+ }
+
+ override fun sink(file: Path, mustCreate: Boolean): Sink {
+ val openOptions = buildList {
+ if (mustCreate) add(StandardOpenOption.CREATE_NEW)
+ }
+ try {
+ return file.resolve()
+ .outputStream(*openOptions.toTypedArray())
+ .sink()
+ } catch (e: NoSuchFileException) {
+ throw FileNotFoundException("no such file: $file")
+ }
+ }
+
+ override fun appendingSink(file: Path, mustExist: Boolean): Sink {
+ val openOptions = buildList {
+ add(StandardOpenOption.APPEND)
+ if (!mustExist) add(StandardOpenOption.CREATE)
+ }
+ return file.resolve()
+ .outputStream(*openOptions.toTypedArray())
+ .sink()
+ }
+
+ override fun createDirectory(dir: Path, mustCreate: Boolean) {
+ val alreadyExist = metadataOrNull(dir)?.isDirectory == true
+ if (alreadyExist && mustCreate) {
+ throw IOException("$dir already exists.")
+ }
+
+ try {
+ dir.resolve().createDirectory()
+ } catch (e: IOException) {
+ if (alreadyExist) return
+ throw IOException("failed to create directory: $dir", e)
+ }
+ }
+
+ // Note that `java.nio.file.FileSystem` allows atomic moves of a file even if the target is an existing directory.
+ override fun atomicMove(source: Path, target: Path) {
+ try {
+ source.resolve().moveTo(
+ target.resolve(),
+ StandardCopyOption.ATOMIC_MOVE,
+ StandardCopyOption.REPLACE_EXISTING,
+ )
+ } catch (e: NoSuchFileException) {
+ throw FileNotFoundException(e.message)
+ } catch (e: UnsupportedOperationException) {
+ throw IOException("atomic move not supported")
+ }
+ }
+
+ override fun delete(path: Path, mustExist: Boolean) {
+ if (Thread.interrupted()) {
+ // If the current thread has been interrupted.
+ throw InterruptedIOException("interrupted")
+ }
+ val nioPath = path.resolve()
+ try {
+ nioPath.deleteExisting()
+ } catch (e: NoSuchFileException) {
+ if (mustExist) throw FileNotFoundException("no such file: $path")
+ } catch (e: IOException) {
+ if (nioPath.exists()) throw IOException("failed to delete $path")
+ }
+ }
+
+ override fun createSymlink(source: Path, target: Path) {
+ source.resolve().createSymbolicLinkPointingTo(target.resolve())
+ }
+
+ override fun toString() = nioFileSystem::class.simpleName!!
+}
diff --git a/okio/src/jvmMain/kotlin/okio/NioSystemFileSystem.kt b/okio/src/jvmMain/kotlin/okio/NioSystemFileSystem.kt
new file mode 100644
index 00000000..f4ebbced
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/NioSystemFileSystem.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.nio.file.FileSystemException
+import java.nio.file.Files
+import java.nio.file.LinkOption
+import java.nio.file.NoSuchFileException
+import java.nio.file.Path as NioPath
+import java.nio.file.StandardCopyOption.ATOMIC_MOVE
+import java.nio.file.StandardCopyOption.REPLACE_EXISTING
+import java.nio.file.attribute.BasicFileAttributes
+import java.nio.file.attribute.FileTime
+import okio.Path.Companion.toOkioPath
+
+/**
+ * Extends [JvmSystemFileSystem] for platforms that support `java.nio.file` first introduced in
+ * Java 7 and Android 8.0 (API level 26).
+ */
+internal open class NioSystemFileSystem : JvmSystemFileSystem() {
+ override fun metadataOrNull(path: Path): FileMetadata? {
+ return metadataOrNull(path.toNioPath())
+ }
+
+ protected fun metadataOrNull(nioPath: NioPath): FileMetadata? {
+ val attributes = try {
+ Files.readAttributes(
+ nioPath,
+ BasicFileAttributes::class.java,
+ LinkOption.NOFOLLOW_LINKS,
+ )
+ } catch (_: NoSuchFileException) {
+ return null
+ } catch (_: FileSystemException) {
+ return null
+ }
+
+ val symlinkTarget: NioPath? = if (attributes.isSymbolicLink) {
+ Files.readSymbolicLink(nioPath)
+ } else {
+ null
+ }
+
+ return FileMetadata(
+ isRegularFile = attributes.isRegularFile,
+ isDirectory = attributes.isDirectory,
+ symlinkTarget = symlinkTarget?.toOkioPath(),
+ size = attributes.size(),
+ createdAtMillis = attributes.creationTime()?.zeroToNull(),
+ lastModifiedAtMillis = attributes.lastModifiedTime()?.zeroToNull(),
+ lastAccessedAtMillis = attributes.lastAccessTime()?.zeroToNull(),
+ )
+ }
+
+ /**
+ * Returns this time as an epoch millis. If this is 0L this returns null, because epoch time 0L is
+ * a special value that indicates the requested time was not available.
+ */
+ private fun FileTime.zeroToNull(): Long? {
+ return toMillis().takeIf { it != 0L }
+ }
+
+ override fun atomicMove(source: Path, target: Path) {
+ try {
+ Files.move(source.toNioPath(), target.toNioPath(), ATOMIC_MOVE, REPLACE_EXISTING)
+ } catch (e: NoSuchFileException) {
+ throw FileNotFoundException(e.message)
+ } catch (e: UnsupportedOperationException) {
+ throw IOException("atomic move not supported")
+ }
+ }
+
+ override fun createSymlink(source: Path, target: Path) {
+ Files.createSymbolicLink(source.toNioPath(), target.toNioPath())
+ }
+
+ override fun toString() = "NioSystemFileSystem"
+}
diff --git a/okio/src/jvmMain/kotlin/okio/Path.kt b/okio/src/jvmMain/kotlin/okio/Path.kt
new file mode 100644
index 00000000..12456969
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/Path.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.File
+import java.nio.file.Path as NioPath
+import java.nio.file.Paths
+import okio.internal.commonCompareTo
+import okio.internal.commonEquals
+import okio.internal.commonHashCode
+import okio.internal.commonIsAbsolute
+import okio.internal.commonIsRelative
+import okio.internal.commonIsRoot
+import okio.internal.commonName
+import okio.internal.commonNameBytes
+import okio.internal.commonNormalized
+import okio.internal.commonParent
+import okio.internal.commonRelativeTo
+import okio.internal.commonResolve
+import okio.internal.commonRoot
+import okio.internal.commonSegments
+import okio.internal.commonSegmentsBytes
+import okio.internal.commonToPath
+import okio.internal.commonToString
+import okio.internal.commonVolumeLetter
+
+actual class Path internal actual constructor(
+ internal actual val bytes: ByteString,
+) : Comparable<Path> {
+ actual val root: Path?
+ get() = commonRoot()
+
+ actual val segments: List<String>
+ get() = commonSegments()
+
+ actual val segmentsBytes: List<ByteString>
+ get() = commonSegmentsBytes()
+
+ actual val isAbsolute: Boolean
+ get() = commonIsAbsolute()
+
+ actual val isRelative: Boolean
+ get() = commonIsRelative()
+
+ @get:JvmName("volumeLetter")
+ actual val volumeLetter: Char?
+ get() = commonVolumeLetter()
+
+ @get:JvmName("nameBytes")
+ actual val nameBytes: ByteString
+ get() = commonNameBytes()
+
+ @get:JvmName("name")
+ actual val name: String
+ get() = commonName()
+
+ @get:JvmName("parent")
+ actual val parent: Path?
+ get() = commonParent()
+
+ actual val isRoot: Boolean
+ get() = commonIsRoot()
+
+ @JvmName("resolve")
+ actual operator fun div(child: String): Path = commonResolve(child, normalize = false)
+
+ @JvmName("resolve")
+ actual operator fun div(child: ByteString): Path = commonResolve(child, normalize = false)
+
+ @JvmName("resolve")
+ actual operator fun div(child: Path): Path = commonResolve(child, normalize = false)
+
+ actual fun resolve(child: String, normalize: Boolean): Path =
+ commonResolve(child, normalize = normalize)
+
+ actual fun resolve(child: ByteString, normalize: Boolean): Path =
+ commonResolve(child, normalize = normalize)
+
+ actual fun resolve(child: Path, normalize: Boolean): Path =
+ commonResolve(child = child, normalize = normalize)
+
+ actual fun relativeTo(other: Path): Path = commonRelativeTo(other)
+
+ actual fun normalized(): Path = commonNormalized()
+
+ fun toFile(): File = File(toString())
+
+ // Can only be invoked on platforms that have java.nio.file.
+ fun toNioPath(): NioPath = Paths.get(toString())
+
+ actual override fun compareTo(other: Path): Int = commonCompareTo(other)
+
+ actual override fun equals(other: Any?): Boolean = commonEquals(other)
+
+ actual override fun hashCode() = commonHashCode()
+
+ actual override fun toString() = commonToString()
+
+ actual companion object {
+ @JvmField
+ actual val DIRECTORY_SEPARATOR: String = File.separator
+
+ @JvmName("get")
+ @JvmStatic
+ @JvmOverloads
+ actual fun String.toPath(normalize: Boolean): Path = commonToPath(normalize)
+
+ @JvmName("get")
+ @JvmStatic
+ @JvmOverloads
+ fun File.toOkioPath(normalize: Boolean = false): Path = toString().toPath(normalize)
+
+ @JvmName("get")
+ @JvmStatic
+ @JvmOverloads
+ fun NioPath.toOkioPath(normalize: Boolean = false): Path = toString().toPath(normalize)
+ }
+}
diff --git a/okio/src/jvmMain/kotlin/okio/Pipe.kt b/okio/src/jvmMain/kotlin/okio/Pipe.kt
index 43c23bfd..0fae4e03 100644
--- a/okio/src/jvmMain/kotlin/okio/Pipe.kt
+++ b/okio/src/jvmMain/kotlin/okio/Pipe.kt
@@ -15,6 +15,10 @@
*/
package okio
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
/**
* A source and a sink that are attached. The sink's output is the source's input. Typically each
* is accessed by its own thread: a producer thread writes data to the sink and a consumer thread
@@ -40,6 +44,9 @@ class Pipe(internal val maxBufferSize: Long) {
internal var sourceClosed = false
internal var foldedSink: Sink? = null
+ val lock: ReentrantLock = ReentrantLock()
+ val condition: Condition = lock.newCondition()
+
init {
require(maxBufferSize >= 1L) { "maxBufferSize < 1: $maxBufferSize" }
}
@@ -51,21 +58,21 @@ class Pipe(internal val maxBufferSize: Long) {
override fun write(source: Buffer, byteCount: Long) {
var byteCount = byteCount
var delegate: Sink? = null
- synchronized(buffer) {
+ lock.withLock {
check(!sinkClosed) { "closed" }
if (canceled) throw IOException("canceled")
while (byteCount > 0) {
foldedSink?.let {
delegate = it
- return@synchronized
+ return@withLock
}
if (sourceClosed) throw IOException("source is closed")
val bufferSpaceAvailable = maxBufferSize - buffer.size
if (bufferSpaceAvailable == 0L) {
- timeout.waitUntilNotified(buffer) // Wait until the source drains the buffer.
+ timeout.awaitSignal(condition) // Wait until the source drains the buffer.
if (canceled) throw IOException("canceled")
continue
}
@@ -73,7 +80,7 @@ class Pipe(internal val maxBufferSize: Long) {
val bytesToWrite = minOf(bufferSpaceAvailable, byteCount)
buffer.write(source, bytesToWrite)
byteCount -= bytesToWrite
- (buffer as Object).notifyAll() // Notify the source that it can resume reading.
+ condition.signalAll() // Notify the source that it can resume reading.
}
}
@@ -82,13 +89,13 @@ class Pipe(internal val maxBufferSize: Long) {
override fun flush() {
var delegate: Sink? = null
- synchronized(buffer) {
+ lock.withLock {
check(!sinkClosed) { "closed" }
if (canceled) throw IOException("canceled")
foldedSink?.let {
delegate = it
- return@synchronized
+ return@withLock
}
if (sourceClosed && buffer.size > 0L) {
@@ -101,17 +108,17 @@ class Pipe(internal val maxBufferSize: Long) {
override fun close() {
var delegate: Sink? = null
- synchronized(buffer) {
+ lock.withLock {
if (sinkClosed) return
foldedSink?.let {
delegate = it
- return@synchronized
+ return@withLock
}
if (sourceClosed && buffer.size > 0L) throw IOException("source is closed")
sinkClosed = true
- (buffer as Object).notifyAll() // Notify the source that no more bytes are coming.
+ condition.signalAll() // Notify the source that no more bytes are coming.
}
delegate?.forward { close() }
@@ -125,26 +132,26 @@ class Pipe(internal val maxBufferSize: Long) {
private val timeout = Timeout()
override fun read(sink: Buffer, byteCount: Long): Long {
- synchronized(buffer) {
+ lock.withLock {
check(!sourceClosed) { "closed" }
if (canceled) throw IOException("canceled")
while (buffer.size == 0L) {
if (sinkClosed) return -1L
- timeout.waitUntilNotified(buffer) // Wait until the sink fills the buffer.
+ timeout.awaitSignal(condition) // Wait until the sink fills the buffer.
if (canceled) throw IOException("canceled")
}
val result = buffer.read(sink, byteCount)
- (buffer as Object).notifyAll() // Notify the sink that it can resume writing.
+ condition.signalAll() // Notify the sink that it can resume writing.
return result
}
}
override fun close() {
- synchronized(buffer) {
+ lock.withLock {
sourceClosed = true
- (buffer as Object).notifyAll() // Notify the sink that no more bytes are desired.
+ condition.signalAll() // Notify the sink that no more bytes are desired.
}
}
@@ -166,7 +173,7 @@ class Pipe(internal val maxBufferSize: Long) {
// must copy it to sink without holding any locks, then try it all again.
var closed = false
lateinit var sinkBuffer: Buffer
- synchronized(buffer) {
+ lock.withLock {
check(foldedSink == null) { "sink already folded" }
if (canceled) {
@@ -183,7 +190,7 @@ class Pipe(internal val maxBufferSize: Long) {
closed = sinkClosed
sinkBuffer = Buffer()
sinkBuffer.write(buffer, buffer.size)
- (buffer as Object).notifyAll() // Notify the sink that it can resume writing.
+ condition.signalAll() // Notify the sink that it can resume writing.
}
var success = false
@@ -197,9 +204,9 @@ class Pipe(internal val maxBufferSize: Long) {
success = true
} finally {
if (!success) {
- synchronized(buffer) {
+ lock.withLock {
sourceClosed = true
- (buffer as Object).notifyAll() // Notify the sink that it can resume writing.
+ condition.signalAll() // Notify the sink that it can resume writing.
}
}
}
@@ -214,7 +221,7 @@ class Pipe(internal val maxBufferSize: Long) {
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "sink"),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun sink() = sink
@@ -222,7 +229,7 @@ class Pipe(internal val maxBufferSize: Long) {
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "source"),
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.ERROR,
)
fun source() = source
@@ -240,10 +247,10 @@ class Pipe(internal val maxBufferSize: Long) {
* operating on the source or the sink.
*/
fun cancel() {
- synchronized(buffer) {
+ lock.withLock {
canceled = true
buffer.clear()
- (buffer as Object).notifyAll() // Notify the source and sink that they're canceled.
+ condition.signalAll() // Notify the source and sink that they're canceled.
}
}
}
diff --git a/okio/src/jvmMain/kotlin/okio/RealBufferedSink.kt b/okio/src/jvmMain/kotlin/okio/RealBufferedSink.kt
index 7df3f937..dece38d6 100644
--- a/okio/src/jvmMain/kotlin/okio/RealBufferedSink.kt
+++ b/okio/src/jvmMain/kotlin/okio/RealBufferedSink.kt
@@ -15,6 +15,10 @@
*/
package okio
+import java.io.IOException
+import java.io.OutputStream
+import java.nio.ByteBuffer
+import java.nio.charset.Charset
import okio.internal.commonClose
import okio.internal.commonEmit
import okio.internal.commonEmitCompleteSegments
@@ -34,15 +38,12 @@ import okio.internal.commonWriteShort
import okio.internal.commonWriteShortLe
import okio.internal.commonWriteUtf8
import okio.internal.commonWriteUtf8CodePoint
-import java.io.IOException
-import java.io.OutputStream
-import java.nio.ByteBuffer
-import java.nio.charset.Charset
internal actual class RealBufferedSink actual constructor(
- @JvmField actual val sink: Sink
+ @JvmField actual val sink: Sink,
) : BufferedSink {
@JvmField val bufferField = Buffer()
+
@JvmField actual var closed: Boolean = false
@Suppress("OVERRIDE_BY_INLINE") // Prevent internal code from calling the getter.
@@ -71,7 +72,7 @@ internal actual class RealBufferedSink actual constructor(
string: String,
beginIndex: Int,
endIndex: Int,
- charset: Charset
+ charset: Charset,
): BufferedSink {
check(!closed) { "closed" }
buffer.writeString(string, beginIndex, endIndex, charset)
diff --git a/okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt b/okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt
index 109ef140..35f6c353 100644
--- a/okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt
+++ b/okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt
@@ -15,6 +15,10 @@
*/
package okio
+import java.io.IOException
+import java.io.InputStream
+import java.nio.ByteBuffer
+import java.nio.charset.Charset
import okio.internal.commonClose
import okio.internal.commonExhausted
import okio.internal.commonIndexOf
@@ -45,15 +49,12 @@ import okio.internal.commonSelect
import okio.internal.commonSkip
import okio.internal.commonTimeout
import okio.internal.commonToString
-import java.io.IOException
-import java.io.InputStream
-import java.nio.ByteBuffer
-import java.nio.charset.Charset
internal actual class RealBufferedSource actual constructor(
- @JvmField actual val source: Source
+ @JvmField actual val source: Source,
) : BufferedSource {
@JvmField val bufferField = Buffer()
+
@JvmField actual var closed: Boolean = false
@Suppress("OVERRIDE_BY_INLINE") // Prevent internal code from calling the getter.
@@ -126,15 +127,17 @@ internal actual class RealBufferedSource actual constructor(
commonIndexOfElement(targetBytes, fromIndex)
override fun rangeEquals(offset: Long, bytes: ByteString) = rangeEquals(
- offset, bytes, 0,
- bytes.size
+ offset,
+ bytes,
+ 0,
+ bytes.size,
)
override fun rangeEquals(
offset: Long,
bytes: ByteString,
bytesOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, bytes, bytesOffset, byteCount)
override fun peek(): BufferedSource = commonPeek()
diff --git a/okio/src/jvmMain/kotlin/okio/SegmentPool.kt b/okio/src/jvmMain/kotlin/okio/SegmentPool.kt
index 7a7b0492..4a05478c 100644
--- a/okio/src/jvmMain/kotlin/okio/SegmentPool.kt
+++ b/okio/src/jvmMain/kotlin/okio/SegmentPool.kt
@@ -15,10 +15,10 @@
*/
package okio
+import java.util.concurrent.atomic.AtomicReference
import okio.SegmentPool.LOCK
import okio.SegmentPool.recycle
import okio.SegmentPool.take
-import java.util.concurrent.atomic.AtomicReference
/**
* This class pools segments in a lock-free singly-linked stack. Though this code is lock-free it
@@ -28,7 +28,9 @@ import java.util.concurrent.atomic.AtomicReference
* On [take], a caller swaps the stack's next pointer with the [LOCK] sentinel. If the stack was
* not already locked, the caller replaces the head node with its successor.
*
- * On [recycle], a caller swaps the head with a new node whose successor is the replaced head.
+ * On [recycle], a caller swaps the stack's next pointer with the [LOCK] sentinel. If the stack was
+ * not already locked, the caller replaces the head node with a new node whose successor is the
+ * replaced head.
*
* On conflict, operations succeed, but segments are not pushed into the stack. For example, a
* [take] that loses a race allocates a new segment regardless of the pool size. A [recycle] call
@@ -104,19 +106,19 @@ internal actual object SegmentPool {
val firstRef = firstRef()
- val first = firstRef.get()
- if (first === LOCK) return // A take() is currently in progress.
+ val first = firstRef.getAndSet(LOCK)
+ if (first === LOCK) return // A take() or recycle() is currently in progress.
val firstLimit = first?.limit ?: 0
- if (firstLimit >= MAX_SIZE) return // Pool is full.
+ if (firstLimit >= MAX_SIZE) {
+ firstRef.set(first) // Pool is full.
+ return
+ }
segment.next = first
segment.pos = 0
segment.limit = firstLimit + Segment.SIZE
- // If we lost a race with another operation, don't recycle this segment.
- if (!firstRef.compareAndSet(first, segment)) {
- segment.next = null // Don't leak a reference in the pool either!
- }
+ firstRef.set(segment)
}
private fun firstRef(): AtomicReference<Segment?> {
diff --git a/okio/src/jvmMain/kotlin/okio/SegmentedByteString.kt b/okio/src/jvmMain/kotlin/okio/SegmentedByteString.kt
index bce9d5a9..fe64afdb 100644
--- a/okio/src/jvmMain/kotlin/okio/SegmentedByteString.kt
+++ b/okio/src/jvmMain/kotlin/okio/SegmentedByteString.kt
@@ -15,6 +15,15 @@
*/
package okio
+import java.io.IOException
+import java.io.OutputStream
+import java.nio.ByteBuffer
+import java.nio.charset.Charset
+import java.security.InvalidKeyException
+import java.security.MessageDigest
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+import okio.internal.commonCopyInto
import okio.internal.commonEquals
import okio.internal.commonGetSize
import okio.internal.commonHashCode
@@ -24,18 +33,10 @@ import okio.internal.commonSubstring
import okio.internal.commonToByteArray
import okio.internal.commonWrite
import okio.internal.forEachSegment
-import java.io.IOException
-import java.io.OutputStream
-import java.nio.ByteBuffer
-import java.nio.charset.Charset
-import java.security.InvalidKeyException
-import java.security.MessageDigest
-import javax.crypto.Mac
-import javax.crypto.spec.SecretKeySpec
internal actual class SegmentedByteString internal actual constructor(
@Transient internal actual val segments: Array<ByteArray>,
- @Transient internal actual val directory: IntArray
+ @Transient internal actual val directory: IntArray,
) : ByteString(EMPTY.data) {
override fun string(charset: Charset) = toByteString().string(charset)
@@ -98,21 +99,28 @@ internal actual class SegmentedByteString internal actual constructor(
offset: Int,
other: ByteString,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
override fun rangeEquals(
offset: Int,
other: ByteArray,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
+ override fun copyInto(
+ offset: Int,
+ target: ByteArray,
+ targetOffset: Int,
+ byteCount: Int,
+ ) = commonCopyInto(offset, target, targetOffset, byteCount)
+
override fun indexOf(other: ByteArray, fromIndex: Int) = toByteString().indexOf(other, fromIndex)
override fun lastIndexOf(other: ByteArray, fromIndex: Int) = toByteString().lastIndexOf(
other,
- fromIndex
+ fromIndex,
)
/** Returns a copy as a non-segmented byte string. */
diff --git a/okio/src/jvmMain/kotlin/okio/Throttler.kt b/okio/src/jvmMain/kotlin/okio/Throttler.kt
index dbb83fe3..859fb716 100644
--- a/okio/src/jvmMain/kotlin/okio/Throttler.kt
+++ b/okio/src/jvmMain/kotlin/okio/Throttler.kt
@@ -17,6 +17,9 @@ package okio
import java.io.IOException
import java.io.InterruptedIOException
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
/**
* Enables limiting of Source and Sink throughput. Attach to this throttler via [source] and [sink]
@@ -40,12 +43,15 @@ class Throttler internal constructor(
* The nanoTime that we've consumed all bytes through. This is never greater than the current
* nanoTime plus nanosForMaxByteCount.
*/
- private var allocatedUntil: Long
+ private var allocatedUntil: Long,
) {
private var bytesPerSecond: Long = 0L
private var waitByteCount: Long = 8 * 1024 // 8 KiB.
private var maxByteCount: Long = 256 * 1024 // 256 KiB.
+ val lock: ReentrantLock = ReentrantLock()
+ val condition: Condition = lock.newCondition()
+
constructor() : this(allocatedUntil = System.nanoTime())
/** Sets the rate at which bytes will be allocated. Use 0 for no limit. */
@@ -53,9 +59,9 @@ class Throttler internal constructor(
fun bytesPerSecond(
bytesPerSecond: Long,
waitByteCount: Long = this.waitByteCount,
- maxByteCount: Long = this.maxByteCount
+ maxByteCount: Long = this.maxByteCount,
) {
- synchronized(this) {
+ lock.withLock {
require(bytesPerSecond >= 0)
require(waitByteCount > 0)
require(maxByteCount >= waitByteCount)
@@ -63,7 +69,7 @@ class Throttler internal constructor(
this.bytesPerSecond = bytesPerSecond
this.waitByteCount = waitByteCount
this.maxByteCount = maxByteCount
- (this as Object).notifyAll()
+ condition.signalAll()
}
}
@@ -74,15 +80,14 @@ class Throttler internal constructor(
internal fun take(byteCount: Long): Long {
require(byteCount > 0)
- synchronized(this) {
+ lock.withLock {
while (true) {
val now = System.nanoTime()
val byteCountOrWaitNanos = byteCountOrWaitNanos(now, byteCount)
if (byteCountOrWaitNanos >= 0) return byteCountOrWaitNanos
- waitNanos(-byteCountOrWaitNanos)
+ condition.awaitNanos(-byteCountOrWaitNanos)
}
}
- throw AssertionError() // Unreachable, but synchronized() doesn't know that.
}
/**
@@ -125,12 +130,6 @@ class Throttler internal constructor(
private fun Long.bytesToNanos() = this * 1_000_000_000L / bytesPerSecond
- private fun waitNanos(nanosToWait: Long) {
- val millisToWait = nanosToWait / 1_000_000L
- val remainderNanos = nanosToWait - (millisToWait * 1_000_000L)
- (this as Object).wait(millisToWait, remainderNanos.toInt())
- }
-
/** Create a Source which honors this Throttler. */
fun source(source: Source): Source {
return object : ForwardingSource(source) {
diff --git a/okio/src/jvmMain/kotlin/okio/Timeout.kt b/okio/src/jvmMain/kotlin/okio/Timeout.kt
index c522380f..b5f5f359 100644
--- a/okio/src/jvmMain/kotlin/okio/Timeout.kt
+++ b/okio/src/jvmMain/kotlin/okio/Timeout.kt
@@ -18,6 +18,11 @@ package okio
import java.io.IOException
import java.io.InterruptedIOException
import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.Condition
+import kotlin.concurrent.Volatile
+import kotlin.time.Duration
+import kotlin.time.DurationUnit
+import kotlin.time.toTimeUnit
actual open class Timeout {
/**
@@ -29,6 +34,12 @@ actual open class Timeout {
private var timeoutNanos = 0L
/**
+ * A sentinel that is updated to a new object on each call to [cancel]. Sample this property
+ * before and after an operation to test if the timeout was canceled during the operation.
+ */
+ @Volatile private var cancelMark: Any? = null
+
+ /**
* Wait at most `timeout` time before aborting an operation. Using a per-operation timeout means
* that as long as forward progress is being made, no sequence of operations will fail.
*
@@ -104,6 +115,104 @@ actual open class Timeout {
}
/**
+ * Prevent all current applications of this timeout from firing. Use this when a time-limited
+ * operation should no longer be time-limited because the nature of the operation has changed.
+ *
+ * This function does not mutate the [deadlineNanoTime] or [timeoutNanos] properties of this
+ * timeout. It only applies to active operations that are limited by this timeout, and applies by
+ * allowing those operations to run indefinitely.
+ *
+ * Subclasses that override this method must call `super.cancel()`.
+ */
+ open fun cancel() {
+ cancelMark = Any()
+ }
+
+ /**
+ * Waits on `monitor` until it is signaled. Throws [InterruptedIOException] if either the thread
+ * is interrupted or if this timeout elapses before `monitor` is signaled.
+ * The caller must hold the lock that monitor is bound to.
+ *
+ * Here's a sample class that uses `awaitSignal()` to await a specific state. Note that the
+ * call is made within a loop to avoid unnecessary waiting and to mitigate spurious notifications.
+ * ```
+ * class Dice {
+ * Random random = new Random();
+ * int latestTotal;
+ *
+ * ReentrantLock lock = new ReentrantLock();
+ * Condition condition = lock.newCondition();
+ *
+ * public void roll() {
+ * lock.withLock {
+ * latestTotal = 2 + random.nextInt(6) + random.nextInt(6);
+ * System.out.println("Rolled " + latestTotal);
+ * condition.signalAll();
+ * }
+ * }
+ *
+ * public void rollAtFixedRate(int period, TimeUnit timeUnit) {
+ * Executors.newScheduledThreadPool(0).scheduleAtFixedRate(new Runnable() {
+ * public void run() {
+ * roll();
+ * }
+ * }, 0, period, timeUnit);
+ * }
+ *
+ * public void awaitTotal(Timeout timeout, int total)
+ * throws InterruptedIOException {
+ * lock.withLock {
+ * while (latestTotal != total) {
+ * timeout.awaitSignal(this);
+ * }
+ * }
+ * }
+ * }
+ * ```
+ */
+ @Throws(InterruptedIOException::class)
+ open fun awaitSignal(condition: Condition) {
+ try {
+ val hasDeadline = hasDeadline()
+ val timeoutNanos = timeoutNanos()
+
+ if (!hasDeadline && timeoutNanos == 0L) {
+ condition.await() // There is no timeout: wait forever.
+ return
+ }
+
+ // Compute how long we'll wait.
+ val waitNanos = if (hasDeadline && timeoutNanos != 0L) {
+ val deadlineNanos = deadlineNanoTime() - System.nanoTime()
+ minOf(timeoutNanos, deadlineNanos)
+ } else if (hasDeadline) {
+ deadlineNanoTime() - System.nanoTime()
+ } else {
+ timeoutNanos
+ }
+
+ if (waitNanos <= 0) throw InterruptedIOException("timeout")
+
+ val cancelMarkBefore = cancelMark
+
+ // Attempt to wait that long. This will return early if the monitor is notified.
+ val nanosRemaining = condition.awaitNanos(waitNanos)
+
+ // If there's time remaining, we probably got the call we were waiting for.
+ if (nanosRemaining > 0) return
+
+ // Return without throwing if this timeout was canceled while we were waiting. Note that this
+ // return is a 'spurious wakeup' because Condition.signal() was not called.
+ if (cancelMark !== cancelMarkBefore) return
+
+ throw InterruptedIOException("timeout")
+ } catch (e: InterruptedException) {
+ Thread.currentThread().interrupt() // Retain interrupted status.
+ throw InterruptedIOException("interrupted")
+ }
+ }
+
+ /**
* Waits on `monitor` until it is notified. Throws [InterruptedIOException] if either the thread
* is interrupted or if this timeout elapses before `monitor` is notified. The caller must be
* synchronized on `monitor`.
@@ -139,7 +248,7 @@ actual open class Timeout {
* ```
*/
@Throws(InterruptedIOException::class)
- fun waitUntilNotified(monitor: Any) {
+ open fun waitUntilNotified(monitor: Any) {
try {
val hasDeadline = hasDeadline()
val timeoutNanos = timeoutNanos()
@@ -160,18 +269,23 @@ actual open class Timeout {
timeoutNanos
}
- // Attempt to wait that long. This will break out early if the monitor is notified.
- var elapsedNanos = 0L
- if (waitNanos > 0L) {
- val waitMillis = waitNanos / 1000000L
- (monitor as Object).wait(waitMillis, (waitNanos - waitMillis * 1000000L).toInt())
- elapsedNanos = System.nanoTime() - start
- }
+ if (waitNanos <= 0) throw InterruptedIOException("timeout")
- // Throw if the timeout elapsed before the monitor was notified.
- if (elapsedNanos >= waitNanos) {
- throw InterruptedIOException("timeout")
- }
+ val cancelMarkBefore = cancelMark
+
+ // Attempt to wait that long. This will return early if the monitor is notified.
+ val waitMillis = waitNanos / 1000000L
+ (monitor as Object).wait(waitMillis, (waitNanos - waitMillis * 1000000L).toInt())
+ val elapsedNanos = System.nanoTime() - start
+
+ // If there's time remaining, we probably got the call we were waiting for.
+ if (elapsedNanos < waitNanos) return
+
+ // Return without throwing if this timeout was canceled while we were waiting. Note that this
+ // return is a 'spurious wakeup' because Object.notify() was not called.
+ if (cancelMark !== cancelMarkBefore) return
+
+ throw InterruptedIOException("timeout")
} catch (e: InterruptedException) {
Thread.currentThread().interrupt() // Retain interrupted status.
throw InterruptedIOException("interrupted")
@@ -182,7 +296,7 @@ actual open class Timeout {
* Applies the minimum intersection between this timeout and `other`, run `block`, then finally
* rollback this timeout's values.
*/
- inline fun intersectWith(other: Timeout, block: () -> Unit) {
+ inline fun <T> intersectWith(other: Timeout, block: () -> T): T {
val originalTimeout = this.timeoutNanos()
this.timeout(minTimeout(other.timeoutNanos(), this.timeoutNanos()), TimeUnit.NANOSECONDS)
@@ -192,7 +306,7 @@ actual open class Timeout {
this.deadlineNanoTime(Math.min(this.deadlineNanoTime(), other.deadlineNanoTime()))
}
try {
- block()
+ return block()
} finally {
this.timeout(originalTimeout, TimeUnit.NANOSECONDS)
if (other.hasDeadline()) {
@@ -204,7 +318,7 @@ actual open class Timeout {
this.deadlineNanoTime(other.deadlineNanoTime())
}
try {
- block()
+ return block()
} finally {
this.timeout(originalTimeout, TimeUnit.NANOSECONDS)
if (other.hasDeadline()) {
@@ -223,6 +337,14 @@ actual open class Timeout {
override fun throwIfReached() {}
}
+ fun Timeout.timeout(timeout: Long, unit: DurationUnit): Timeout {
+ return timeout(timeout, unit.toTimeUnit())
+ }
+
+ fun Timeout.timeout(duration: Duration): Timeout {
+ return timeout(duration.inWholeNanoseconds, TimeUnit.NANOSECONDS)
+ }
+
fun minTimeout(aNanos: Long, bNanos: Long) = when {
aNanos == 0L -> bNanos
bNanos == 0L -> aNanos
diff --git a/okio/src/jvmMain/kotlin/okio/ZipFileSystem.kt b/okio/src/jvmMain/kotlin/okio/ZipFileSystem.kt
new file mode 100644
index 00000000..2c8d9ea5
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/ZipFileSystem.kt
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.FileNotFoundException
+import java.util.zip.Inflater
+import okio.Path.Companion.toPath
+import okio.internal.COMPRESSION_METHOD_STORED
+import okio.internal.FixedLengthSource
+import okio.internal.ZipEntry
+import okio.internal.readLocalHeader
+import okio.internal.skipLocalHeader
+
+/**
+ * Read only access to a [zip file][zip_format] and common [extra fields][extra_fields].
+ *
+ * Zip Timestamps
+ * --------------
+ *
+ * The base zip format tracks the [last modified timestamp][FileMetadata.lastModifiedAtMillis]. It
+ * does not track [created timestamps][FileMetadata.createdAtMillis] or [last accessed
+ * timestamps][FileMetadata.lastAccessedAtMillis]. This format has limitations:
+ *
+ * * Timestamps are 16-bit values stored with 2-second precision. Some zip encoders (WinZip, PKZIP)
+ * round up to the nearest 2 seconds; other encoders (Java) round down.
+ *
+ * * Timestamps before 1980-01-01 cannot be represented. They cannot represent dates after
+ * 2107-12-31.
+ *
+ * * Timestamps are stored in local time with no time zone offset. If the time zone offset changes
+ * – due to daylight savings time or the zip file being sent to another time zone – file times
+ * will be incorrect. The file time will be shifted by the difference in time zone offsets
+ * between the encoder and decoder.
+ *
+ * The zip format has optional extensions for timestamps.
+ *
+ * * UNIX timestamps (0x000d) support both last-access time and last modification time. These
+ * timestamps are stored with 1-second precision using UTC.
+ *
+ * * NTFS timestamps (0x000a) support creation time, last access time, and last modified time.
+ * These timestamps are stored with 100-millisecond precision using UTC.
+ *
+ * * Extended timestamps (0x5455) are stored as signed 32-bit timestamps with 1-second precision.
+ * These cannot express dates beyond 2038-01-19.
+ *
+ * This class currently supports base timestamps and extended timestamps.
+ *
+ * [zip_format]: https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE_6.2.0.txt
+ * [extra_fields]: https://opensource.apple.com/source/zip/zip-6/unzip/unzip/proginfo/extra.fld
+ */
+internal class ZipFileSystem internal constructor(
+ private val zipPath: Path,
+ private val fileSystem: FileSystem,
+ private val entries: Map<Path, ZipEntry>,
+ private val comment: String?,
+) : FileSystem() {
+ override fun canonicalize(path: Path): Path {
+ val canonical = canonicalizeInternal(path)
+ if (canonical !in entries) {
+ throw FileNotFoundException("$path")
+ }
+ return canonical
+ }
+
+ /** Don't throw [FileNotFoundException] if the path doesn't identify a file. */
+ private fun canonicalizeInternal(path: Path): Path {
+ return ROOT.resolve(path, normalize = true)
+ }
+
+ override fun metadataOrNull(path: Path): FileMetadata? {
+ val canonicalPath = canonicalizeInternal(path)
+ val entry = entries[canonicalPath] ?: return null
+
+ val basicMetadata = FileMetadata(
+ isRegularFile = !entry.isDirectory,
+ isDirectory = entry.isDirectory,
+ symlinkTarget = null,
+ size = if (entry.isDirectory) null else entry.size,
+ createdAtMillis = null,
+ lastModifiedAtMillis = entry.lastModifiedAtMillis,
+ lastAccessedAtMillis = null,
+ )
+
+ if (entry.offset == -1L) {
+ return basicMetadata
+ }
+
+ return fileSystem.openReadOnly(zipPath).use { fileHandle ->
+ return@use fileHandle.source(entry.offset).buffer().use { source ->
+ source.readLocalHeader(basicMetadata)
+ }
+ }
+ }
+
+ override fun openReadOnly(file: Path): FileHandle {
+ throw UnsupportedOperationException("not implemented yet!")
+ }
+
+ override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
+ throw IOException("zip entries are not writable")
+ }
+
+ override fun list(dir: Path): List<Path> = list(dir, throwOnFailure = true)!!
+
+ override fun listOrNull(dir: Path): List<Path>? = list(dir, throwOnFailure = false)
+
+ private fun list(dir: Path, throwOnFailure: Boolean): List<Path>? {
+ val canonicalDir = canonicalizeInternal(dir)
+ val entry = entries[canonicalDir]
+ ?: if (throwOnFailure) throw IOException("not a directory: $dir") else return null
+ return entry.children.toList()
+ }
+
+ @Throws(IOException::class)
+ override fun source(file: Path): Source {
+ val canonicalPath = canonicalizeInternal(file)
+ val entry = entries[canonicalPath] ?: throw FileNotFoundException("no such file: $file")
+ val source = fileSystem.openReadOnly(zipPath).use { fileHandle ->
+ fileHandle.source(entry.offset).buffer()
+ }
+ source.skipLocalHeader()
+
+ return when (entry.compressionMethod) {
+ COMPRESSION_METHOD_STORED -> {
+ FixedLengthSource(source, entry.size, truncate = true)
+ }
+ else -> {
+ val inflaterSource = InflaterSource(
+ FixedLengthSource(source, entry.compressedSize, truncate = true),
+ Inflater(true),
+ )
+ FixedLengthSource(inflaterSource, entry.size, truncate = false)
+ }
+ }
+ }
+
+ override fun sink(file: Path, mustCreate: Boolean): Sink {
+ throw IOException("zip file systems are read-only")
+ }
+
+ override fun appendingSink(file: Path, mustExist: Boolean): Sink {
+ throw IOException("zip file systems are read-only")
+ }
+
+ override fun createDirectory(dir: Path, mustCreate: Boolean): Unit =
+ throw IOException("zip file systems are read-only")
+
+ override fun atomicMove(source: Path, target: Path): Unit =
+ throw IOException("zip file systems are read-only")
+
+ override fun delete(path: Path, mustExist: Boolean): Unit =
+ throw IOException("zip file systems are read-only")
+
+ override fun createSymlink(source: Path, target: Path): Unit =
+ throw IOException("zip file systems are read-only")
+
+ private companion object {
+ val ROOT = "/".toPath()
+ }
+}
diff --git a/okio/src/jvmMain/kotlin/okio/internal/FixedLengthSource.kt b/okio/src/jvmMain/kotlin/okio/internal/FixedLengthSource.kt
new file mode 100644
index 00000000..bc2c7d0b
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/internal/FixedLengthSource.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.internal
+
+import okio.Buffer
+import okio.ForwardingSource
+import okio.IOException
+import okio.Source
+
+/**
+ * A source that returns [size] bytes of [delegate].
+ *
+ * This throws an [IOException] if the delegate returns fewer than [size] bytes.
+ *
+ * If [truncate] is true, this truncates to [size] bytes. Otherwise this requires that [delegate]
+ * will return exactly [size] bytes, and will throw an [IOException] if it doesn't.
+ */
+internal class FixedLengthSource(
+ delegate: Source,
+ private val size: Long,
+ private val truncate: Boolean,
+) : ForwardingSource(delegate) {
+ private var bytesReceived = 0L
+
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ // Figure out how many bytes to attempt to read.
+ //
+ // If we're truncating, we never attempt to read more than what's remaining.
+ //
+ // Otherwise we expect the underlying source to be exactly the promised size. Read as much as
+ // possible and throw an exception if too many bytes are returned.
+ val toRead = when {
+ bytesReceived > size -> 0L // Already read more than the promised size.
+ truncate -> {
+ val remaining = size - bytesReceived
+ if (remaining == 0L) return -1L // Already read exactly the promised size.
+ minOf(byteCount, remaining)
+ }
+ else -> byteCount
+ }
+
+ val result = super.read(sink, toRead)
+
+ if (result != -1L) bytesReceived += result
+
+ // Throw an exception if we received too few bytes or too many.
+ if ((bytesReceived < size && result == -1L) || bytesReceived > size) {
+ if (result > 0L && bytesReceived > size) {
+ // If we received bytes beyond the limit, don't return them to the caller.
+ sink.truncateToSize(sink.size - (bytesReceived - size))
+ }
+ throw IOException("expected $size bytes but got $bytesReceived")
+ }
+
+ return result
+ }
+
+ private fun Buffer.truncateToSize(newSize: Long) {
+ val scratch = Buffer()
+ scratch.writeAll(this)
+ write(scratch, newSize)
+ scratch.clear()
+ }
+}
diff --git a/okio/src/jvmMain/kotlin/okio/internal/ResourceFileSystem.kt b/okio/src/jvmMain/kotlin/okio/internal/ResourceFileSystem.kt
new file mode 100644
index 00000000..efe76801
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/internal/ResourceFileSystem.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.internal
+
+import java.io.File
+import java.io.IOException
+import java.net.URI
+import java.net.URL
+import okio.FileHandle
+import okio.FileMetadata
+import okio.FileNotFoundException
+import okio.FileSystem
+import okio.Path
+import okio.Path.Companion.toOkioPath
+import okio.Path.Companion.toPath
+import okio.Sink
+import okio.Source
+import okio.source
+
+/**
+ * A file system exposing Java classpath resources. It is equivalent to the files returned by
+ * [ClassLoader.getResource] but supports extra features like [metadataOrNull] and [list].
+ *
+ * If `.jar` files overlap, this returns an arbitrary element. For overlapping directories it unions
+ * their contents.
+ *
+ * ResourceFileSystem excludes `.class` files.
+ *
+ * This file system is read-only.
+ */
+internal class ResourceFileSystem internal constructor(
+ private val classLoader: ClassLoader,
+ indexEagerly: Boolean,
+ private val systemFileSystem: FileSystem = SYSTEM,
+) : FileSystem() {
+ private val roots: List<Pair<FileSystem, Path>> by lazy { classLoader.toClasspathRoots() }
+
+ init {
+ if (indexEagerly) {
+ roots.size
+ }
+ }
+
+ override fun canonicalize(path: Path): Path {
+ // TODO(jwilson): throw FileNotFoundException if the canonical file doesn't exist.
+ return canonicalizeInternal(path)
+ }
+
+ /** Don't throw [FileNotFoundException] if the path doesn't identify a file. */
+ private fun canonicalizeInternal(path: Path): Path {
+ return ROOT.resolve(path, normalize = true)
+ }
+
+ override fun list(dir: Path): List<Path> {
+ val relativePath = dir.toRelativePath()
+ val result = mutableSetOf<Path>()
+ var foundAny = false
+ for ((fileSystem, base) in roots) {
+ try {
+ result += fileSystem.list(base / relativePath)
+ .filter { keepPath(it) }
+ .map { it.removeBase(base) }
+ foundAny = true
+ } catch (_: IOException) {
+ }
+ }
+ if (!foundAny) throw FileNotFoundException("file not found: $dir")
+ return result.toList()
+ }
+
+ override fun listOrNull(dir: Path): List<Path>? {
+ val relativePath = dir.toRelativePath()
+ val result = mutableSetOf<Path>()
+ var foundAny = false
+ for ((fileSystem, base) in roots) {
+ val baseResult = fileSystem.listOrNull(base / relativePath)
+ ?.filter { keepPath(it) }
+ ?.map { it.removeBase(base) }
+ if (baseResult != null) {
+ result += baseResult
+ foundAny = true
+ }
+ }
+ return if (foundAny) result.toList() else null
+ }
+
+ override fun openReadOnly(file: Path): FileHandle {
+ if (!keepPath(file)) throw FileNotFoundException("file not found: $file")
+ val relativePath = file.toRelativePath()
+ for ((fileSystem, base) in roots) {
+ try {
+ return fileSystem.openReadOnly(base / relativePath)
+ } catch (_: FileNotFoundException) {
+ }
+ }
+ throw FileNotFoundException("file not found: $file")
+ }
+
+ override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
+ throw IOException("resources are not writable")
+ }
+
+ override fun metadataOrNull(path: Path): FileMetadata? {
+ if (!keepPath(path)) return null
+ val relativePath = path.toRelativePath()
+ for ((fileSystem, base) in roots) {
+ return fileSystem.metadataOrNull(base / relativePath) ?: continue
+ }
+ return null
+ }
+
+ override fun source(file: Path): Source {
+ if (!keepPath(file)) throw FileNotFoundException("file not found: $file")
+ // Make sure we have a path that doesn't start with '/'.
+ val relativePath = ROOT.resolve(file).relativeTo(ROOT)
+ return classLoader.getResourceAsStream(relativePath.toString())?.source()
+ ?: throw FileNotFoundException("file not found: $file")
+ }
+
+ override fun sink(file: Path, mustCreate: Boolean): Sink {
+ throw IOException("$this is read-only")
+ }
+
+ override fun appendingSink(file: Path, mustExist: Boolean): Sink {
+ throw IOException("$this is read-only")
+ }
+
+ override fun createDirectory(dir: Path, mustCreate: Boolean): Unit =
+ throw IOException("$this is read-only")
+
+ override fun atomicMove(source: Path, target: Path): Unit =
+ throw IOException("$this is read-only")
+
+ override fun delete(path: Path, mustExist: Boolean): Unit =
+ throw IOException("$this is read-only")
+
+ override fun createSymlink(source: Path, target: Path): Unit =
+ throw IOException("$this is read-only")
+
+ private fun Path.toRelativePath(): String {
+ val canonicalThis = canonicalizeInternal(this)
+ return canonicalThis.relativeTo(ROOT).toString()
+ }
+
+ /**
+ * Returns a search path of classpath roots. Each element contains a file system to use, and
+ * the base directory of that file system to search from.
+ */
+ private fun ClassLoader.toClasspathRoots(): List<Pair<FileSystem, Path>> {
+ // We'd like to build this upon an API like ClassLoader.getURLs() but unfortunately that
+ // API exists only on URLClassLoader (and that isn't the default class loader implementation).
+ //
+ // The closest we have is `ClassLoader.getResources("")`. It returns all classpath roots that
+ // are directories but none that are .jar files. To mitigate that we also search for all
+ // `META-INF/MANIFEST.MF` files, hastily assuming that every .jar file will have such an
+ // entry.
+ //
+ // Classpath entries that aren't directories and don't have a META-INF/MANIFEST.MF file will
+ // not be visible in this file system.
+ return getResources("").toList().mapNotNull { it.toFileRoot() } +
+ getResources("META-INF/MANIFEST.MF").toList().mapNotNull { it.toJarRoot() }
+ }
+
+ private fun URL.toFileRoot(): Pair<FileSystem, Path>? {
+ if (protocol != "file") return null // Ignore unexpected URLs.
+ return systemFileSystem to File(toURI()).toOkioPath()
+ }
+
+ private fun URL.toJarRoot(): Pair<FileSystem, Path>? {
+ val urlString = toString()
+ if (!urlString.startsWith("jar:file:")) return null // Ignore unexpected URLs.
+
+ // Given a URL like `jar:file:/tmp/foo.jar!/META-INF/MANIFEST.MF`, get the path to the archive
+ // file, like `/tmp/foo.jar`.
+ val suffixStart = urlString.lastIndexOf("!")
+ if (suffixStart == -1) return null
+ val path = File(URI.create(urlString.substring("jar:".length, suffixStart))).toOkioPath()
+ val zip = openZip(
+ zipPath = path,
+ fileSystem = systemFileSystem,
+ predicate = { entry -> keepPath(entry.canonicalPath) },
+ )
+ return zip to ROOT
+ }
+
+ private companion object {
+ val ROOT = "/".toPath()
+
+ fun Path.removeBase(base: Path): Path {
+ val prefix = base.toString()
+ return ROOT / (toString().removePrefix(prefix).replace('\\', '/'))
+ }
+
+ private fun keepPath(path: Path) = !path.name.endsWith(".class", ignoreCase = true)
+ }
+}
diff --git a/okio/src/jvmMain/kotlin/okio/internal/ZipEntry.kt b/okio/src/jvmMain/kotlin/okio/internal/ZipEntry.kt
new file mode 100644
index 00000000..a370aca8
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/internal/ZipEntry.kt
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.internal
+
+import okio.Path
+
+internal class ZipEntry(
+ /**
+ * Absolute path of this entry. If the raw name on disk contains relative paths like `..`, they
+ * are not present in this path.
+ */
+ val canonicalPath: Path,
+
+ /** True if this entry is a directory. When encoded directory entries' names end with `/`. */
+ val isDirectory: Boolean = false,
+
+ /** The comment on this entry. Empty if there is no comment. */
+ val comment: String = "",
+
+ /** The CRC32 of the uncompressed data, or -1 if not set. */
+ val crc: Long = -1L,
+
+ /** The compressed size in bytes, or -1 if unknown. */
+ val compressedSize: Long = -1L,
+
+ /** The uncompressed size in bytes, or -1 if unknown. */
+ val size: Long = -1L,
+
+ /** Either [COMPRESSION_METHOD_DEFLATED] or [COMPRESSION_METHOD_STORED]. */
+ val compressionMethod: Int = -1,
+
+ val lastModifiedAtMillis: Long? = null,
+
+ val offset: Long = -1L,
+) {
+ val children = mutableListOf<Path>()
+}
diff --git a/okio/src/jvmMain/kotlin/okio/internal/ZipFiles.kt b/okio/src/jvmMain/kotlin/okio/internal/ZipFiles.kt
new file mode 100644
index 00000000..02b6a848
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/internal/ZipFiles.kt
@@ -0,0 +1,458 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.internal
+
+import java.util.Calendar
+import java.util.GregorianCalendar
+import okio.BufferedSource
+import okio.FileMetadata
+import okio.FileSystem
+import okio.IOException
+import okio.Path
+import okio.Path.Companion.toPath
+import okio.ZipFileSystem
+import okio.buffer
+
+private const val LOCAL_FILE_HEADER_SIGNATURE = 0x4034b50
+private const val CENTRAL_FILE_HEADER_SIGNATURE = 0x2014b50
+private const val END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x6054b50
+private const val ZIP64_LOCATOR_SIGNATURE = 0x07064b50
+private const val ZIP64_EOCD_RECORD_SIGNATURE = 0x06064b50
+
+internal const val COMPRESSION_METHOD_DEFLATED = 8
+internal const val COMPRESSION_METHOD_STORED = 0
+
+/** General Purpose Bit Flags, Bit 0. Set if the file is encrypted. */
+private const val BIT_FLAG_ENCRYPTED = 1 shl 0
+
+/**
+ * General purpose bit flags that this implementation handles. Strict enforcement of additional
+ * flags may break legitimate use cases.
+ */
+private const val BIT_FLAG_UNSUPPORTED_MASK = BIT_FLAG_ENCRYPTED
+
+/** Max size of entries and archives without zip64. */
+private const val MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE = 0xffffffffL
+
+private const val HEADER_ID_ZIP64_EXTENDED_INFO = 0x1
+private const val HEADER_ID_EXTENDED_TIMESTAMP = 0x5455
+
+/**
+ * Opens the file at [zipPath] for use as a file system. This uses UTF-8 to comments and names in
+ * the zip file.
+ *
+ * @param predicate a function that returns false for entries that should be omitted from the file
+ * system.
+ */
+@Throws(IOException::class)
+internal fun openZip(
+ zipPath: Path,
+ fileSystem: FileSystem,
+ predicate: (ZipEntry) -> Boolean = { true },
+): ZipFileSystem {
+ fileSystem.openReadOnly(zipPath).use { fileHandle ->
+ // Scan backwards from the end of the file looking for the END_OF_CENTRAL_DIRECTORY_SIGNATURE.
+ // If this file has no comment we'll see it on the first attempt; otherwise we have to go
+ // backwards byte-by-byte until we reach it. (The number of bytes scanned will equal the comment
+ // size).
+ var scanOffset = fileHandle.size() - 22 // end of central directory record size is 22 bytes.
+ if (scanOffset < 0L) {
+ throw IOException("not a zip: size=${fileHandle.size()}")
+ }
+ val stopOffset = maxOf(scanOffset - 65_536L, 0L)
+ val eocdOffset: Long
+ var record: EocdRecord
+ val comment: String
+ while (true) {
+ val source = fileHandle.source(scanOffset).buffer()
+ try {
+ if (source.readIntLe() == END_OF_CENTRAL_DIRECTORY_SIGNATURE) {
+ eocdOffset = scanOffset
+ record = source.readEocdRecord()
+ comment = source.readUtf8(record.commentByteCount.toLong())
+ break
+ }
+ } finally {
+ source.close()
+ }
+
+ scanOffset--
+ if (scanOffset < stopOffset) {
+ throw IOException("not a zip: end of central directory signature not found")
+ }
+ }
+
+ // If this is a zip64, read a zip64 central directory record.
+ val zip64LocatorOffset = eocdOffset - 20 // zip64 end of central directory locator is 20 bytes.
+ if (zip64LocatorOffset > 0L) {
+ fileHandle.source(zip64LocatorOffset).buffer().use { zip64LocatorSource ->
+ if (zip64LocatorSource.readIntLe() == ZIP64_LOCATOR_SIGNATURE) {
+ val diskWithCentralDir = zip64LocatorSource.readIntLe()
+ val zip64EocdRecordOffset = zip64LocatorSource.readLongLe()
+ val numDisks = zip64LocatorSource.readIntLe()
+ if (numDisks != 1 || diskWithCentralDir != 0) {
+ throw IOException("unsupported zip: spanned")
+ }
+ fileHandle.source(zip64EocdRecordOffset).buffer().use { zip64EocdSource ->
+ val zip64EocdSignature = zip64EocdSource.readIntLe()
+ if (zip64EocdSignature != ZIP64_EOCD_RECORD_SIGNATURE) {
+ throw IOException(
+ "bad zip: expected ${ZIP64_EOCD_RECORD_SIGNATURE.hex} " +
+ "but was ${zip64EocdSignature.hex}",
+ )
+ }
+ record = zip64EocdSource.readZip64EocdRecord(record)
+ }
+ }
+ }
+ }
+
+ // Seek to the first central directory entry and read all of the entries.
+ val entries = mutableListOf<ZipEntry>()
+ fileHandle.source(record.centralDirectoryOffset).buffer().use { source ->
+ for (i in 0 until record.entryCount) {
+ val entry = source.readEntry()
+ if (entry.offset >= record.centralDirectoryOffset) {
+ throw IOException("bad zip: local file header offset >= central directory offset")
+ }
+ if (predicate(entry)) {
+ entries += entry
+ }
+ }
+ }
+
+ // Organize the entries into a tree.
+ val index = buildIndex(entries)
+
+ return ZipFileSystem(zipPath, fileSystem, index, comment)
+ }
+}
+
+/**
+ * Returns a map containing all of [entries], plus parent entries required so that all entries
+ * (other than the file system root `/`) have a parent.
+ */
+private fun buildIndex(entries: List<ZipEntry>): Map<Path, ZipEntry> {
+ val root = "/".toPath()
+ val result = mutableMapOf(
+ root to ZipEntry(canonicalPath = root, isDirectory = true),
+ )
+
+ // Iterate in sorted order so each path is preceded by its parent.
+ for (entry in entries.sortedBy { it.canonicalPath }) {
+ // Note that this may clobber an existing element in the map. For consistency with java.util.zip
+ // and java.nio.file.FileSystem, this prefers the last-encountered element.
+ val replaced = result.put(entry.canonicalPath, entry)
+ if (replaced != null) continue
+
+ // Make sure this parent directories exist all the way up to the file system root.
+ var child = entry
+ while (true) {
+ val parentPath = child.canonicalPath.parent ?: break // child is '/'.
+ var parentEntry = result[parentPath]
+
+ // We've found a parent that already exists! Add the child; we're done.
+ if (parentEntry != null) {
+ parentEntry.children += child.canonicalPath
+ break
+ }
+
+ // A parent is missing! Synthesize one.
+ parentEntry = ZipEntry(
+ canonicalPath = parentPath,
+ isDirectory = true,
+ )
+ result[parentPath] = parentEntry
+ parentEntry.children += child.canonicalPath
+ child = parentEntry
+ }
+ }
+
+ return result
+}
+
+/** When this returns, [this] will be positioned at the start of the next entry. */
+@Throws(IOException::class)
+internal fun BufferedSource.readEntry(): ZipEntry {
+ val signature = readIntLe()
+ if (signature != CENTRAL_FILE_HEADER_SIGNATURE) {
+ throw IOException(
+ "bad zip: expected ${CENTRAL_FILE_HEADER_SIGNATURE.hex} but was ${signature.hex}",
+ )
+ }
+
+ skip(4) // version made by (2) + version to extract (2).
+ val bitFlag = readShortLe().toInt() and 0xffff
+ if (bitFlag and BIT_FLAG_UNSUPPORTED_MASK != 0) {
+ throw IOException("unsupported zip: general purpose bit flag=${bitFlag.hex}")
+ }
+
+ val compressionMethod = readShortLe().toInt() and 0xffff
+ val time = readShortLe().toInt() and 0xffff
+ val date = readShortLe().toInt() and 0xffff
+ // TODO(jwilson): decode NTFS and UNIX extra metadata to return better timestamps.
+ val lastModifiedAtMillis = dosDateTimeToEpochMillis(date, time)
+
+ // These are 32-bit values in the file, but 64-bit fields in this object.
+ val crc = readIntLe().toLong() and 0xffffffffL
+ var compressedSize = readIntLe().toLong() and 0xffffffffL
+ var size = readIntLe().toLong() and 0xffffffffL
+ val nameSize = readShortLe().toInt() and 0xffff
+ val extraSize = readShortLe().toInt() and 0xffff
+ val commentByteCount = readShortLe().toInt() and 0xffff
+
+ skip(8) // disk number start (2) + internal file attributes (2) + external file attributes (4).
+ var offset = readIntLe().toLong() and 0xffffffffL
+ val name = readUtf8(nameSize.toLong())
+ if ('\u0000' in name) throw IOException("bad zip: filename contains 0x00")
+
+ val requiredZip64ExtraSize = run {
+ var result = 0L
+ if (size == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) result += 8
+ if (compressedSize == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) result += 8
+ if (offset == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) result += 8
+ return@run result
+ }
+
+ var hasZip64Extra = false
+ readExtra(extraSize) { headerId, dataSize ->
+ when (headerId) {
+ HEADER_ID_ZIP64_EXTENDED_INFO -> {
+ if (hasZip64Extra) {
+ throw IOException("bad zip: zip64 extra repeated")
+ }
+ hasZip64Extra = true
+
+ if (dataSize < requiredZip64ExtraSize) {
+ throw IOException("bad zip: zip64 extra too short")
+ }
+
+ // Read each field if it has a sentinel value in the regular header.
+ size = if (size == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) readLongLe() else size
+ compressedSize = if (compressedSize == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) readLongLe() else 0L
+ offset = if (offset == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) readLongLe() else 0L
+ }
+ }
+ }
+
+ if (requiredZip64ExtraSize > 0L && !hasZip64Extra) {
+ throw IOException("bad zip: zip64 extra required but absent")
+ }
+
+ val comment = readUtf8(commentByteCount.toLong())
+ val canonicalPath = "/".toPath() / name
+ val isDirectory = name.endsWith("/")
+
+ return ZipEntry(
+ canonicalPath = canonicalPath,
+ isDirectory = isDirectory,
+ comment = comment,
+ crc = crc,
+ compressedSize = compressedSize,
+ size = size,
+ compressionMethod = compressionMethod,
+ lastModifiedAtMillis = lastModifiedAtMillis,
+ offset = offset,
+ )
+}
+
+@Throws(IOException::class)
+private fun BufferedSource.readEocdRecord(): EocdRecord {
+ val diskNumber = readShortLe().toInt() and 0xffff
+ val diskWithCentralDir = readShortLe().toInt() and 0xffff
+ val entryCount = (readShortLe().toInt() and 0xffff).toLong()
+ val totalEntryCount = (readShortLe().toInt() and 0xffff).toLong()
+ if (entryCount != totalEntryCount || diskNumber != 0 || diskWithCentralDir != 0) {
+ throw IOException("unsupported zip: spanned")
+ }
+ skip(4) // central directory size.
+ val centralDirectoryOffset = readIntLe().toLong() and 0xffffffffL
+ val commentByteCount = readShortLe().toInt() and 0xffff
+
+ return EocdRecord(
+ entryCount = entryCount,
+ centralDirectoryOffset = centralDirectoryOffset,
+ commentByteCount = commentByteCount,
+ )
+}
+
+@Throws(IOException::class)
+private fun BufferedSource.readZip64EocdRecord(regularRecord: EocdRecord): EocdRecord {
+ skip(12) // size of central directory record (8) + version made by (2) + version to extract (2).
+ val diskNumber = readIntLe()
+ val diskWithCentralDirStart = readIntLe()
+ val entryCount = readLongLe()
+ val totalEntryCount = readLongLe()
+ if (entryCount != totalEntryCount || diskNumber != 0 || diskWithCentralDirStart != 0) {
+ throw IOException("unsupported zip: spanned")
+ }
+ skip(8) // central directory size.
+ val centralDirectoryOffset = readLongLe()
+
+ return EocdRecord(
+ entryCount = entryCount,
+ centralDirectoryOffset = centralDirectoryOffset,
+ commentByteCount = regularRecord.commentByteCount,
+ )
+}
+
+/**
+ * Read a sequence of 0 or more extra fields. Each field has this structure:
+ *
+ * * 2-byte header ID
+ * * 2-byte data size
+ * * variable-byte data value
+ *
+ * This reads each extra field and calls [block] for each. The parameters are the header ID and
+ * data size. It is an error for [block] to process more bytes than the data size.
+ */
+private fun BufferedSource.readExtra(extraSize: Int, block: (Int, Long) -> Unit) {
+ var remaining = extraSize.toLong()
+ while (remaining != 0L) {
+ if (remaining < 4) {
+ throw IOException("bad zip: truncated header in extra field")
+ }
+ val headerId = readShortLe().toInt() and 0xffff
+ val dataSize = readShortLe().toLong() and 0xffff
+ remaining -= 4
+ if (remaining < dataSize) {
+ throw IOException("bad zip: truncated value in extra field")
+ }
+ require(dataSize)
+ val sizeBefore = buffer.size
+ block(headerId, dataSize)
+ val fieldRemaining = dataSize + buffer.size - sizeBefore
+ when {
+ fieldRemaining < 0 -> {
+ throw IOException("unsupported zip: too many bytes processed for $headerId")
+ }
+ fieldRemaining > 0 -> {
+ buffer.skip(fieldRemaining)
+ }
+ }
+ remaining -= dataSize
+ }
+}
+
+internal fun BufferedSource.skipLocalHeader() {
+ readOrSkipLocalHeader(null)
+}
+
+internal fun BufferedSource.readLocalHeader(basicMetadata: FileMetadata): FileMetadata {
+ return readOrSkipLocalHeader(basicMetadata)!!
+}
+
+/**
+ * If [basicMetadata] is null this will return null. Otherwise it will return a new header which
+ * updates [basicMetadata] with information from the local header.
+ */
+private fun BufferedSource.readOrSkipLocalHeader(basicMetadata: FileMetadata?): FileMetadata? {
+ var lastModifiedAtMillis = basicMetadata?.lastModifiedAtMillis
+ var lastAccessedAtMillis: Long? = null
+ var createdAtMillis: Long? = null
+
+ val signature = readIntLe()
+ if (signature != LOCAL_FILE_HEADER_SIGNATURE) {
+ throw IOException(
+ "bad zip: expected ${LOCAL_FILE_HEADER_SIGNATURE.hex} but was ${signature.hex}",
+ )
+ }
+ skip(2) // version to extract.
+ val bitFlag = readShortLe().toInt() and 0xffff
+ if (bitFlag and BIT_FLAG_UNSUPPORTED_MASK != 0) {
+ throw IOException("unsupported zip: general purpose bit flag=${bitFlag.hex}")
+ }
+ skip(18) // compression method (2) + time+date (4) + crc32 (4) + compressed size (4) + size (4).
+ val fileNameLength = readShortLe().toLong() and 0xffff
+ val extraSize = readShortLe().toInt() and 0xffff
+ skip(fileNameLength)
+
+ if (basicMetadata == null) {
+ skip(extraSize.toLong())
+ return null
+ }
+
+ readExtra(extraSize) { headerId, dataSize ->
+ when (headerId) {
+ HEADER_ID_EXTENDED_TIMESTAMP -> {
+ if (dataSize < 1) {
+ throw IOException("bad zip: extended timestamp extra too short")
+ }
+ val flags = readByte().toInt() and 0xff
+
+ val hasLastModifiedAtMillis = (flags and 0x1) == 0x1
+ val hasLastAccessedAtMillis = (flags and 0x2) == 0x2
+ val hasCreatedAtMillis = (flags and 0x4) == 0x4
+ val requiredSize = run {
+ var result = 1L
+ if (hasLastModifiedAtMillis) result += 4L
+ if (hasLastAccessedAtMillis) result += 4L
+ if (hasCreatedAtMillis) result += 4L
+ return@run result
+ }
+ if (dataSize < requiredSize) {
+ throw IOException("bad zip: extended timestamp extra too short")
+ }
+
+ if (hasLastModifiedAtMillis) lastModifiedAtMillis = readIntLe() * 1000L
+ if (hasLastAccessedAtMillis) lastAccessedAtMillis = readIntLe() * 1000L
+ if (hasCreatedAtMillis) createdAtMillis = readIntLe() * 1000L
+ }
+ }
+ }
+
+ return FileMetadata(
+ isRegularFile = basicMetadata.isRegularFile,
+ isDirectory = basicMetadata.isDirectory,
+ symlinkTarget = null,
+ size = basicMetadata.size,
+ createdAtMillis = createdAtMillis,
+ lastModifiedAtMillis = lastModifiedAtMillis,
+ lastAccessedAtMillis = lastAccessedAtMillis,
+ )
+}
+
+/**
+ * Converts a 32-bit DOS date+time to milliseconds since epoch. Note that this function interprets
+ * a value with no time zone as a value with the local time zone.
+ */
+private fun dosDateTimeToEpochMillis(date: Int, time: Int): Long? {
+ if (time == -1) {
+ return null
+ }
+
+ // Note that this inherits the local time zone.
+ val cal = GregorianCalendar()
+ cal.set(Calendar.MILLISECOND, 0)
+ val year = 1980 + (date shr 9 and 0x7f)
+ val month = date shr 5 and 0xf
+ val day = date and 0x1f
+ val hour = time shr 11 and 0x1f
+ val minute = time shr 5 and 0x3f
+ val second = time and 0x1f shl 1
+ cal.set(year, month - 1, day, hour, minute, second)
+ return cal.time.time
+}
+
+private class EocdRecord(
+ val entryCount: Long,
+ val centralDirectoryOffset: Long,
+ val commentByteCount: Int,
+)
+
+private val Int.hex: String
+ get() = "0x${this.toString(16)}"
diff --git a/okio/src/jvmMain/resources/META-INF/proguard/okio.pro b/okio/src/jvmMain/resources/META-INF/proguard/okio.pro
deleted file mode 100644
index 2b698343..00000000
--- a/okio/src/jvmMain/resources/META-INF/proguard/okio.pro
+++ /dev/null
@@ -1,2 +0,0 @@
-# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
--dontwarn org.codehaus.mojo.animal_sniffer.*
diff --git a/okio/src/jvmTest/hashFunctions b/okio/src/jvmTest/hashFunctions
new file mode 120000
index 00000000..1634c79e
--- /dev/null
+++ b/okio/src/jvmTest/hashFunctions
@@ -0,0 +1 @@
+../hashFunctions/kotlin \ No newline at end of file
diff --git a/okio/src/jvmTest/java/okio/AsyncTimeoutTest.java b/okio/src/jvmTest/java/okio/AsyncTimeoutTest.java
deleted file mode 100644
index 974218b1..00000000
--- a/okio/src/jvmTest/java/okio/AsyncTimeoutTest.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Random;
-import java.util.concurrent.BlockingDeque;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.TimeUnit;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static okio.TestUtil.bufferWithRandomSegmentLayout;
-import static org.junit.Assert.*;
-
-/**
- * This test uses four timeouts of varying durations: 250ms, 500ms, 750ms and
- * 1000ms, named 'a', 'b', 'c' and 'd'.
- */
-public final class AsyncTimeoutTest {
- private final BlockingDeque<AsyncTimeout> timedOut = new LinkedBlockingDeque<>();
- private final AsyncTimeout a = new RecordingAsyncTimeout();
- private final AsyncTimeout b = new RecordingAsyncTimeout();
- private final AsyncTimeout c = new RecordingAsyncTimeout();
- private final AsyncTimeout d = new RecordingAsyncTimeout();
-
- @Before public void setUp() throws Exception {
- a.timeout( 250, TimeUnit.MILLISECONDS);
- b.timeout( 500, TimeUnit.MILLISECONDS);
- c.timeout( 750, TimeUnit.MILLISECONDS);
- d.timeout(1000, TimeUnit.MILLISECONDS);
- }
-
- @Test public void zeroTimeoutIsNoTimeout() throws Exception {
- AsyncTimeout timeout = new RecordingAsyncTimeout();
- timeout.timeout(0, TimeUnit.MILLISECONDS);
- timeout.enter();
- Thread.sleep(250);
- assertFalse(timeout.exit());
- assertTimedOut();
- }
-
- @Test public void singleInstanceTimedOut() throws Exception {
- a.enter();
- Thread.sleep(500);
- assertTrue(a.exit());
- assertTimedOut(a);
- }
-
- @Test public void singleInstanceNotTimedOut() throws Exception {
- b.enter();
- Thread.sleep(250);
- b.exit();
- assertFalse(b.exit());
- assertTimedOut();
- }
-
- @Test public void instancesAddedAtEnd() throws Exception {
- a.enter();
- b.enter();
- c.enter();
- d.enter();
- Thread.sleep(1250);
- assertTrue(a.exit());
- assertTrue(b.exit());
- assertTrue(c.exit());
- assertTrue(d.exit());
- assertTimedOut(a, b, c, d);
- }
-
- @Test public void instancesAddedAtFront() throws Exception {
- d.enter();
- c.enter();
- b.enter();
- a.enter();
- Thread.sleep(1250);
- assertTrue(d.exit());
- assertTrue(c.exit());
- assertTrue(b.exit());
- assertTrue(a.exit());
- assertTimedOut(a, b, c, d);
- }
-
- @Test public void instancesRemovedAtFront() throws Exception {
- a.enter();
- b.enter();
- c.enter();
- d.enter();
- assertFalse(a.exit());
- assertFalse(b.exit());
- assertFalse(c.exit());
- assertFalse(d.exit());
- assertTimedOut();
- }
-
- @Test public void instancesRemovedAtEnd() throws Exception {
- a.enter();
- b.enter();
- c.enter();
- d.enter();
- assertFalse(d.exit());
- assertFalse(c.exit());
- assertFalse(b.exit());
- assertFalse(a.exit());
- assertTimedOut();
- }
-
- @Test public void doubleEnter() throws Exception {
- a.enter();
- try {
- a.enter();
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- @Test public void reEnter() throws Exception {
- a.timeout(10, SECONDS);
- a.enter();
- assertFalse(a.exit());
- a.enter();
- assertFalse(a.exit());
- }
-
- @Test public void reEnterAfterTimeout() throws Exception {
- a.timeout(1, MILLISECONDS);
- a.enter();
- assertSame(a, timedOut.take());
- assertTrue(a.exit());
- a.enter();
- assertFalse(a.exit());
- }
-
- @Test public void deadlineOnly() throws Exception {
- RecordingAsyncTimeout timeout = new RecordingAsyncTimeout();
- timeout.deadline(250, TimeUnit.MILLISECONDS);
- timeout.enter();
- Thread.sleep(500);
- assertTrue(timeout.exit());
- assertTimedOut(timeout);
- }
-
- @Test public void deadlineBeforeTimeout() throws Exception {
- RecordingAsyncTimeout timeout = new RecordingAsyncTimeout();
- timeout.deadline(250, TimeUnit.MILLISECONDS);
- timeout.timeout(750, TimeUnit.MILLISECONDS);
- timeout.enter();
- Thread.sleep(500);
- assertTrue(timeout.exit());
- assertTimedOut(timeout);
- }
-
- @Test public void deadlineAfterTimeout() throws Exception {
- RecordingAsyncTimeout timeout = new RecordingAsyncTimeout();
- timeout.timeout(250, TimeUnit.MILLISECONDS);
- timeout.deadline(750, TimeUnit.MILLISECONDS);
- timeout.enter();
- Thread.sleep(500);
- assertTrue(timeout.exit());
- assertTimedOut(timeout);
- }
-
- @Test public void deadlineStartsBeforeEnter() throws Exception {
- RecordingAsyncTimeout timeout = new RecordingAsyncTimeout();
- timeout.deadline(500, TimeUnit.MILLISECONDS);
- Thread.sleep(500);
- timeout.enter();
- Thread.sleep(250);
- assertTrue(timeout.exit());
- assertTimedOut(timeout);
- }
-
- @Test public void deadlineInThePast() throws Exception {
- RecordingAsyncTimeout timeout = new RecordingAsyncTimeout();
- timeout.deadlineNanoTime(System.nanoTime() - 1);
- timeout.enter();
- Thread.sleep(250);
- assertTrue(timeout.exit());
- assertTimedOut(timeout);
- }
-
- @Test public void wrappedSinkTimesOut() throws Exception {
- Sink sink = new ForwardingSink(new Buffer()) {
- @Override public void write(Buffer source, long byteCount) throws IOException {
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- throw new AssertionError();
- }
- }
- };
- AsyncTimeout timeout = new AsyncTimeout();
- timeout.timeout(250, TimeUnit.MILLISECONDS);
- Sink timeoutSink = timeout.sink(sink);
- Buffer data = new Buffer().writeUtf8("a");
- try {
- timeoutSink.write(data, 1);
- fail();
- } catch (InterruptedIOException expected) {
- }
- }
-
- @Test public void wrappedSinkFlushTimesOut() throws Exception {
- Sink sink = new ForwardingSink(new Buffer()) {
- @Override public void flush() throws IOException {
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- throw new AssertionError();
- }
- }
- };
- AsyncTimeout timeout = new AsyncTimeout();
- timeout.timeout(250, TimeUnit.MILLISECONDS);
- Sink timeoutSink = timeout.sink(sink);
- try {
- timeoutSink.flush();
- fail();
- } catch (InterruptedIOException expected) {
- }
- }
-
- @Test public void wrappedSinkCloseTimesOut() throws Exception {
- Sink sink = new ForwardingSink(new Buffer()) {
- @Override public void close() throws IOException {
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- throw new AssertionError();
- }
- }
- };
- AsyncTimeout timeout = new AsyncTimeout();
- timeout.timeout(250, TimeUnit.MILLISECONDS);
- Sink timeoutSink = timeout.sink(sink);
- try {
- timeoutSink.close();
- fail();
- } catch (InterruptedIOException expected) {
- }
- }
-
- @Test public void wrappedSourceTimesOut() throws Exception {
- Source source = new ForwardingSource(new Buffer()) {
- @Override public long read(Buffer sink, long byteCount) throws IOException {
- try {
- Thread.sleep(500);
- return -1;
- } catch (InterruptedException e) {
- throw new AssertionError();
- }
- }
- };
- AsyncTimeout timeout = new AsyncTimeout();
- timeout.timeout(250, TimeUnit.MILLISECONDS);
- Source timeoutSource = timeout.source(source);
- try {
- timeoutSource.read(new Buffer(), 0);
- fail();
- } catch (InterruptedIOException expected) {
- }
- }
-
- @Test public void wrappedSourceCloseTimesOut() throws Exception {
- Source source = new ForwardingSource(new Buffer()) {
- @Override public void close() throws IOException {
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- throw new AssertionError();
- }
- }
- };
- AsyncTimeout timeout = new AsyncTimeout();
- timeout.timeout(250, TimeUnit.MILLISECONDS);
- Source timeoutSource = timeout.source(source);
- try {
- timeoutSource.close();
- fail();
- } catch (InterruptedIOException expected) {
- }
- }
-
- @Test public void wrappedThrowsWithTimeout() throws Exception {
- Sink sink = new ForwardingSink(new Buffer()) {
- @Override public void write(Buffer source, long byteCount) throws IOException {
- try {
- Thread.sleep(500);
- throw new IOException("exception and timeout");
- } catch (InterruptedException e) {
- throw new AssertionError();
- }
- }
- };
- AsyncTimeout timeout = new AsyncTimeout();
- timeout.timeout(250, TimeUnit.MILLISECONDS);
- Sink timeoutSink = timeout.sink(sink);
- Buffer data = new Buffer().writeUtf8("a");
- try {
- timeoutSink.write(data, 1);
- fail();
- } catch (InterruptedIOException expected) {
- assertEquals("timeout", expected.getMessage());
- assertEquals("exception and timeout", expected.getCause().getMessage());
- }
- }
-
- @Test public void wrappedThrowsWithoutTimeout() throws Exception {
- Sink sink = new ForwardingSink(new Buffer()) {
- @Override public void write(Buffer source, long byteCount) throws IOException {
- throw new IOException("no timeout occurred");
- }
- };
- AsyncTimeout timeout = new AsyncTimeout();
- timeout.timeout(250, TimeUnit.MILLISECONDS);
- Sink timeoutSink = timeout.sink(sink);
- Buffer data = new Buffer().writeUtf8("a");
- try {
- timeoutSink.write(data, 1);
- fail();
- } catch (IOException expected) {
- assertEquals("no timeout occurred", expected.getMessage());
- }
- }
-
- /**
- * We had a bug where writing a very large buffer would fail with an
- * unexpected timeout because although the sink was making steady forward
- * progress, doing it all as a single write caused a timeout.
- */
- @Ignore("Flaky")
- @Test public void sinkSplitsLargeWrites() throws Exception {
- byte[] data = new byte[512 * 1024];
- Random dice = new Random(0);
- dice.nextBytes(data);
- final Buffer source = bufferWithRandomSegmentLayout(dice, data);
- final Buffer target = new Buffer();
-
- Sink sink = new ForwardingSink(new Buffer()) {
- @Override public void write(Buffer source, long byteCount) throws IOException {
- try {
- Thread.sleep(byteCount / 500); // ~500 KiB/s.
- target.write(source, byteCount);
- } catch (InterruptedException e) {
- throw new AssertionError();
- }
- }
- };
-
- // Timeout after 250 ms of inactivity.
- AsyncTimeout timeout = new AsyncTimeout();
- timeout.timeout(250, TimeUnit.MILLISECONDS);
- Sink timeoutSink = timeout.sink(sink);
-
- // Transmit 500 KiB of data, which should take ~1 second. But expect no timeout!
- timeoutSink.write(source, source.size());
-
- // The data should all have arrived.
- assertEquals(ByteString.of(data), target.readByteString());
- }
-
- /** Asserts which timeouts fired, and in which order. */
- private void assertTimedOut(Timeout... expected) {
- assertEquals(Arrays.asList(expected), new ArrayList<Timeout>(timedOut));
- }
-
- class RecordingAsyncTimeout extends AsyncTimeout {
- @Override protected void timedOut() {
- timedOut.add(this);
- }
- }
-}
diff --git a/okio/src/jvmTest/java/okio/BufferCursorTest.java b/okio/src/jvmTest/java/okio/BufferCursorTest.java
deleted file mode 100644
index 4cad858a..00000000
--- a/okio/src/jvmTest/java/okio/BufferCursorTest.java
+++ /dev/null
@@ -1,468 +0,0 @@
-/*
- * Copyright (C) 2018 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import static kotlin.text.StringsKt.repeat;
-import static okio.Buffer.UnsafeCursor;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static okio.TestUtil.deepCopy;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-@RunWith(Parameterized.class)
-public final class BufferCursorTest {
-
- @Parameters(name = "{0}")
- public static List<Object[]> parameters() throws Exception {
- List<Object[]> result = new ArrayList<>();
- for (BufferFactory bufferFactory : BufferFactory.values()) {
- result.add(new Object[] {bufferFactory});
- }
- return result;
- }
-
- @Parameter public BufferFactory bufferFactory;
-
- @Test public void apiExample() throws Exception {
- Buffer buffer = new Buffer();
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- cursor.resizeBuffer(1000_000);
-
- do {
- Arrays.fill(cursor.data, cursor.start, cursor.end, (byte) 'x');
- } while (cursor.next() != -1);
-
- cursor.seek(3);
- cursor.data[cursor.start] = 'o';
-
- cursor.seek(1);
- cursor.data[cursor.start] = 'o';
-
- cursor.resizeBuffer(4);
- }
-
- assertEquals(new Buffer().writeUtf8("xoxo"), buffer);
- }
-
- @Test public void accessSegmentBySegment() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- try (UnsafeCursor cursor = buffer.readUnsafe()) {
- Buffer actual = new Buffer();
- while (cursor.next() != -1L) {
- actual.write(cursor.data, cursor.start, cursor.end - cursor.start);
- }
- assertEquals(buffer, actual);
- }
- }
-
- @Test public void seekToNegativeOneSeeksBeforeFirstSegment() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- try (UnsafeCursor cursor = buffer.readUnsafe()) {
- cursor.seek(-1L);
- assertEquals(-1, cursor.offset);
- assertEquals(null, cursor.data);
- assertEquals(-1, cursor.start);
- assertEquals(-1, cursor.end);
-
- cursor.next();
- assertEquals(0, cursor.offset);
- }
- }
-
- @Test public void accessByteByByte() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- try (UnsafeCursor cursor = buffer.readUnsafe()) {
- byte[] actual = new byte[(int) buffer.size()];
- for (int i = 0; i < buffer.size(); i++) {
- cursor.seek(i);
- actual[i] = cursor.data[cursor.start];
- }
- assertEquals(ByteString.of(actual), buffer.snapshot());
- }
- }
-
- @Test public void accessByteByByteReverse() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- try (UnsafeCursor cursor = buffer.readUnsafe()) {
- byte[] actual = new byte[(int) buffer.size()];
- for (int i = (int) (buffer.size() - 1); i >= 0; i--) {
- cursor.seek(i);
- actual[i] = cursor.data[cursor.start];
- }
- assertEquals(ByteString.of(actual), buffer.snapshot());
- }
- }
-
- @Test public void accessByteByByteAlwaysResettingToZero() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- try (UnsafeCursor cursor = buffer.readUnsafe()) {
- byte[] actual = new byte[(int) buffer.size()];
- for (int i = 0; i < buffer.size(); i++) {
- cursor.seek(i);
- actual[i] = cursor.data[cursor.start];
- cursor.seek(0L);
- }
- assertEquals(ByteString.of(actual), buffer.snapshot());
- }
- }
-
- @Test public void segmentBySegmentNavigation() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- UnsafeCursor cursor = buffer.readUnsafe();
- assertEquals(-1, cursor.offset);
- try {
- long lastOffset = cursor.offset;
- while (cursor.next() != -1L) {
- assertTrue(cursor.offset > lastOffset);
- lastOffset = cursor.offset;
- }
- assertEquals(buffer.size(), cursor.offset);
- assertNull(cursor.data);
- assertEquals(-1, cursor.start);
- assertEquals(-1, cursor.end);
- } finally {
- cursor.close();
- }
- }
-
- @Test public void seekWithinSegment() throws Exception {
- assumeTrue(bufferFactory == BufferFactory.SMALL_SEGMENTED_BUFFER);
- Buffer buffer = bufferFactory.newBuffer();
- assertEquals("abcdefghijkl", buffer.clone().readUtf8());
-
- // Seek to the 'f' in the "defg" segment.
- try (UnsafeCursor cursor = buffer.readUnsafe()) {
- assertEquals(2, cursor.seek(5)); // 2 for 2 bytes left in the segment: "fg".
- assertEquals(5, cursor.offset);
- assertEquals(2, cursor.end - cursor.start);
- assertEquals('d', (char) cursor.data[cursor.start - 2]); // Out of bounds!
- assertEquals('e', (char) cursor.data[cursor.start - 1]); // Out of bounds!
- assertEquals('f', (char) cursor.data[cursor.start]);
- assertEquals('g', (char) cursor.data[cursor.start + 1]);
- }
- }
-
- @Test public void acquireAndRelease() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- UnsafeCursor cursor = new UnsafeCursor();
-
- // Nothing initialized before acquire.
- assertEquals(-1, cursor.offset);
- assertNull(cursor.data);
- assertEquals(-1, cursor.start);
- assertEquals(-1, cursor.end);
-
- buffer.readUnsafe(cursor);
- cursor.close();
-
- // Nothing initialized after close.
- assertEquals(-1, cursor.offset);
- assertNull(cursor.data);
- assertEquals(-1, cursor.start);
- assertEquals(-1, cursor.end);
- }
-
- @Test public void doubleAcquire() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- try (UnsafeCursor cursor = buffer.readUnsafe()) {
- buffer.readUnsafe(cursor);
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- @Test public void releaseWithoutAcquire() throws Exception {
- UnsafeCursor cursor = new UnsafeCursor();
- try {
- cursor.close();
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- @Test public void releaseAfterRelease() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- UnsafeCursor cursor = buffer.readUnsafe();
- cursor.close();
- try {
- cursor.close();
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- @Test public void enlarge() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- long originalSize = buffer.size();
-
- Buffer expected = deepCopy(buffer);
- expected.writeUtf8("abc");
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- assertEquals(originalSize, cursor.resizeBuffer(originalSize + 3));
- cursor.seek(originalSize);
- cursor.data[cursor.start] = 'a';
- cursor.seek(originalSize + 1);
- cursor.data[cursor.start] = 'b';
- cursor.seek(originalSize + 2);
- cursor.data[cursor.start] = 'c';
- }
-
- assertEquals(expected, buffer);
- }
-
- @Test public void enlargeByManySegments() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- long originalSize = buffer.size();
-
- Buffer expected = deepCopy(buffer);
- expected.writeUtf8(repeat("x", 1_000_000));
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- cursor.resizeBuffer(originalSize + 1_000_000);
- cursor.seek(originalSize);
- do {
- Arrays.fill(cursor.data, cursor.start, cursor.end, (byte) 'x');
- } while (cursor.next() != -1);
- }
-
- assertEquals(expected, buffer);
- }
-
- @Test public void resizeNotAcquired() throws Exception {
- UnsafeCursor cursor = new UnsafeCursor();
- try {
- cursor.resizeBuffer(10);
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- @Test public void expandNotAcquired() throws Exception {
- UnsafeCursor cursor = new UnsafeCursor();
- try {
- cursor.expandBuffer(10);
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- @Test public void resizeAcquiredReadOnly() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
-
- try (UnsafeCursor cursor = buffer.readUnsafe()) {
- cursor.resizeBuffer(10);
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- @Test public void expandAcquiredReadOnly() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
-
- try (UnsafeCursor cursor = buffer.readUnsafe()) {
- cursor.expandBuffer(10);
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- @Test public void shrink() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- assumeTrue(buffer.size() > 3);
- long originalSize = buffer.size();
-
- Buffer expected = new Buffer();
- deepCopy(buffer).copyTo(expected, 0, originalSize - 3);
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- assertEquals(originalSize, cursor.resizeBuffer(originalSize - 3));
- }
-
- assertEquals(expected, buffer);
- }
-
- @Test public void shrinkByManySegments() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- assumeTrue(buffer.size() <= 1_000_000);
- long originalSize = buffer.size();
-
- Buffer toShrink = new Buffer();
- toShrink.writeUtf8(repeat("x", 1_000_000));
- deepCopy(buffer).copyTo(toShrink, 0, originalSize);
-
- UnsafeCursor cursor = new UnsafeCursor();
- toShrink.readAndWriteUnsafe(cursor);
- try {
- cursor.resizeBuffer(originalSize);
- } finally {
- cursor.close();
- }
-
- Buffer expected = new Buffer();
- expected.writeUtf8(repeat("x", (int) originalSize));
- assertEquals(expected, toShrink);
- }
-
- @Test public void shrinkAdjustOffset() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- assumeTrue(buffer.size() > 4);
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- cursor.seek(buffer.size() - 1);
- cursor.resizeBuffer(3);
- assertEquals(3, cursor.offset);
- assertEquals(null, cursor.data);
- assertEquals(-1, cursor.start);
- assertEquals(-1, cursor.end);
- }
- }
-
- @Test public void resizeToSameSizeSeeksToEnd() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- long originalSize = buffer.size();
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- cursor.seek(buffer.size() / 2);
- assertEquals(originalSize, buffer.size());
- cursor.resizeBuffer(originalSize);
- assertEquals(originalSize, buffer.size());
- assertEquals(originalSize, cursor.offset);
- assertNull(cursor.data);
- assertEquals(-1, cursor.start);
- assertEquals(-1, cursor.end);
- }
- }
-
- @Test public void resizeEnlargeMovesCursorToOldSize() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- long originalSize = buffer.size();
-
- Buffer expected = deepCopy(buffer);
- expected.writeUtf8("a");
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- cursor.seek(buffer.size() / 2);
- assertEquals(originalSize, buffer.size());
- cursor.resizeBuffer(originalSize + 1);
- assertEquals(originalSize, cursor.offset);
- assertNotNull(cursor.data);
- assertNotEquals(-1, cursor.start);
- assertEquals(cursor.start + 1, cursor.end);
- cursor.data[cursor.start] = 'a';
- }
-
- assertEquals(expected, buffer);
- }
-
- @Test public void resizeShrinkMovesCursorToEnd() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- assumeTrue(buffer.size() > 0);
- long originalSize = buffer.size();
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- cursor.seek(buffer.size() / 2);
- assertEquals(originalSize, buffer.size());
- cursor.resizeBuffer(originalSize - 1);
- assertEquals(originalSize - 1, cursor.offset);
- assertNull(cursor.data);
- assertEquals(-1, cursor.start);
- assertEquals(-1, cursor.end);
- }
- }
-
- @Test public void expand() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- long originalSize = buffer.size();
-
- Buffer expected = deepCopy(buffer);
- expected.writeUtf8("abcde");
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- cursor.expandBuffer(5);
-
- for (int i = 0; i < 5; i++) {
- cursor.data[cursor.start + i] = (byte) ('a' + i);
- }
-
- cursor.resizeBuffer(originalSize + 5);
- }
-
- assertEquals(expected, buffer);
- }
-
- @Test public void expandSameSegment() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- long originalSize = buffer.size();
- assumeTrue(originalSize > 0);
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- cursor.seek(originalSize - 1);
- int originalEnd = cursor.end;
- assumeTrue(originalEnd < SEGMENT_SIZE);
-
- long addedByteCount = cursor.expandBuffer(1);
- assertEquals(SEGMENT_SIZE - originalEnd, addedByteCount);
-
- assertEquals(originalSize + addedByteCount, buffer.size());
- assertEquals(originalSize, cursor.offset);
- assertEquals(originalEnd, cursor.start);
- assertEquals(SEGMENT_SIZE, cursor.end);
- }
- }
-
- @Test public void expandNewSegment() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- long originalSize = buffer.size();
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- long addedByteCount = cursor.expandBuffer(SEGMENT_SIZE);
- assertEquals(SEGMENT_SIZE, addedByteCount);
-
- assertEquals(originalSize, cursor.offset);
- assertEquals(0, cursor.start);
- assertEquals(SEGMENT_SIZE, cursor.end);
- }
- }
-
- @Test public void expandMovesOffsetToOldSize() throws Exception {
- Buffer buffer = bufferFactory.newBuffer();
- long originalSize = buffer.size();
-
- try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) {
- cursor.seek(buffer.size() / 2);
- assertEquals(originalSize, buffer.size());
- long addedByteCount = cursor.expandBuffer(5);
- assertEquals(originalSize + addedByteCount, buffer.size());
- assertEquals(originalSize, cursor.offset);
- }
- }
-}
diff --git a/okio/src/jvmTest/java/okio/BufferTest.java b/okio/src/jvmTest/java/okio/BufferTest.java
deleted file mode 100644
index f5586d40..00000000
--- a/okio/src/jvmTest/java/okio/BufferTest.java
+++ /dev/null
@@ -1,580 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Random;
-import org.junit.Test;
-
-import static java.util.Arrays.asList;
-import static kotlin.text.Charsets.UTF_8;
-import static kotlin.text.StringsKt.repeat;
-import static okio.TestUtil.SEGMENT_POOL_MAX_SIZE;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static okio.TestUtil.assertNoEmptySegments;
-import static okio.TestUtil.bufferWithRandomSegmentLayout;
-import static okio.TestUtil.segmentPoolByteCount;
-import static okio.TestUtil.segmentSizes;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-/**
- * Tests solely for the behavior of Buffer's implementation. For generic BufferedSink or
- * BufferedSource behavior use BufferedSinkTest or BufferedSourceTest, respectively.
- */
-public final class BufferTest {
- @Test public void readAndWriteUtf8() throws Exception {
- Buffer buffer = new Buffer();
- buffer.writeUtf8("ab");
- assertEquals(2, buffer.size());
- buffer.writeUtf8("cdef");
- assertEquals(6, buffer.size());
- assertEquals("abcd", buffer.readUtf8(4));
- assertEquals(2, buffer.size());
- assertEquals("ef", buffer.readUtf8(2));
- assertEquals(0, buffer.size());
- try {
- buffer.readUtf8(1);
- fail();
- } catch (EOFException expected) {
- }
- }
-
- /** Buffer's toString is the same as ByteString's. */
- @Test public void bufferToString() {
- assertEquals("[size=0]", new Buffer().toString());
- assertEquals("[text=a\\r\\nb\\nc\\rd\\\\e]",
- new Buffer().writeUtf8("a\r\nb\nc\rd\\e").toString());
- assertEquals("[text=Tyrannosaur]",
- new Buffer().writeUtf8("Tyrannosaur").toString());
- assertEquals("[text=təˈranəˌsôr]", new Buffer()
- .write(ByteString.decodeHex("74c999cb8872616ec999cb8c73c3b472"))
- .toString());
- assertEquals("[hex=0000000000000000000000000000000000000000000000000000000000000000000000000000"
- + "0000000000000000000000000000000000000000000000000000]",
- new Buffer().write(new byte[64]).toString());
- }
-
- @Test public void multipleSegmentBuffers() throws Exception {
- Buffer buffer = new Buffer();
- buffer.writeUtf8(repeat("a", 1000));
- buffer.writeUtf8(repeat("b", 2500));
- buffer.writeUtf8(repeat("c", 5000));
- buffer.writeUtf8(repeat("d", 10000));
- buffer.writeUtf8(repeat("e", 25000));
- buffer.writeUtf8(repeat("f", 50000));
-
- assertEquals(repeat("a", 999), buffer.readUtf8(999)); // a...a
- assertEquals("a" + repeat("b", 2500) + "c", buffer.readUtf8(2502)); // ab...bc
- assertEquals(repeat("c", 4998), buffer.readUtf8(4998)); // c...c
- assertEquals("c" + repeat("d", 10000) + "e", buffer.readUtf8(10002)); // cd...de
- assertEquals(repeat("e", 24998), buffer.readUtf8(24998)); // e...e
- assertEquals("e" + repeat("f", 50000), buffer.readUtf8(50001)); // ef...f
- assertEquals(0, buffer.size());
- }
-
- @Test public void fillAndDrainPool() throws Exception {
- Buffer buffer = new Buffer();
-
- // Take 2 * MAX_SIZE segments. This will drain the pool, even if other tests filled it.
- buffer.write(new byte[(int) SEGMENT_POOL_MAX_SIZE]);
- buffer.write(new byte[(int) SEGMENT_POOL_MAX_SIZE]);
- assertEquals(0, segmentPoolByteCount());
-
- // Recycle MAX_SIZE segments. They're all in the pool.
- buffer.skip(SEGMENT_POOL_MAX_SIZE);
- assertEquals(SEGMENT_POOL_MAX_SIZE, segmentPoolByteCount());
-
- // Recycle MAX_SIZE more segments. The pool is full so they get garbage collected.
- buffer.skip(SEGMENT_POOL_MAX_SIZE);
- assertEquals(SEGMENT_POOL_MAX_SIZE, segmentPoolByteCount());
-
- // Take MAX_SIZE segments to drain the pool.
- buffer.write(new byte[(int) SEGMENT_POOL_MAX_SIZE]);
- assertEquals(0, segmentPoolByteCount());
-
- // Take MAX_SIZE more segments. The pool is drained so these will need to be allocated.
- buffer.write(new byte[(int) SEGMENT_POOL_MAX_SIZE]);
- assertEquals(0, segmentPoolByteCount());
- }
-
- @Test public void moveBytesBetweenBuffersShareSegment() throws Exception {
- int size = (SEGMENT_SIZE / 2) - 1;
- List<Integer> segmentSizes = moveBytesBetweenBuffers(repeat("a", size), repeat("b", size));
- assertEquals(asList(size * 2), segmentSizes);
- }
-
- @Test public void moveBytesBetweenBuffersReassignSegment() throws Exception {
- int size = (SEGMENT_SIZE / 2) + 1;
- List<Integer> segmentSizes = moveBytesBetweenBuffers(repeat("a", size), repeat("b", size));
- assertEquals(asList(size, size), segmentSizes);
- }
-
- @Test public void moveBytesBetweenBuffersMultipleSegments() throws Exception {
- int size = 3 * SEGMENT_SIZE + 1;
- List<Integer> segmentSizes = moveBytesBetweenBuffers(repeat("a", size), repeat("b", size));
- assertEquals(asList(SEGMENT_SIZE, SEGMENT_SIZE, SEGMENT_SIZE, 1,
- SEGMENT_SIZE, SEGMENT_SIZE, SEGMENT_SIZE, 1), segmentSizes);
- }
-
- private List<Integer> moveBytesBetweenBuffers(String... contents) throws IOException {
- StringBuilder expected = new StringBuilder();
- Buffer buffer = new Buffer();
- for (String s : contents) {
- Buffer source = new Buffer();
- source.writeUtf8(s);
- buffer.writeAll(source);
- expected.append(s);
- }
- List<Integer> segmentSizes = segmentSizes(buffer);
- assertEquals(expected.toString(), buffer.readUtf8(expected.length()));
- return segmentSizes;
- }
-
- /** The big part of source's first segment is being moved. */
- @Test public void writeSplitSourceBufferLeft() {
- int writeSize = SEGMENT_SIZE / 2 + 1;
-
- Buffer sink = new Buffer();
- sink.writeUtf8(repeat("b", SEGMENT_SIZE - 10));
-
- Buffer source = new Buffer();
- source.writeUtf8(repeat("a", SEGMENT_SIZE * 2));
- sink.write(source, writeSize);
-
- assertEquals(asList(SEGMENT_SIZE - 10, writeSize), segmentSizes(sink));
- assertEquals(asList(SEGMENT_SIZE - writeSize, SEGMENT_SIZE), segmentSizes(source));
- }
-
- /** The big part of source's first segment is staying put. */
- @Test public void writeSplitSourceBufferRight() {
- int writeSize = SEGMENT_SIZE / 2 - 1;
-
- Buffer sink = new Buffer();
- sink.writeUtf8(repeat("b", SEGMENT_SIZE - 10));
-
- Buffer source = new Buffer();
- source.writeUtf8(repeat("a", SEGMENT_SIZE * 2));
- sink.write(source, writeSize);
-
- assertEquals(asList(SEGMENT_SIZE - 10, writeSize), segmentSizes(sink));
- assertEquals(asList(SEGMENT_SIZE - writeSize, SEGMENT_SIZE), segmentSizes(source));
- }
-
- @Test public void writePrefixDoesntSplit() {
- Buffer sink = new Buffer();
- sink.writeUtf8(repeat("b", 10));
-
- Buffer source = new Buffer();
- source.writeUtf8(repeat("a", SEGMENT_SIZE * 2));
- sink.write(source, 20);
-
- assertEquals(asList(30), segmentSizes(sink));
- assertEquals(asList(SEGMENT_SIZE - 20, SEGMENT_SIZE), segmentSizes(source));
- assertEquals(30, sink.size());
- assertEquals(SEGMENT_SIZE * 2 - 20, source.size());
- }
-
- @Test public void writePrefixDoesntSplitButRequiresCompact() throws Exception {
- Buffer sink = new Buffer();
- sink.writeUtf8(repeat("b", SEGMENT_SIZE - 10)); // limit = size - 10
- sink.readUtf8(SEGMENT_SIZE - 20); // pos = size = 20
-
- Buffer source = new Buffer();
- source.writeUtf8(repeat("a", SEGMENT_SIZE * 2));
- sink.write(source, 20);
-
- assertEquals(asList(30), segmentSizes(sink));
- assertEquals(asList(SEGMENT_SIZE - 20, SEGMENT_SIZE), segmentSizes(source));
- assertEquals(30, sink.size());
- assertEquals(SEGMENT_SIZE * 2 - 20, source.size());
- }
-
- @Test public void copyToSpanningSegments() throws Exception {
- Buffer source = new Buffer();
- source.writeUtf8(repeat("a", SEGMENT_SIZE * 2));
- source.writeUtf8(repeat("b", SEGMENT_SIZE * 2));
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- source.copyTo(out, 10, SEGMENT_SIZE * 3);
-
- assertEquals(repeat("a", SEGMENT_SIZE * 2 - 10) + repeat("b", SEGMENT_SIZE + 10),
- out.toString());
- assertEquals(repeat("a", SEGMENT_SIZE * 2) + repeat("b", SEGMENT_SIZE * 2),
- source.readUtf8(SEGMENT_SIZE * 4));
- }
-
- @Test public void copyToStream() throws Exception {
- Buffer buffer = new Buffer().writeUtf8("hello, world!");
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- buffer.copyTo(out);
- String outString = new String(out.toByteArray(), UTF_8);
- assertEquals("hello, world!", outString);
- assertEquals("hello, world!", buffer.readUtf8());
- }
-
- @Test public void writeToSpanningSegments() throws Exception {
- Buffer buffer = new Buffer();
- buffer.writeUtf8(repeat("a", SEGMENT_SIZE * 2));
- buffer.writeUtf8(repeat("b", SEGMENT_SIZE * 2));
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- buffer.skip(10);
- buffer.writeTo(out, SEGMENT_SIZE * 3);
-
- assertEquals(repeat("a", SEGMENT_SIZE * 2 - 10) + repeat("b", SEGMENT_SIZE + 10),
- out.toString());
- assertEquals(repeat("b", SEGMENT_SIZE - 10), buffer.readUtf8(buffer.size()));
- }
-
- @Test public void writeToStream() throws Exception {
- Buffer buffer = new Buffer().writeUtf8("hello, world!");
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- buffer.writeTo(out);
- String outString = new String(out.toByteArray(), UTF_8);
- assertEquals("hello, world!", outString);
- assertEquals(0, buffer.size());
- }
-
- @Test public void readFromStream() throws Exception {
- InputStream in = new ByteArrayInputStream("hello, world!".getBytes(UTF_8));
- Buffer buffer = new Buffer();
- buffer.readFrom(in);
- String out = buffer.readUtf8();
- assertEquals("hello, world!", out);
- }
-
- @Test public void readFromSpanningSegments() throws Exception {
- InputStream in = new ByteArrayInputStream("hello, world!".getBytes(UTF_8));
- Buffer buffer = new Buffer().writeUtf8(repeat("a", SEGMENT_SIZE - 10));
- buffer.readFrom(in);
- String out = buffer.readUtf8();
- assertEquals(repeat("a", SEGMENT_SIZE - 10) + "hello, world!", out);
- }
-
- @Test public void readFromStreamWithCount() throws Exception {
- InputStream in = new ByteArrayInputStream("hello, world!".getBytes(UTF_8));
- Buffer buffer = new Buffer();
- buffer.readFrom(in, 10);
- String out = buffer.readUtf8();
- assertEquals("hello, wor", out);
- }
-
- @Test public void readFromDoesNotLeaveEmptyTailSegment() throws IOException {
- Buffer buffer = new Buffer();
- buffer.readFrom(new ByteArrayInputStream(new byte[SEGMENT_SIZE]));
- assertNoEmptySegments(buffer);
- }
-
- @Test public void moveAllRequestedBytesWithRead() throws Exception {
- Buffer sink = new Buffer();
- sink.writeUtf8(repeat("a", 10));
-
- Buffer source = new Buffer();
- source.writeUtf8(repeat("b", 15));
-
- assertEquals(10, source.read(sink, 10));
- assertEquals(20, sink.size());
- assertEquals(5, source.size());
- assertEquals(repeat("a", 10) + repeat("b", 10), sink.readUtf8(20));
- }
-
- @Test public void moveFewerThanRequestedBytesWithRead() throws Exception {
- Buffer sink = new Buffer();
- sink.writeUtf8(repeat("a", 10));
-
- Buffer source = new Buffer();
- source.writeUtf8(repeat("b", 20));
-
- assertEquals(20, source.read(sink, 25));
- assertEquals(30, sink.size());
- assertEquals(0, source.size());
- assertEquals(repeat("a", 10) + repeat("b", 20), sink.readUtf8(30));
- }
-
- @Test public void indexOfWithOffset() {
- Buffer buffer = new Buffer();
- int halfSegment = SEGMENT_SIZE / 2;
- buffer.writeUtf8(repeat("a", halfSegment));
- buffer.writeUtf8(repeat("b", halfSegment));
- buffer.writeUtf8(repeat("c", halfSegment));
- buffer.writeUtf8(repeat("d", halfSegment));
- assertEquals(0, buffer.indexOf((byte) 'a', 0));
- assertEquals(halfSegment - 1, buffer.indexOf((byte) 'a', halfSegment - 1));
- assertEquals(halfSegment, buffer.indexOf((byte) 'b', halfSegment - 1));
- assertEquals(halfSegment * 2, buffer.indexOf((byte) 'c', halfSegment - 1));
- assertEquals(halfSegment * 3, buffer.indexOf((byte) 'd', halfSegment - 1));
- assertEquals(halfSegment * 3, buffer.indexOf((byte) 'd', halfSegment * 2));
- assertEquals(halfSegment * 3, buffer.indexOf((byte) 'd', halfSegment * 3));
- assertEquals(halfSegment * 4 - 1, buffer.indexOf((byte) 'd', halfSegment * 4 - 1));
- }
-
- @Test public void byteAt() {
- Buffer buffer = new Buffer();
- buffer.writeUtf8("a");
- buffer.writeUtf8(repeat("b", SEGMENT_SIZE));
- buffer.writeUtf8("c");
- assertEquals('a', buffer.getByte(0));
- assertEquals('a', buffer.getByte(0)); // getByte doesn't mutate!
- assertEquals('c', buffer.getByte(buffer.size() - 1));
- assertEquals('b', buffer.getByte(buffer.size() - 2));
- assertEquals('b', buffer.getByte(buffer.size() - 3));
- }
-
- @Test public void getByteOfEmptyBuffer() {
- Buffer buffer = new Buffer();
- try {
- buffer.getByte(0);
- fail();
- } catch (IndexOutOfBoundsException expected) {
- }
- }
-
- @Test public void writePrefixToEmptyBuffer() throws IOException {
- Buffer sink = new Buffer();
- Buffer source = new Buffer();
- source.writeUtf8("abcd");
- sink.write(source, 2);
- assertEquals("ab", sink.readUtf8(2));
- }
-
- @Test public void cloneDoesNotObserveWritesToOriginal() {
- Buffer original = new Buffer();
- Buffer clone = original.clone();
- original.writeUtf8("abc");
- assertEquals(0, clone.size());
- }
-
- @Test public void cloneDoesNotObserveReadsFromOriginal() throws Exception {
- Buffer original = new Buffer();
- original.writeUtf8("abc");
- Buffer clone = original.clone();
- assertEquals("abc", original.readUtf8(3));
- assertEquals(3, clone.size());
- assertEquals("ab", clone.readUtf8(2));
- }
-
- @Test public void originalDoesNotObserveWritesToClone() {
- Buffer original = new Buffer();
- Buffer clone = original.clone();
- clone.writeUtf8("abc");
- assertEquals(0, original.size());
- }
-
- @Test public void originalDoesNotObserveReadsFromClone() throws Exception {
- Buffer original = new Buffer();
- original.writeUtf8("abc");
- Buffer clone = original.clone();
- assertEquals("abc", clone.readUtf8(3));
- assertEquals(3, original.size());
- assertEquals("ab", original.readUtf8(2));
- }
-
- @Test public void cloneMultipleSegments() throws Exception {
- Buffer original = new Buffer();
- original.writeUtf8(repeat("a", SEGMENT_SIZE * 3));
- Buffer clone = original.clone();
- original.writeUtf8(repeat("b", SEGMENT_SIZE * 3));
- clone.writeUtf8(repeat("c", SEGMENT_SIZE * 3));
-
- assertEquals(repeat("a", SEGMENT_SIZE * 3) + repeat("b", SEGMENT_SIZE * 3),
- original.readUtf8(SEGMENT_SIZE * 6));
- assertEquals(repeat("a", SEGMENT_SIZE * 3) + repeat("c", SEGMENT_SIZE * 3),
- clone.readUtf8(SEGMENT_SIZE * 6));
- }
-
- @Test public void equalsAndHashCodeEmpty() {
- Buffer a = new Buffer();
- Buffer b = new Buffer();
- assertTrue(a.equals(b));
- assertTrue(a.hashCode() == b.hashCode());
- }
-
- @Test public void equalsAndHashCode() throws Exception {
- Buffer a = new Buffer().writeUtf8("dog");
- Buffer b = new Buffer().writeUtf8("hotdog");
- assertFalse(a.equals(b));
- assertFalse(a.hashCode() == b.hashCode());
-
- b.readUtf8(3); // Leaves b containing 'dog'.
- assertTrue(a.equals(b));
- assertTrue(a.hashCode() == b.hashCode());
- }
-
- @Test public void equalsAndHashCodeSpanningSegments() throws Exception {
- byte[] data = new byte[1024 * 1024];
- Random dice = new Random(0);
- dice.nextBytes(data);
-
- Buffer a = bufferWithRandomSegmentLayout(dice, data);
- Buffer b = bufferWithRandomSegmentLayout(dice, data);
- assertTrue(a.equals(b));
- assertTrue(a.hashCode() == b.hashCode());
-
- data[data.length / 2]++; // Change a single byte.
- Buffer c = bufferWithRandomSegmentLayout(dice, data);
- assertFalse(a.equals(c));
- assertFalse(a.hashCode() == c.hashCode());
- }
-
- @Test public void bufferInputStreamByteByByte() throws Exception {
- Buffer source = new Buffer();
- source.writeUtf8("abc");
-
- InputStream in = source.inputStream();
- assertEquals(3, in.available());
- assertEquals('a', in.read());
- assertEquals('b', in.read());
- assertEquals('c', in.read());
- assertEquals(-1, in.read());
- assertEquals(0, in.available());
- }
-
- @Test public void bufferInputStreamBulkReads() throws Exception {
- Buffer source = new Buffer();
- source.writeUtf8("abc");
-
- byte[] byteArray = new byte[4];
-
- Arrays.fill(byteArray, (byte) -5);
- InputStream in = source.inputStream();
- assertEquals(3, in.read(byteArray));
- assertEquals("[97, 98, 99, -5]", Arrays.toString(byteArray));
-
- Arrays.fill(byteArray, (byte) -7);
- assertEquals(-1, in.read(byteArray));
- assertEquals("[-7, -7, -7, -7]", Arrays.toString(byteArray));
- }
-
- /**
- * When writing data that's already buffered, there's no reason to page the
- * data by segment.
- */
- @Test public void readAllWritesAllSegmentsAtOnce() throws Exception {
- Buffer write1 = new Buffer().writeUtf8(""
- + repeat("a", SEGMENT_SIZE)
- + repeat("b", SEGMENT_SIZE)
- + repeat("c", SEGMENT_SIZE));
-
- Buffer source = new Buffer().writeUtf8(""
- + repeat("a", SEGMENT_SIZE)
- + repeat("b", SEGMENT_SIZE)
- + repeat("c", SEGMENT_SIZE));
-
- MockSink mockSink = new MockSink();
-
- assertEquals(SEGMENT_SIZE * 3, source.readAll(mockSink));
- assertEquals(0, source.size());
- mockSink.assertLog("write(" + write1 + ", " + write1.size() + ")");
- }
-
- @Test public void writeAllMultipleSegments() throws Exception {
- Buffer source = new Buffer().writeUtf8(repeat("a", SEGMENT_SIZE * 3));
- Buffer sink = new Buffer();
-
- assertEquals(SEGMENT_SIZE * 3, sink.writeAll(source));
- assertEquals(0, source.size());
- assertEquals(repeat("a", SEGMENT_SIZE * 3), sink.readUtf8());
- }
-
- @Test public void copyTo() {
- Buffer source = new Buffer();
- source.writeUtf8("party");
-
- Buffer target = new Buffer();
- source.copyTo(target, 1, 3);
-
- assertEquals("art", target.readUtf8());
- assertEquals("party", source.readUtf8());
- }
-
- @Test public void copyToOnSegmentBoundary() {
- String as = repeat("a", SEGMENT_SIZE);
- String bs = repeat("b", SEGMENT_SIZE);
- String cs = repeat("c", SEGMENT_SIZE);
- String ds = repeat("d", SEGMENT_SIZE);
-
- Buffer source = new Buffer();
- source.writeUtf8(as);
- source.writeUtf8(bs);
- source.writeUtf8(cs);
-
- Buffer target = new Buffer();
- target.writeUtf8(ds);
-
- source.copyTo(target, as.length(), bs.length() + cs.length());
- assertEquals(ds + bs + cs, target.readUtf8());
- }
-
- @Test public void copyToOffSegmentBoundary() {
- String as = repeat("a", SEGMENT_SIZE - 1);
- String bs = repeat("b", SEGMENT_SIZE + 2);
- String cs = repeat("c", SEGMENT_SIZE - 4);
- String ds = repeat("d", SEGMENT_SIZE + 8);
-
- Buffer source = new Buffer();
- source.writeUtf8(as);
- source.writeUtf8(bs);
- source.writeUtf8(cs);
-
- Buffer target = new Buffer();
- target.writeUtf8(ds);
-
- source.copyTo(target, as.length(), bs.length() + cs.length());
- assertEquals(ds + bs + cs, target.readUtf8());
- }
-
- @Test public void copyToSourceAndTargetCanBeTheSame() {
- String as = repeat("a", SEGMENT_SIZE);
- String bs = repeat("b", SEGMENT_SIZE);
-
- Buffer source = new Buffer();
- source.writeUtf8(as);
- source.writeUtf8(bs);
-
- source.copyTo(source, 0, source.size());
- assertEquals(as + bs + as + bs, source.readUtf8());
- }
-
- @Test public void copyToEmptySource() {
- Buffer source = new Buffer();
- Buffer target = new Buffer().writeUtf8("aaa");
- source.copyTo(target, 0L, 0L);
- assertEquals("", source.readUtf8());
- assertEquals("aaa", target.readUtf8());
- }
-
- @Test public void copyToEmptyTarget() {
- Buffer source = new Buffer().writeUtf8("aaa");
- Buffer target = new Buffer();
- source.copyTo(target, 0L, 3L);
- assertEquals("aaa", source.readUtf8());
- assertEquals("aaa", target.readUtf8());
- }
-
- @Test public void snapshotReportsAccurateSize() {
- Buffer buf = new Buffer().write(new byte[] { 0, 1, 2, 3 });
- assertEquals(1, buf.snapshot(1).size());
- }
-}
diff --git a/okio/src/jvmTest/java/okio/BufferedSinkJavaTest.java b/okio/src/jvmTest/java/okio/BufferedSinkJavaTest.java
deleted file mode 100644
index 357b992a..00000000
--- a/okio/src/jvmTest/java/okio/BufferedSinkJavaTest.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import org.junit.Test;
-
-import static kotlin.text.StringsKt.repeat;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-/**
- * Tests solely for the behavior of RealBufferedSink's implementation. For generic
- * BufferedSink behavior use BufferedSinkTest.
- */
-public final class BufferedSinkJavaTest {
- @Test public void inputStreamCloses() throws Exception {
- BufferedSink sink = Okio.buffer((Sink) new Buffer());
- OutputStream out = sink.outputStream();
- out.close();
- try {
- sink.writeUtf8("Hi!");
- fail();
- } catch (IllegalStateException e) {
- assertEquals("closed", e.getMessage());
- }
- }
-
- @Test public void bufferedSinkEmitsTailWhenItIsComplete() throws IOException {
- Buffer sink = new Buffer();
- BufferedSink bufferedSink = Okio.buffer((Sink) sink);
- bufferedSink.writeUtf8(repeat("a", SEGMENT_SIZE - 1));
- assertEquals(0, sink.size());
- bufferedSink.writeByte(0);
- assertEquals(SEGMENT_SIZE, sink.size());
- assertEquals(0, bufferedSink.getBuffer().size());
- }
-
- @Test public void bufferedSinkEmitMultipleSegments() throws IOException {
- Buffer sink = new Buffer();
- BufferedSink bufferedSink = Okio.buffer((Sink) sink);
- bufferedSink.writeUtf8(repeat("a", SEGMENT_SIZE * 4 - 1));
- assertEquals(SEGMENT_SIZE * 3, sink.size());
- assertEquals(SEGMENT_SIZE - 1, bufferedSink.getBuffer().size());
- }
-
- @Test public void bufferedSinkFlush() throws IOException {
- Buffer sink = new Buffer();
- BufferedSink bufferedSink = Okio.buffer((Sink) sink);
- bufferedSink.writeByte('a');
- assertEquals(0, sink.size());
- bufferedSink.flush();
- assertEquals(0, bufferedSink.getBuffer().size());
- assertEquals(1, sink.size());
- }
-
- @Test public void bytesEmittedToSinkWithFlush() throws Exception {
- Buffer sink = new Buffer();
- BufferedSink bufferedSink = Okio.buffer((Sink) sink);
- bufferedSink.writeUtf8("abc");
- bufferedSink.flush();
- assertEquals(3, sink.size());
- }
-
- @Test public void bytesNotEmittedToSinkWithoutFlush() throws Exception {
- Buffer sink = new Buffer();
- BufferedSink bufferedSink = Okio.buffer((Sink) sink);
- bufferedSink.writeUtf8("abc");
- assertEquals(0, sink.size());
- }
-
- @Test public void bytesEmittedToSinkWithEmit() throws Exception {
- Buffer sink = new Buffer();
- BufferedSink bufferedSink = Okio.buffer((Sink) sink);
- bufferedSink.writeUtf8("abc");
- bufferedSink.emit();
- assertEquals(3, sink.size());
- }
-
- @Test public void completeSegmentsEmitted() throws Exception {
- Buffer sink = new Buffer();
- BufferedSink bufferedSink = Okio.buffer((Sink) sink);
- bufferedSink.writeUtf8(repeat("a", SEGMENT_SIZE * 3));
- assertEquals(SEGMENT_SIZE * 3, sink.size());
- }
-
- @Test public void incompleteSegmentsNotEmitted() throws Exception {
- Buffer sink = new Buffer();
- BufferedSink bufferedSink = Okio.buffer((Sink) sink);
- bufferedSink.writeUtf8(repeat("a", SEGMENT_SIZE * 3 - 1));
- assertEquals(SEGMENT_SIZE * 2, sink.size());
- }
-
- @Test public void closeWithExceptionWhenWriting() throws IOException {
- MockSink mockSink = new MockSink();
- mockSink.scheduleThrow(0, new IOException());
- BufferedSink bufferedSink = Okio.buffer(mockSink);
- bufferedSink.writeByte('a');
- try {
- bufferedSink.close();
- fail();
- } catch (IOException expected) {
- }
- mockSink.assertLog("write([text=a], 1)", "close()");
- }
-
- @Test public void closeWithExceptionWhenClosing() throws IOException {
- MockSink mockSink = new MockSink();
- mockSink.scheduleThrow(1, new IOException());
- BufferedSink bufferedSink = Okio.buffer(mockSink);
- bufferedSink.writeByte('a');
- try {
- bufferedSink.close();
- fail();
- } catch (IOException expected) {
- }
- mockSink.assertLog("write([text=a], 1)", "close()");
- }
-
- @Test public void closeWithExceptionWhenWritingAndClosing() throws IOException {
- MockSink mockSink = new MockSink();
- mockSink.scheduleThrow(0, new IOException("first"));
- mockSink.scheduleThrow(1, new IOException("second"));
- BufferedSink bufferedSink = Okio.buffer(mockSink);
- bufferedSink.writeByte('a');
- try {
- bufferedSink.close();
- fail();
- } catch (IOException expected) {
- assertEquals("first", expected.getMessage());
- }
- mockSink.assertLog("write([text=a], 1)", "close()");
- }
-
- @Test public void operationsAfterClose() throws IOException {
- MockSink mockSink = new MockSink();
- BufferedSink bufferedSink = Okio.buffer(mockSink);
- bufferedSink.writeByte('a');
- bufferedSink.close();
-
- // Test a sample set of methods.
- try {
- bufferedSink.writeByte('a');
- fail();
- } catch (IllegalStateException expected) {
- }
-
- try {
- bufferedSink.write(new byte[10]);
- fail();
- } catch (IllegalStateException expected) {
- }
-
- try {
- bufferedSink.emitCompleteSegments();
- fail();
- } catch (IllegalStateException expected) {
- }
-
- try {
- bufferedSink.emit();
- fail();
- } catch (IllegalStateException expected) {
- }
-
- try {
- bufferedSink.flush();
- fail();
- } catch (IllegalStateException expected) {
- }
-
- // Test a sample set of methods on the OutputStream.
- OutputStream os = bufferedSink.outputStream();
- try {
- os.write('a');
- fail();
- } catch (IOException expected) {
- }
-
- try {
- os.write(new byte[10]);
- fail();
- } catch (IOException expected) {
- }
-
- // Permitted
- os.flush();
- }
-
- @Test public void writeAll() throws IOException {
- MockSink mockSink = new MockSink();
- BufferedSink bufferedSink = Okio.buffer(mockSink);
-
- bufferedSink.getBuffer().writeUtf8("abc");
- assertEquals(3, bufferedSink.writeAll(new Buffer().writeUtf8("def")));
-
- assertEquals(6, bufferedSink.getBuffer().size());
- assertEquals("abcdef", bufferedSink.getBuffer().readUtf8(6));
- mockSink.assertLog(); // No writes.
- }
-
- @Test public void writeAllExhausted() throws IOException {
- MockSink mockSink = new MockSink();
- BufferedSink bufferedSink = Okio.buffer(mockSink);
-
- assertEquals(0, bufferedSink.writeAll(new Buffer()));
- assertEquals(0, bufferedSink.getBuffer().size());
- mockSink.assertLog(); // No writes.
- }
-
- @Test public void writeAllWritesOneSegmentAtATime() throws IOException {
- Buffer write1 = new Buffer().writeUtf8(repeat("a", SEGMENT_SIZE));
- Buffer write2 = new Buffer().writeUtf8(repeat("b", SEGMENT_SIZE));
- Buffer write3 = new Buffer().writeUtf8(repeat("c", SEGMENT_SIZE));
-
- Buffer source = new Buffer().writeUtf8(""
- + repeat("a", SEGMENT_SIZE)
- + repeat("b", SEGMENT_SIZE)
- + repeat("c", SEGMENT_SIZE));
-
- MockSink mockSink = new MockSink();
- BufferedSink bufferedSink = Okio.buffer(mockSink);
- assertEquals(SEGMENT_SIZE * 3, bufferedSink.writeAll(source));
-
- mockSink.assertLog(
- "write(" + write1 + ", " + write1.size() + ")",
- "write(" + write2 + ", " + write2.size() + ")",
- "write(" + write3 + ", " + write3.size() + ")");
- }
-}
diff --git a/okio/src/jvmTest/java/okio/BufferedSinkTest.java b/okio/src/jvmTest/java/okio/BufferedSinkTest.java
deleted file mode 100644
index e0132846..00000000
--- a/okio/src/jvmTest/java/okio/BufferedSinkTest.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import static java.util.Arrays.asList;
-import static kotlin.text.Charsets.UTF_8;
-import static kotlin.text.StringsKt.repeat;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static okio.TestUtil.segmentSizes;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-@RunWith(Parameterized.class)
-public final class BufferedSinkTest {
- private interface Factory {
- Factory BUFFER = new Factory() {
- @Override public BufferedSink create(Buffer data) {
- return data;
- }
-
- @Override public String toString() {
- return "Buffer";
- }
- };
-
- Factory REAL_BUFFERED_SINK = new Factory() {
- @Override public BufferedSink create(Buffer data) {
- return Okio.buffer((Sink) data);
- }
-
- @Override public String toString() {
- return "RealBufferedSink";
- }
- };
-
- BufferedSink create(Buffer data);
- }
-
- @Parameters(name = "{0}")
- public static List<Object[]> parameters() {
- return Arrays.asList(
- new Object[] {Factory.BUFFER},
- new Object[] {Factory.REAL_BUFFERED_SINK});
- }
-
- @Parameter public Factory factory;
- private Buffer data;
- private BufferedSink sink;
-
- @Before public void setUp() {
- data = new Buffer();
- sink = factory.create(data);
- }
-
- @Test public void writeNothing() throws IOException {
- sink.writeUtf8("");
- sink.flush();
- assertEquals(0, data.size());
- }
-
- @Test public void writeBytes() throws Exception {
- sink.writeByte(0xab);
- sink.writeByte(0xcd);
- sink.flush();
- assertEquals("[hex=abcd]", data.toString());
- }
-
- @Test public void writeLastByteInSegment() throws Exception {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE - 1));
- sink.writeByte(0x20);
- sink.writeByte(0x21);
- sink.flush();
- assertEquals(asList(SEGMENT_SIZE, 1), segmentSizes(data));
- assertEquals(repeat("a", SEGMENT_SIZE - 1), data.readUtf8(SEGMENT_SIZE - 1));
- assertEquals("[text= !]", data.toString());
- }
-
- @Test public void writeShort() throws Exception {
- sink.writeShort(0xabcd);
- sink.writeShort(0x4321);
- sink.flush();
- assertEquals("[hex=abcd4321]", data.toString());
- }
-
- @Test public void writeShortLe() throws Exception {
- sink.writeShortLe(0xcdab);
- sink.writeShortLe(0x2143);
- sink.flush();
- assertEquals("[hex=abcd4321]", data.toString());
- }
-
- @Test public void writeInt() throws Exception {
- sink.writeInt(0xabcdef01);
- sink.writeInt(0x87654321);
- sink.flush();
- assertEquals("[hex=abcdef0187654321]", data.toString());
- }
-
- @Test public void writeLastIntegerInSegment() throws Exception {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE - 4));
- sink.writeInt(0xabcdef01);
- sink.writeInt(0x87654321);
- sink.flush();
- assertEquals(asList(SEGMENT_SIZE, 4), segmentSizes(data));
- assertEquals(repeat("a", SEGMENT_SIZE - 4), data.readUtf8(SEGMENT_SIZE - 4));
- assertEquals("[hex=abcdef0187654321]", data.toString());
- }
-
- @Test public void writeIntegerDoesNotQuiteFitInSegment() throws Exception {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE - 3));
- sink.writeInt(0xabcdef01);
- sink.writeInt(0x87654321);
- sink.flush();
- assertEquals(asList(SEGMENT_SIZE - 3, 8), segmentSizes(data));
- assertEquals(repeat("a", SEGMENT_SIZE - 3), data.readUtf8(SEGMENT_SIZE - 3));
- assertEquals("[hex=abcdef0187654321]", data.toString());
- }
-
- @Test public void writeIntLe() throws Exception {
- sink.writeIntLe(0xabcdef01);
- sink.writeIntLe(0x87654321);
- sink.flush();
- assertEquals("[hex=01efcdab21436587]", data.toString());
- }
-
- @Test public void writeLong() throws Exception {
- sink.writeLong(0xabcdef0187654321L);
- sink.writeLong(0xcafebabeb0b15c00L);
- sink.flush();
- assertEquals("[hex=abcdef0187654321cafebabeb0b15c00]", data.toString());
- }
-
- @Test public void writeLongLe() throws Exception {
- sink.writeLongLe(0xabcdef0187654321L);
- sink.writeLongLe(0xcafebabeb0b15c00L);
- sink.flush();
- assertEquals("[hex=2143658701efcdab005cb1b0bebafeca]", data.toString());
- }
-
- @Test public void writeByteString() throws IOException {
- sink.write(ByteString.encodeUtf8("təˈranəˌsôr"));
- sink.flush();
- assertEquals(ByteString.decodeHex("74c999cb8872616ec999cb8c73c3b472"), data.readByteString());
- }
-
- @Test public void writeByteStringOffset() throws IOException {
- sink.write(ByteString.encodeUtf8("təˈranəˌsôr"), 5, 5);
- sink.flush();
- assertEquals(ByteString.decodeHex("72616ec999"), data.readByteString());
- }
-
- @Test public void writeSegmentedByteString() throws IOException {
- sink.write(new Buffer().write(ByteString.encodeUtf8("təˈranəˌsôr")).snapshot());
- sink.flush();
- assertEquals(ByteString.decodeHex("74c999cb8872616ec999cb8c73c3b472"), data.readByteString());
- }
-
- @Test public void writeSegmentedByteStringOffset() throws IOException {
- sink.write(new Buffer().write(ByteString.encodeUtf8("təˈranəˌsôr")).snapshot(), 5, 5);
- sink.flush();
- assertEquals(ByteString.decodeHex("72616ec999"), data.readByteString());
- }
-
- @Test public void writeStringUtf8() throws IOException {
- sink.writeUtf8("təˈranəˌsôr");
- sink.flush();
- assertEquals(ByteString.decodeHex("74c999cb8872616ec999cb8c73c3b472"), data.readByteString());
- }
-
- @Test public void writeSubstringUtf8() throws IOException {
- sink.writeUtf8("təˈranəˌsôr", 3, 7);
- sink.flush();
- assertEquals(ByteString.decodeHex("72616ec999"), data.readByteString());
- }
-
- @Test public void writeStringWithCharset() throws IOException {
- sink.writeString("təˈranəˌsôr", Charset.forName("utf-32be"));
- sink.flush();
- assertEquals(ByteString.decodeHex("0000007400000259000002c800000072000000610000006e00000259"
- + "000002cc00000073000000f400000072"), data.readByteString());
- }
-
- @Test public void writeSubstringWithCharset() throws IOException {
- sink.writeString("təˈranəˌsôr", 3, 7, Charset.forName("utf-32be"));
- sink.flush();
- assertEquals(ByteString.decodeHex("00000072000000610000006e00000259"), data.readByteString());
- }
-
- @Test public void writeUtf8SubstringWithCharset() throws IOException {
- sink.writeString("təˈranəˌsôr", 3, 7, Charset.forName("utf-8"));
- sink.flush();
- assertEquals(ByteString.encodeUtf8("ranə"), data.readByteString());
- }
-
- @Test public void writeAll() throws Exception {
- Buffer source = new Buffer().writeUtf8("abcdef");
-
- assertEquals(6, sink.writeAll(source));
- assertEquals(0, source.size());
- sink.flush();
- assertEquals("abcdef", data.readUtf8());
- }
-
- @Test public void writeSource() throws Exception {
- Buffer source = new Buffer().writeUtf8("abcdef");
-
- // Force resolution of the Source method overload.
- sink.write((Source) source, 4);
- sink.flush();
- assertEquals("abcd", data.readUtf8());
- assertEquals("ef", source.readUtf8());
- }
-
- @Test public void writeSourceReadsFully() throws Exception {
- Source source = new ForwardingSource(new Buffer()) {
- @Override public long read(Buffer sink, long byteCount) throws IOException {
- sink.writeUtf8("abcd");
- return 4;
- }
- };
-
- sink.write(source, 8);
- sink.flush();
- assertEquals("abcdabcd", data.readUtf8());
- }
-
- @Test public void writeSourcePropagatesEof() throws IOException {
- Source source = new Buffer().writeUtf8("abcd");
-
- try {
- sink.write(source, 8);
- fail();
- } catch (EOFException expected) {
- }
-
- // Ensure that whatever was available was correctly written.
- sink.flush();
- assertEquals("abcd", data.readUtf8());
- }
-
- @Test public void writeSourceWithZeroIsNoOp() throws IOException {
- // This test ensures that a zero byte count never calls through to read the source. It may be
- // tied to something like a socket which will potentially block trying to read a segment when
- // ultimately we don't want any data.
- Source source = new ForwardingSource(new Buffer()) {
- @Override public long read(Buffer sink, long byteCount) throws IOException {
- throw new AssertionError();
- }
- };
- sink.write(source, 0);
- assertEquals(0, data.size());
- }
-
- @Test public void writeAllExhausted() throws Exception {
- Buffer source = new Buffer();
- assertEquals(0, sink.writeAll(source));
- assertEquals(0, source.size());
- }
-
- @Test public void closeEmitsBufferedBytes() throws IOException {
- sink.writeByte('a');
- sink.close();
- assertEquals('a', data.readByte());
- }
-
- @Test public void outputStream() throws Exception {
- OutputStream out = sink.outputStream();
- out.write('a');
- out.write(repeat("b", 9998).getBytes(UTF_8));
- out.write('c');
- out.flush();
- assertEquals("a" + repeat("b", 9998) + "c", data.readUtf8());
- }
-
- @Test public void outputStreamBounds() throws Exception {
- OutputStream out = sink.outputStream();
- try {
- out.write(new byte[100], 50, 51);
- fail();
- } catch (ArrayIndexOutOfBoundsException expected) {
- }
- }
-
- @Test public void longDecimalString() throws IOException {
- assertLongDecimalString(0);
- assertLongDecimalString(Long.MIN_VALUE);
- assertLongDecimalString(Long.MAX_VALUE);
-
- for (int i = 1; i < 20; i++) {
- long value = BigInteger.valueOf(10L).pow(i).longValue();
- assertLongDecimalString(value - 1);
- assertLongDecimalString(value);
- }
- }
-
- private void assertLongDecimalString(long value) throws IOException {
- sink.writeDecimalLong(value).writeUtf8("zzz").flush();
- String expected = Long.toString(value) + "zzz";
- String actual = data.readUtf8();
- assertEquals(value + " expected " + expected + " but was " + actual, actual, expected);
- }
-
- @Test public void longHexString() throws IOException {
- assertLongHexString(0);
- assertLongHexString(Long.MIN_VALUE);
- assertLongHexString(Long.MAX_VALUE);
-
- for (int i = 0; i < 63; i++) {
- assertLongHexString((1L << i) - 1);
- assertLongHexString(1L << i);
- }
- }
-
- @Test public void writeNioBuffer() throws Exception {
- String expected = "abcdefg";
-
- ByteBuffer nioByteBuffer = ByteBuffer.allocate(1024);
- nioByteBuffer.put("abcdefg".getBytes(UTF_8));
- nioByteBuffer.flip();
-
- int byteCount = sink.write(nioByteBuffer);
- assertEquals(expected.length(), byteCount);
- assertEquals(expected.length(), nioByteBuffer.position());
- assertEquals(expected.length(), nioByteBuffer.limit());
-
- sink.flush();
- assertEquals(expected, data.readUtf8());
- }
-
- @Test public void writeLargeNioBufferWritesAllData() throws Exception {
- String expected = repeat("a", SEGMENT_SIZE * 3);
-
- ByteBuffer nioByteBuffer = ByteBuffer.allocate(SEGMENT_SIZE * 4);
- nioByteBuffer.put(repeat("a", SEGMENT_SIZE * 3).getBytes(UTF_8));
- nioByteBuffer.flip();
-
- int byteCount = sink.write(nioByteBuffer);
- assertEquals(expected.length(), byteCount);
- assertEquals(expected.length(), nioByteBuffer.position());
- assertEquals(expected.length(), nioByteBuffer.limit());
-
- sink.flush();
- assertEquals(expected, data.readUtf8());
- }
-
- private void assertLongHexString(long value) throws IOException {
- sink.writeHexadecimalUnsignedLong(value).writeUtf8("zzz").flush();
- String expected = String.format("%x", value) + "zzz";
- String actual = data.readUtf8();
- assertEquals(value + " expected " + expected + " but was " + actual, actual, expected);
- }
-}
diff --git a/okio/src/jvmTest/java/okio/BufferedSourceJavaTest.java b/okio/src/jvmTest/java/okio/BufferedSourceJavaTest.java
deleted file mode 100644
index 470a2ddc..00000000
--- a/okio/src/jvmTest/java/okio/BufferedSourceJavaTest.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import org.junit.Test;
-
-import static kotlin.text.Charsets.UTF_8;
-import static kotlin.text.StringsKt.repeat;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-/**
- * Tests solely for the behavior of RealBufferedSource's implementation. For generic
- * BufferedSource behavior use BufferedSourceTest.
- */
-public final class BufferedSourceJavaTest {
- @Test public void inputStreamTracksSegments() throws Exception {
- Buffer source = new Buffer();
- source.writeUtf8("a");
- source.writeUtf8(repeat("b", SEGMENT_SIZE));
- source.writeUtf8("c");
-
- InputStream in = Okio.buffer((Source) source).inputStream();
- assertEquals(0, in.available());
- assertEquals(SEGMENT_SIZE + 2, source.size());
-
- // Reading one byte buffers a full segment.
- assertEquals('a', in.read());
- assertEquals(SEGMENT_SIZE - 1, in.available());
- assertEquals(2, source.size());
-
- // Reading as much as possible reads the rest of that buffered segment.
- byte[] data = new byte[SEGMENT_SIZE * 2];
- assertEquals(SEGMENT_SIZE - 1, in.read(data, 0, data.length));
- assertEquals(repeat("b", SEGMENT_SIZE - 1), new String(data, 0, SEGMENT_SIZE - 1, UTF_8));
- assertEquals(2, source.size());
-
- // Continuing to read buffers the next segment.
- assertEquals('b', in.read());
- assertEquals(1, in.available());
- assertEquals(0, source.size());
-
- // Continuing to read reads from the buffer.
- assertEquals('c', in.read());
- assertEquals(0, in.available());
- assertEquals(0, source.size());
-
- // Once we've exhausted the source, we're done.
- assertEquals(-1, in.read());
- assertEquals(0, source.size());
- }
-
- @Test public void inputStreamCloses() throws Exception {
- BufferedSource source = Okio.buffer((Source) new Buffer());
- InputStream in = source.inputStream();
- in.close();
- try {
- source.require(1);
- fail();
- } catch (IllegalStateException e) {
- assertEquals("closed", e.getMessage());
- }
- }
-
- @Test public void indexOfStopsReadingAtLimit() throws Exception {
- Buffer buffer = new Buffer().writeUtf8("abcdef");
- BufferedSource bufferedSource = Okio.buffer(new ForwardingSource(buffer) {
- @Override public long read(Buffer sink, long byteCount) throws IOException {
- return super.read(sink, Math.min(1, byteCount));
- }
- });
-
- assertEquals(6, buffer.size());
- assertEquals(-1, bufferedSource.indexOf((byte) 'e', 0, 4));
- assertEquals(2, buffer.size());
- }
-
- @Test public void requireTracksBufferFirst() throws Exception {
- Buffer source = new Buffer();
- source.writeUtf8("bb");
-
- BufferedSource bufferedSource = Okio.buffer((Source) source);
- bufferedSource.getBuffer().writeUtf8("aa");
-
- bufferedSource.require(2);
- assertEquals(2, bufferedSource.getBuffer().size());
- assertEquals(2, source.size());
- }
-
- @Test public void requireIncludesBufferBytes() throws Exception {
- Buffer source = new Buffer();
- source.writeUtf8("b");
-
- BufferedSource bufferedSource = Okio.buffer((Source) source);
- bufferedSource.getBuffer().writeUtf8("a");
-
- bufferedSource.require(2);
- assertEquals("ab", bufferedSource.getBuffer().readUtf8(2));
- }
-
- @Test public void requireInsufficientData() throws Exception {
- Buffer source = new Buffer();
- source.writeUtf8("a");
-
- BufferedSource bufferedSource = Okio.buffer((Source) source);
-
- try {
- bufferedSource.require(2);
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void requireReadsOneSegmentAtATime() throws Exception {
- Buffer source = new Buffer();
- source.writeUtf8(repeat("a", SEGMENT_SIZE));
- source.writeUtf8(repeat("b", SEGMENT_SIZE));
-
- BufferedSource bufferedSource = Okio.buffer((Source) source);
-
- bufferedSource.require(2);
- assertEquals(SEGMENT_SIZE, source.size());
- assertEquals(SEGMENT_SIZE, bufferedSource.getBuffer().size());
- }
-
- @Test public void skipReadsOneSegmentAtATime() throws Exception {
- Buffer source = new Buffer();
- source.writeUtf8(repeat("a", SEGMENT_SIZE));
- source.writeUtf8(repeat("b", SEGMENT_SIZE));
- BufferedSource bufferedSource = Okio.buffer((Source) source);
- bufferedSource.skip(2);
- assertEquals(SEGMENT_SIZE, source.size());
- assertEquals(SEGMENT_SIZE - 2, bufferedSource.getBuffer().size());
- }
-
- @Test public void skipTracksBufferFirst() throws Exception {
- Buffer source = new Buffer();
- source.writeUtf8("bb");
-
- BufferedSource bufferedSource = Okio.buffer((Source) source);
- bufferedSource.getBuffer().writeUtf8("aa");
-
- bufferedSource.skip(2);
- assertEquals(0, bufferedSource.getBuffer().size());
- assertEquals(2, source.size());
- }
-
- @Test public void operationsAfterClose() throws IOException {
- Buffer source = new Buffer();
- BufferedSource bufferedSource = Okio.buffer((Source) source);
- bufferedSource.close();
-
- // Test a sample set of methods.
- try {
- bufferedSource.indexOf((byte) 1);
- fail();
- } catch (IllegalStateException expected) {
- }
-
- try {
- bufferedSource.skip(1);
- fail();
- } catch (IllegalStateException expected) {
- }
-
- try {
- bufferedSource.readByte();
- fail();
- } catch (IllegalStateException expected) {
- }
-
- try {
- bufferedSource.readByteString(10);
- fail();
- } catch (IllegalStateException expected) {
- }
-
- // Test a sample set of methods on the InputStream.
- InputStream is = bufferedSource.inputStream();
- try {
- is.read();
- fail();
- } catch (IOException expected) {
- }
-
- try {
- is.read(new byte[10]);
- fail();
- } catch (IOException expected) {
- }
- }
-
- /**
- * We don't want readAll to buffer an unbounded amount of data. Instead it
- * should buffer a segment, write it, and repeat.
- */
- @Test public void readAllReadsOneSegmentAtATime() throws IOException {
- Buffer write1 = new Buffer().writeUtf8(repeat("a", SEGMENT_SIZE));
- Buffer write2 = new Buffer().writeUtf8(repeat("b", SEGMENT_SIZE));
- Buffer write3 = new Buffer().writeUtf8(repeat("c", SEGMENT_SIZE));
-
- Buffer source = new Buffer().writeUtf8(""
- + repeat("a", SEGMENT_SIZE)
- + repeat("b", SEGMENT_SIZE)
- + repeat("c", SEGMENT_SIZE));
-
- MockSink mockSink = new MockSink();
- BufferedSource bufferedSource = Okio.buffer((Source) source);
- assertEquals(SEGMENT_SIZE * 3, bufferedSource.readAll(mockSink));
- mockSink.assertLog(
- "write(" + write1 + ", " + write1.size() + ")",
- "write(" + write2 + ", " + write2.size() + ")",
- "write(" + write3 + ", " + write3.size() + ")");
- }
-}
diff --git a/okio/src/jvmTest/java/okio/BufferedSourceTest.java b/okio/src/jvmTest/java/okio/BufferedSourceTest.java
deleted file mode 100644
index 149ae64a..00000000
--- a/okio/src/jvmTest/java/okio/BufferedSourceTest.java
+++ /dev/null
@@ -1,1492 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import static kotlin.text.Charsets.US_ASCII;
-import static kotlin.text.Charsets.UTF_8;
-import static kotlin.text.StringsKt.repeat;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static okio.TestUtil.assertByteArrayEquals;
-import static okio.TestUtil.assertByteArraysEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-@RunWith(Parameterized.class)
-public final class BufferedSourceTest {
- interface Factory {
- Factory BUFFER = new Factory() {
- @Override public Pipe pipe() {
- Buffer buffer = new Buffer();
- Pipe result = new Pipe();
- result.sink = buffer;
- result.source = buffer;
- return result;
- }
-
- @Override public boolean isOneByteAtATime() {
- return false;
- }
-
- @Override public String toString() {
- return "Buffer";
- }
- };
-
- Factory REAL_BUFFERED_SOURCE = new Factory() {
- @Override public Pipe pipe() {
- Buffer buffer = new Buffer();
- Pipe result = new Pipe();
- result.sink = buffer;
- result.source = Okio.buffer((Source) buffer);
- return result;
- }
-
- @Override public boolean isOneByteAtATime() {
- return false;
- }
-
- @Override public String toString() {
- return "RealBufferedSource";
- }
- };
-
- /**
- * A factory deliberately written to create buffers whose internal segments are always 1 byte
- * long. We like testing with these segments because are likely to trigger bugs!
- */
- Factory ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE = new Factory() {
- @Override public Pipe pipe() {
- Buffer buffer = new Buffer();
- Pipe result = new Pipe();
- result.sink = buffer;
- result.source = Okio.buffer(new ForwardingSource(buffer) {
- @Override public long read(Buffer sink, long byteCount) throws IOException {
- // Read one byte into a new buffer, then clone it so that the segment is shared.
- // Shared segments cannot be compacted so we'll get a long chain of short segments.
- Buffer box = new Buffer();
- long result = super.read(box, Math.min(byteCount, 1L));
- if (result > 0L) sink.write(box.clone(), result);
- return result;
- }
- });
- return result;
- }
-
- @Override public boolean isOneByteAtATime() {
- return true;
- }
-
- @Override public String toString() {
- return "OneByteAtATimeBufferedSource";
- }
- };
-
- Factory ONE_BYTE_AT_A_TIME_BUFFER = new Factory() {
- @Override public Pipe pipe() {
- Buffer buffer = new Buffer();
- Pipe result = new Pipe();
- result.source = buffer;
- result.sink = Okio.buffer(new ForwardingSink(buffer) {
- @Override public void write(Buffer source, long byteCount) throws IOException {
- // Write each byte into a new buffer, then clone it so that the segments are shared.
- // Shared segments cannot be compacted so we'll get a long chain of short segments.
- for (int i = 0; i < byteCount; i++) {
- Buffer box = new Buffer();
- box.write(source, 1);
- super.write(box.clone(), 1);
- }
- }
- });
- return result;
- }
-
- @Override public boolean isOneByteAtATime() {
- return true;
- }
-
- @Override public String toString() {
- return "OneByteAtATimeBuffer";
- }
- };
-
- Factory PEEK_BUFFER = new Factory() {
- @Override public Pipe pipe() {
- Buffer buffer = new Buffer();
- Pipe result = new Pipe();
- result.sink = buffer;
- result.source = buffer.peek();
- return result;
- }
-
- @Override public boolean isOneByteAtATime() {
- return false;
- }
-
- @Override public String toString() {
- return "PeekBuffer";
- }
- };
-
- Factory PEEK_BUFFERED_SOURCE = new Factory() {
- @Override public Pipe pipe() {
- Buffer buffer = new Buffer();
- Pipe result = new Pipe();
- result.sink = buffer;
- result.source = Okio.buffer((Source) buffer).peek();
- return result;
- }
-
- @Override public boolean isOneByteAtATime() {
- return false;
- }
-
- @Override public String toString() {
- return "PeekBufferedSource";
- }
- };
-
- Pipe pipe();
-
- boolean isOneByteAtATime();
- }
-
- private static class Pipe {
- BufferedSink sink;
- BufferedSource source;
- }
-
- @Parameters(name = "{0}")
- public static List<Object[]> parameters() {
- return Arrays.asList(
- new Object[] { Factory.BUFFER },
- new Object[] { Factory.REAL_BUFFERED_SOURCE },
- new Object[] { Factory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE },
- new Object[] { Factory.ONE_BYTE_AT_A_TIME_BUFFER },
- new Object[] { Factory.PEEK_BUFFER },
- new Object[] { Factory.PEEK_BUFFERED_SOURCE });
- }
-
- @Parameter public Factory factory;
- private BufferedSink sink;
- private BufferedSource source;
-
- @Before public void setUp() {
- Pipe pipe = factory.pipe();
- sink = pipe.sink;
- source = pipe.source;
- }
-
- @Test public void readBytes() throws Exception {
- sink.write(new byte[] { (byte) 0xab, (byte) 0xcd });
- sink.emit();
- assertEquals(0xab, source.readByte() & 0xff);
- assertEquals(0xcd, source.readByte() & 0xff);
- assertTrue(source.exhausted());
- }
-
- @Test public void readByteTooShortThrows() throws IOException {
- try {
- source.readByte();
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void readShort() throws Exception {
- sink.write(new byte[] {
- (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x01
- });
- sink.emit();
- assertEquals((short) 0xabcd, source.readShort());
- assertEquals((short) 0xef01, source.readShort());
- assertTrue(source.exhausted());
- }
-
- @Test public void readShortLe() throws Exception {
- sink.write(new byte[] {
- (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x10
- });
- sink.emit();
- assertEquals((short) 0xcdab, source.readShortLe());
- assertEquals((short) 0x10ef, source.readShortLe());
- assertTrue(source.exhausted());
- }
-
- @Test public void readShortSplitAcrossMultipleSegments() throws Exception {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE - 1));
- sink.write(new byte[] { (byte) 0xab, (byte) 0xcd });
- sink.emit();
- source.skip(SEGMENT_SIZE - 1);
- assertEquals((short) 0xabcd, source.readShort());
- assertTrue(source.exhausted());
- }
-
- @Test public void readShortTooShortThrows() throws IOException {
- sink.writeShort(Short.MAX_VALUE);
- sink.emit();
- source.readByte();
- try {
- source.readShort();
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void readShortLeTooShortThrows() throws IOException {
- sink.writeShortLe(Short.MAX_VALUE);
- sink.emit();
- source.readByte();
- try {
- source.readShortLe();
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void readInt() throws Exception {
- sink.write(new byte[] {
- (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x01, (byte) 0x87, (byte) 0x65, (byte) 0x43,
- (byte) 0x21
- });
- sink.emit();
- assertEquals(0xabcdef01, source.readInt());
- assertEquals(0x87654321, source.readInt());
- assertTrue(source.exhausted());
- }
-
- @Test public void readIntLe() throws Exception {
- sink.write(new byte[] {
- (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x10, (byte) 0x87, (byte) 0x65, (byte) 0x43,
- (byte) 0x21
- });
- sink.emit();
- assertEquals(0x10efcdab, source.readIntLe());
- assertEquals(0x21436587, source.readIntLe());
- assertTrue(source.exhausted());
- }
-
- @Test public void readIntSplitAcrossMultipleSegments() throws Exception {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE - 3));
- sink.write(new byte[] {
- (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x01
- });
- sink.emit();
- source.skip(SEGMENT_SIZE - 3);
- assertEquals(0xabcdef01, source.readInt());
- assertTrue(source.exhausted());
- }
-
- @Test public void readIntTooShortThrows() throws IOException {
- sink.writeInt(Integer.MAX_VALUE);
- sink.emit();
- source.readByte();
- try {
- source.readInt();
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void readIntLeTooShortThrows() throws IOException {
- sink.writeIntLe(Integer.MAX_VALUE);
- sink.emit();
- source.readByte();
- try {
- source.readIntLe();
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void readLong() throws Exception {
- sink.write(new byte[] {
- (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x10, (byte) 0x87, (byte) 0x65, (byte) 0x43,
- (byte) 0x21, (byte) 0x36, (byte) 0x47, (byte) 0x58, (byte) 0x69, (byte) 0x12, (byte) 0x23,
- (byte) 0x34, (byte) 0x45
- });
- sink.emit();
- assertEquals(0xabcdef1087654321L, source.readLong());
- assertEquals(0x3647586912233445L, source.readLong());
- assertTrue(source.exhausted());
- }
-
- @Test public void readLongLe() throws Exception {
- sink.write(new byte[] {
- (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x10, (byte) 0x87, (byte) 0x65, (byte) 0x43,
- (byte) 0x21, (byte) 0x36, (byte) 0x47, (byte) 0x58, (byte) 0x69, (byte) 0x12, (byte) 0x23,
- (byte) 0x34, (byte) 0x45
- });
- sink.emit();
- assertEquals(0x2143658710efcdabL, source.readLongLe());
- assertEquals(0x4534231269584736L, source.readLongLe());
- assertTrue(source.exhausted());
- }
-
- @Test public void readLongSplitAcrossMultipleSegments() throws Exception {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE - 7));
- sink.write(new byte[] {
- (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x01, (byte) 0x87, (byte) 0x65, (byte) 0x43,
- (byte) 0x21,
- });
- sink.emit();
- source.skip(SEGMENT_SIZE - 7);
- assertEquals(0xabcdef0187654321L, source.readLong());
- assertTrue(source.exhausted());
- }
-
- @Test public void readLongTooShortThrows() throws IOException {
- sink.writeLong(Long.MAX_VALUE);
- sink.emit();
- source.readByte();
- try {
- source.readLong();
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void readLongLeTooShortThrows() throws IOException {
- sink.writeLongLe(Long.MAX_VALUE);
- sink.emit();
- source.readByte();
- try {
- source.readLongLe();
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void readAll() throws IOException {
- source.getBuffer().writeUtf8("abc");
- sink.writeUtf8("def");
- sink.emit();
-
- Buffer sink = new Buffer();
- assertEquals(6, source.readAll(sink));
- assertEquals("abcdef", sink.readUtf8());
- assertTrue(source.exhausted());
- }
-
- @Test public void readAllExhausted() throws IOException {
- MockSink mockSink = new MockSink();
- assertEquals(0, source.readAll(mockSink));
- assertTrue(source.exhausted());
- mockSink.assertLog();
- }
-
- @Test public void readExhaustedSource() throws Exception {
- Buffer sink = new Buffer();
- sink.writeUtf8(repeat("a", 10));
- assertEquals(-1, source.read(sink, 10));
- assertEquals(10, sink.size());
- assertTrue(source.exhausted());
- }
-
- @Test public void readZeroBytesFromSource() throws Exception {
- Buffer sink = new Buffer();
- sink.writeUtf8(repeat("a", 10));
-
- // Either 0 or -1 is reasonable here. For consistency with Android's
- // ByteArrayInputStream we return 0.
- assertEquals(-1, source.read(sink, 0));
- assertEquals(10, sink.size());
- assertTrue(source.exhausted());
- }
-
- @Test public void readFully() throws Exception {
- sink.writeUtf8(repeat("a", 10000));
- sink.emit();
- Buffer sink = new Buffer();
- source.readFully(sink, 9999);
- assertEquals(repeat("a", 9999), sink.readUtf8());
- assertEquals("a", source.readUtf8());
- }
-
- @Test public void readFullyTooShortThrows() throws IOException {
- sink.writeUtf8("Hi");
- sink.emit();
- Buffer sink = new Buffer();
- try {
- source.readFully(sink, 5);
- fail();
- } catch (EOFException ignored) {
- }
-
- // Verify we read all that we could from the source.
- assertEquals("Hi", sink.readUtf8());
- }
-
- @Test public void readFullyByteArray() throws IOException {
- Buffer data = new Buffer();
- data.writeUtf8("Hello").writeUtf8(repeat("e", SEGMENT_SIZE));
-
- byte[] expected = data.clone().readByteArray();
- sink.write(data, data.size());
- sink.emit();
-
- byte[] sink = new byte[SEGMENT_SIZE + 5];
- source.readFully(sink);
- assertByteArraysEquals(expected, sink);
- }
-
- @Test public void readFullyByteArrayTooShortThrows() throws IOException {
- sink.writeUtf8("Hello");
- sink.emit();
-
- byte[] array = new byte[6];
- try {
- source.readFully(array);
- fail();
- } catch (EOFException ignored) {
- }
-
- // Verify we read all that we could from the source.
- assertByteArraysEquals(new byte[] { 'H', 'e', 'l', 'l', 'o', 0 }, array);
- }
-
- @Test public void readIntoByteArray() throws IOException {
- sink.writeUtf8("abcd");
- sink.emit();
-
- byte[] sink = new byte[3];
- int read = source.read(sink);
- if (factory.isOneByteAtATime()) {
- assertEquals(1, read);
- byte[] expected = { 'a', 0, 0 };
- assertByteArraysEquals(expected, sink);
- } else {
- assertEquals(3, read);
- byte[] expected = { 'a', 'b', 'c' };
- assertByteArraysEquals(expected, sink);
- }
- }
-
- @Test public void readIntoByteArrayNotEnough() throws IOException {
- sink.writeUtf8("abcd");
- sink.emit();
-
- byte[] sink = new byte[5];
- int read = source.read(sink);
- if (factory.isOneByteAtATime()) {
- assertEquals(1, read);
- byte[] expected = { 'a', 0, 0, 0, 0 };
- assertByteArraysEquals(expected, sink);
- } else {
- assertEquals(4, read);
- byte[] expected = { 'a', 'b', 'c', 'd', 0 };
- assertByteArraysEquals(expected, sink);
- }
- }
-
- @Test public void readIntoByteArrayOffsetAndCount() throws IOException {
- sink.writeUtf8("abcd");
- sink.emit();
-
- byte[] sink = new byte[7];
- int read = source.read(sink, 2, 3);
- if (factory.isOneByteAtATime()) {
- assertEquals(1, read);
- byte[] expected = { 0, 0, 'a', 0, 0, 0, 0 };
- assertByteArraysEquals(expected, sink);
- } else {
- assertEquals(3, read);
- byte[] expected = { 0, 0, 'a', 'b', 'c', 0, 0 };
- assertByteArraysEquals(expected, sink);
- }
- }
-
- @Test public void readByteArray() throws IOException {
- String string = "abcd" + repeat("e", SEGMENT_SIZE);
- sink.writeUtf8(string);
- sink.emit();
- assertByteArraysEquals(string.getBytes(UTF_8), source.readByteArray());
- }
-
- @Test public void readByteArrayPartial() throws IOException {
- sink.writeUtf8("abcd");
- sink.emit();
- assertEquals("[97, 98, 99]", Arrays.toString(source.readByteArray(3)));
- assertEquals("d", source.readUtf8(1));
- }
-
- @Test public void readByteArrayTooShortThrows() throws IOException {
- sink.writeUtf8("abc");
- sink.emit();
- try {
- source.readByteArray(4);
- fail();
- } catch (EOFException expected) {
- }
- assertEquals("abc", source.readUtf8()); // The read shouldn't consume any data.
- }
-
- @Test public void readByteString() throws IOException {
- sink.writeUtf8("abcd").writeUtf8(repeat("e", SEGMENT_SIZE));
- sink.emit();
- assertEquals("abcd" + repeat("e", SEGMENT_SIZE), source.readByteString().utf8());
- }
-
- @Test public void readByteStringPartial() throws IOException {
- sink.writeUtf8("abcd").writeUtf8(repeat("e", SEGMENT_SIZE));
- sink.emit();
- assertEquals("abc", source.readByteString(3).utf8());
- assertEquals("d", source.readUtf8(1));
- }
-
- @Test public void readByteStringTooShortThrows() throws IOException {
- sink.writeUtf8("abc");
- sink.emit();
- try {
- source.readByteString(4);
- fail();
- } catch (EOFException expected) {
- }
- assertEquals("abc", source.readUtf8()); // The read shouldn't consume any data.
- }
-
- @Test public void readSpecificCharsetPartial() throws Exception {
- sink.write(ByteString.decodeHex("0000007600000259000002c80000006c000000e40000007300000259"
- + "000002cc000000720000006100000070000000740000025900000072"));
- sink.emit();
- assertEquals("vəˈläsə", source.readString(7 * 4, Charset.forName("utf-32")));
- }
-
- @Test public void readSpecificCharset() throws Exception {
- sink.write(ByteString.decodeHex("0000007600000259000002c80000006c000000e40000007300000259"
- + "000002cc000000720000006100000070000000740000025900000072"));
- sink.emit();
- assertEquals("vəˈläsəˌraptər", source.readString(Charset.forName("utf-32")));
- }
-
- @Test public void readStringTooShortThrows() throws IOException {
- sink.writeString("abc", US_ASCII);
- sink.emit();
- try {
- source.readString(4, US_ASCII);
- fail();
- } catch (EOFException expected) {
- }
- assertEquals("abc", source.readUtf8()); // The read shouldn't consume any data.
- }
-
- @Test public void readUtf8SpansSegments() throws Exception {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE * 2));
- sink.emit();
- source.skip(SEGMENT_SIZE - 1);
- assertEquals("aa", source.readUtf8(2));
- }
-
- @Test public void readUtf8Segment() throws Exception {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE));
- sink.emit();
- assertEquals(repeat("a", SEGMENT_SIZE), source.readUtf8(SEGMENT_SIZE));
- }
-
- @Test public void readUtf8PartialBuffer() throws Exception {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE + 20));
- sink.emit();
- assertEquals(repeat("a", SEGMENT_SIZE + 10), source.readUtf8(SEGMENT_SIZE + 10));
- }
-
- @Test public void readUtf8EntireBuffer() throws Exception {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE * 2));
- sink.emit();
- assertEquals(repeat("a", SEGMENT_SIZE * 2), source.readUtf8());
- }
-
- @Test public void readUtf8TooShortThrows() throws IOException {
- sink.writeUtf8("abc");
- sink.emit();
- try {
- source.readUtf8(4L);
- fail();
- } catch (EOFException expected) {
- }
- assertEquals("abc", source.readUtf8()); // The read shouldn't consume any data.
- }
-
- @Test public void skip() throws Exception {
- sink.writeUtf8("a");
- sink.writeUtf8(repeat("b", SEGMENT_SIZE));
- sink.writeUtf8("c");
- sink.emit();
- source.skip(1);
- assertEquals('b', source.readByte() & 0xff);
- source.skip(SEGMENT_SIZE - 2);
- assertEquals('b', source.readByte() & 0xff);
- source.skip(1);
- assertTrue(source.exhausted());
- }
-
- @Test public void skipInsufficientData() throws Exception {
- sink.writeUtf8("a");
- sink.emit();
-
- try {
- source.skip(2);
- fail();
- } catch (EOFException ignored) {
- }
- }
-
- @Test public void indexOf() throws Exception {
- // The segment is empty.
- assertEquals(-1, source.indexOf((byte) 'a'));
-
- // The segment has one value.
- sink.writeUtf8("a"); // a
- sink.emit();
- assertEquals(0, source.indexOf((byte) 'a'));
- assertEquals(-1, source.indexOf((byte) 'b'));
-
- // The segment has lots of data.
- sink.writeUtf8(repeat("b", SEGMENT_SIZE - 2)); // ab...b
- sink.emit();
- assertEquals(0, source.indexOf((byte) 'a'));
- assertEquals(1, source.indexOf((byte) 'b'));
- assertEquals(-1, source.indexOf((byte) 'c'));
-
- // The segment doesn't start at 0, it starts at 2.
- source.skip(2); // b...b
- assertEquals(-1, source.indexOf((byte) 'a'));
- assertEquals(0, source.indexOf((byte) 'b'));
- assertEquals(-1, source.indexOf((byte) 'c'));
-
- // The segment is full.
- sink.writeUtf8("c"); // b...bc
- sink.emit();
- assertEquals(-1, source.indexOf((byte) 'a'));
- assertEquals(0, source.indexOf((byte) 'b'));
- assertEquals(SEGMENT_SIZE - 3, source.indexOf((byte) 'c'));
-
- // The segment doesn't start at 2, it starts at 4.
- source.skip(2); // b...bc
- assertEquals(-1, source.indexOf((byte) 'a'));
- assertEquals(0, source.indexOf((byte) 'b'));
- assertEquals(SEGMENT_SIZE - 5, source.indexOf((byte) 'c'));
-
- // Two segments.
- sink.writeUtf8("d"); // b...bcd, d is in the 2nd segment.
- sink.emit();
- assertEquals(SEGMENT_SIZE - 4, source.indexOf((byte) 'd'));
- assertEquals(-1, source.indexOf((byte) 'e'));
- }
-
- @Test public void indexOfByteWithStartOffset() throws IOException {
- sink.writeUtf8("a").writeUtf8(repeat("b", SEGMENT_SIZE)).writeUtf8("c");
- sink.emit();
- assertEquals(-1, source.indexOf((byte) 'a', 1));
- assertEquals(15, source.indexOf((byte) 'b', 15));
- }
-
- @Test public void indexOfByteWithBothOffsets() throws IOException {
- if (factory.isOneByteAtATime()) {
- // When run on Travis this causes out-of-memory errors.
- return;
- }
- byte a = (byte) 'a';
- byte c = (byte) 'c';
-
- int size = SEGMENT_SIZE * 5;
- byte[] bytes = new byte[size];
- Arrays.fill(bytes, a);
-
- // These are tricky places where the buffer
- // starts, ends, or segments come together.
- int[] points = {
- 0, 1, 2,
- SEGMENT_SIZE - 1, SEGMENT_SIZE, SEGMENT_SIZE + 1,
- size / 2 - 1, size / 2, size / 2 + 1,
- size - SEGMENT_SIZE - 1, size - SEGMENT_SIZE, size - SEGMENT_SIZE + 1,
- size - 3, size - 2, size - 1
- };
-
- // In each iteration, we write c to the known point and then search for it using different
- // windows. Some of the windows don't overlap with c's position, and therefore a match shouldn't
- // be found.
- for (int p : points) {
- bytes[p] = c;
- sink.write(bytes);
- sink.emit();
-
- assertEquals( p, source.indexOf(c, 0, size ));
- assertEquals( p, source.indexOf(c, 0, p + 1 ));
- assertEquals( p, source.indexOf(c, p, size ));
- assertEquals( p, source.indexOf(c, p, p + 1 ));
- assertEquals( p, source.indexOf(c, p / 2, p * 2 + 1));
- assertEquals(-1, source.indexOf(c, 0, p / 2 ));
- assertEquals(-1, source.indexOf(c, 0, p ));
- assertEquals(-1, source.indexOf(c, 0, 0 ));
- assertEquals(-1, source.indexOf(c, p, p ));
-
- // Reset.
- source.readUtf8();
- bytes[p] = a;
- }
- }
-
- @Test public void indexOfByteInvalidBoundsThrows() throws IOException {
- sink.writeUtf8("abc");
- sink.emit();
-
- try {
- source.indexOf((byte) 'a', -1);
- fail("Expected failure: fromIndex < 0");
- } catch (IllegalArgumentException expected) {
- }
-
- try {
- source.indexOf((byte) 'a', 10, 0);
- fail("Expected failure: fromIndex > toIndex");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test public void indexOfByteString() throws IOException {
- assertEquals(-1, source.indexOf(ByteString.encodeUtf8("flop")));
-
- sink.writeUtf8("flip flop");
- sink.emit();
- assertEquals(5, source.indexOf(ByteString.encodeUtf8("flop")));
- source.readUtf8(); // Clear stream.
-
- // Make sure we backtrack and resume searching after partial match.
- sink.writeUtf8("hi hi hi hey");
- sink.emit();
- assertEquals(3, source.indexOf(ByteString.encodeUtf8("hi hi hey")));
- }
-
- @Test public void indexOfByteStringAtSegmentBoundary() throws IOException {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE - 1));
- sink.writeUtf8("bcd");
- sink.emit();
- assertEquals(SEGMENT_SIZE - 3, source.indexOf(ByteString.encodeUtf8("aabc"), SEGMENT_SIZE - 4));
- assertEquals(SEGMENT_SIZE - 3, source.indexOf(ByteString.encodeUtf8("aabc"), SEGMENT_SIZE - 3));
- assertEquals(SEGMENT_SIZE - 2, source.indexOf(ByteString.encodeUtf8("abcd"), SEGMENT_SIZE - 2));
- assertEquals(SEGMENT_SIZE - 2, source.indexOf(ByteString.encodeUtf8("abc"), SEGMENT_SIZE - 2));
- assertEquals(SEGMENT_SIZE - 2, source.indexOf(ByteString.encodeUtf8("abc"), SEGMENT_SIZE - 2));
- assertEquals(SEGMENT_SIZE - 2, source.indexOf(ByteString.encodeUtf8("ab"), SEGMENT_SIZE - 2));
- assertEquals(SEGMENT_SIZE - 2, source.indexOf(ByteString.encodeUtf8("a"), SEGMENT_SIZE - 2));
- assertEquals(SEGMENT_SIZE - 1, source.indexOf(ByteString.encodeUtf8("bc"), SEGMENT_SIZE - 2));
- assertEquals(SEGMENT_SIZE - 1, source.indexOf(ByteString.encodeUtf8("b"), SEGMENT_SIZE - 2));
- assertEquals(SEGMENT_SIZE, source.indexOf(ByteString.encodeUtf8("c"), SEGMENT_SIZE - 2));
- assertEquals(SEGMENT_SIZE, source.indexOf(ByteString.encodeUtf8("c"), SEGMENT_SIZE ));
- assertEquals(SEGMENT_SIZE + 1, source.indexOf(ByteString.encodeUtf8("d"), SEGMENT_SIZE - 2));
- assertEquals(SEGMENT_SIZE + 1, source.indexOf(ByteString.encodeUtf8("d"), SEGMENT_SIZE + 1));
- }
-
- @Test public void indexOfDoesNotWrapAround() throws IOException {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE - 1));
- sink.writeUtf8("bcd");
- sink.emit();
- assertEquals(-1, source.indexOf(ByteString.encodeUtf8("abcda"), SEGMENT_SIZE - 3));
- }
-
- @Test public void indexOfByteStringWithOffset() throws IOException {
- assertEquals(-1, source.indexOf(ByteString.encodeUtf8("flop"), 1));
-
- sink.writeUtf8("flop flip flop");
- sink.emit();
- assertEquals(10, source.indexOf(ByteString.encodeUtf8("flop"), 1));
- source.readUtf8(); // Clear stream
-
- // Make sure we backtrack and resume searching after partial match.
- sink.writeUtf8("hi hi hi hi hey");
- sink.emit();
- assertEquals(6, source.indexOf(ByteString.encodeUtf8("hi hi hey"), 1));
- }
-
- @Test public void indexOfByteStringInvalidArgumentsThrows() throws IOException {
- try {
- source.indexOf(ByteString.of());
- fail();
- } catch (IllegalArgumentException e) {
- assertEquals("bytes is empty", e.getMessage());
- }
- try {
- source.indexOf(ByteString.encodeUtf8("hi"), -1);
- fail();
- } catch (IllegalArgumentException e) {
- assertEquals("fromIndex < 0: -1", e.getMessage());
- }
- }
-
- /**
- * With {@link Factory#ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE}, this code was extremely slow.
- * https://github.com/square/okio/issues/171
- */
- @Test public void indexOfByteStringAcrossSegmentBoundaries() throws IOException {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE * 2 - 3));
- sink.writeUtf8("bcdefg");
- sink.emit();
- assertEquals(SEGMENT_SIZE * 2 - 4, source.indexOf(ByteString.encodeUtf8("ab")));
- assertEquals(SEGMENT_SIZE * 2 - 4, source.indexOf(ByteString.encodeUtf8("abc")));
- assertEquals(SEGMENT_SIZE * 2 - 4, source.indexOf(ByteString.encodeUtf8("abcd")));
- assertEquals(SEGMENT_SIZE * 2 - 4, source.indexOf(ByteString.encodeUtf8("abcde")));
- assertEquals(SEGMENT_SIZE * 2 - 4, source.indexOf(ByteString.encodeUtf8("abcdef")));
- assertEquals(SEGMENT_SIZE * 2 - 4, source.indexOf(ByteString.encodeUtf8("abcdefg")));
- assertEquals(SEGMENT_SIZE * 2 - 3, source.indexOf(ByteString.encodeUtf8("bcdefg")));
- assertEquals(SEGMENT_SIZE * 2 - 2, source.indexOf(ByteString.encodeUtf8("cdefg")));
- assertEquals(SEGMENT_SIZE * 2 - 1, source.indexOf(ByteString.encodeUtf8("defg")));
- assertEquals(SEGMENT_SIZE * 2, source.indexOf(ByteString.encodeUtf8("efg")));
- assertEquals(SEGMENT_SIZE * 2 + 1, source.indexOf(ByteString.encodeUtf8("fg")));
- assertEquals(SEGMENT_SIZE * 2 + 2, source.indexOf(ByteString.encodeUtf8("g")));
- }
-
- @Test public void indexOfElement() throws IOException {
- sink.writeUtf8("a").writeUtf8(repeat("b", SEGMENT_SIZE)).writeUtf8("c");
- sink.emit();
- assertEquals(0, source.indexOfElement(ByteString.encodeUtf8("DEFGaHIJK")));
- assertEquals(1, source.indexOfElement(ByteString.encodeUtf8("DEFGHIJKb")));
- assertEquals(SEGMENT_SIZE + 1, source.indexOfElement(ByteString.encodeUtf8("cDEFGHIJK")));
- assertEquals(1, source.indexOfElement(ByteString.encodeUtf8("DEFbGHIc")));
- assertEquals(-1L, source.indexOfElement(ByteString.encodeUtf8("DEFGHIJK")));
- assertEquals(-1L, source.indexOfElement(ByteString.encodeUtf8("")));
- }
-
- @Test public void indexOfElementWithOffset() throws IOException {
- sink.writeUtf8("a").writeUtf8(repeat("b", SEGMENT_SIZE)).writeUtf8("c");
- sink.emit();
- assertEquals(-1, source.indexOfElement(ByteString.encodeUtf8("DEFGaHIJK"), 1));
- assertEquals(15, source.indexOfElement(ByteString.encodeUtf8("DEFGHIJKb"), 15));
- }
-
- @Test public void indexOfByteWithFromIndex() throws Exception {
- sink.writeUtf8("aaa");
- sink.emit();
- assertEquals(0, source.indexOf((byte) 'a'));
- assertEquals(0, source.indexOf((byte) 'a', 0));
- assertEquals(1, source.indexOf((byte) 'a', 1));
- assertEquals(2, source.indexOf((byte) 'a', 2));
- }
-
- @Test public void indexOfByteStringWithFromIndex() throws Exception {
- sink.writeUtf8("aaa");
- sink.emit();
- assertEquals(0, source.indexOf(ByteString.encodeUtf8("a")));
- assertEquals(0, source.indexOf(ByteString.encodeUtf8("a"), 0));
- assertEquals(1, source.indexOf(ByteString.encodeUtf8("a"), 1));
- assertEquals(2, source.indexOf(ByteString.encodeUtf8("a"), 2));
- }
-
- @Test public void indexOfElementWithFromIndex() throws Exception {
- sink.writeUtf8("aaa");
- sink.emit();
- assertEquals(0, source.indexOfElement(ByteString.encodeUtf8("a")));
- assertEquals(0, source.indexOfElement(ByteString.encodeUtf8("a"), 0));
- assertEquals(1, source.indexOfElement(ByteString.encodeUtf8("a"), 1));
- assertEquals(2, source.indexOfElement(ByteString.encodeUtf8("a"), 2));
- }
-
- @Test public void request() throws IOException {
- sink.writeUtf8("a").writeUtf8(repeat("b", SEGMENT_SIZE)).writeUtf8("c");
- sink.emit();
- assertTrue(source.request(SEGMENT_SIZE + 2));
- assertFalse(source.request(SEGMENT_SIZE + 3));
- }
-
- @Test public void require() throws IOException {
- sink.writeUtf8("a").writeUtf8(repeat("b", SEGMENT_SIZE)).writeUtf8("c");
- sink.emit();
- source.require(SEGMENT_SIZE + 2);
- try {
- source.require(SEGMENT_SIZE + 3);
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void inputStream() throws Exception {
- sink.writeUtf8("abc");
- sink.emit();
- InputStream in = source.inputStream();
- byte[] bytes = { 'z', 'z', 'z' };
- int read = in.read(bytes);
- if (factory.isOneByteAtATime()) {
- assertEquals(1, read);
- assertByteArrayEquals("azz", bytes);
-
- read = in.read(bytes);
- assertEquals(1, read);
- assertByteArrayEquals("bzz", bytes);
-
- read = in.read(bytes);
- assertEquals(1, read);
- assertByteArrayEquals("czz", bytes);
- } else {
- assertEquals(3, read);
- assertByteArrayEquals("abc", bytes);
- }
-
- assertEquals(-1, in.read());
- }
-
- @Test public void inputStreamOffsetCount() throws Exception {
- sink.writeUtf8("abcde");
- sink.emit();
- InputStream in = source.inputStream();
- byte[] bytes = { 'z', 'z', 'z', 'z', 'z' };
- int read = in.read(bytes, 1, 3);
- if (factory.isOneByteAtATime()) {
- assertEquals(1, read);
- assertByteArrayEquals("zazzz", bytes);
- } else {
- assertEquals(3, read);
- assertByteArrayEquals("zabcz", bytes);
- }
- }
-
- @Test public void inputStreamSkip() throws Exception {
- sink.writeUtf8("abcde");
- sink.emit();
- InputStream in = source.inputStream();
- assertEquals(4, in.skip(4));
- assertEquals('e', in.read());
-
- sink.writeUtf8("abcde");
- sink.emit();
- assertEquals(5, in.skip(10)); // Try to skip too much.
- assertEquals(0, in.skip(1)); // Try to skip when exhausted.
- }
-
- @Test public void inputStreamCharByChar() throws Exception {
- sink.writeUtf8("abc");
- sink.emit();
- InputStream in = source.inputStream();
- assertEquals('a', in.read());
- assertEquals('b', in.read());
- assertEquals('c', in.read());
- assertEquals(-1, in.read());
- }
-
- @Test public void inputStreamBounds() throws IOException {
- sink.writeUtf8(repeat("a", 100));
- sink.emit();
- InputStream in = source.inputStream();
- try {
- in.read(new byte[100], 50, 51);
- fail();
- } catch (ArrayIndexOutOfBoundsException expected) {
- }
- }
-
- @Test public void longHexString() throws IOException {
- assertLongHexString("8000000000000000", 0x8000000000000000L);
- assertLongHexString("fffffffffffffffe", 0xFFFFFFFFFFFFFFFEL);
- assertLongHexString("FFFFFFFFFFFFFFFe", 0xFFFFFFFFFFFFFFFEL);
- assertLongHexString("ffffffffffffffff", 0xffffffffffffffffL);
- assertLongHexString("FFFFFFFFFFFFFFFF", 0xFFFFFFFFFFFFFFFFL);
- assertLongHexString("0000000000000000", 0x0);
- assertLongHexString("0000000000000001", 0x1);
- assertLongHexString("7999999999999999", 0x7999999999999999L);
-
- assertLongHexString("FF", 0xFF);
- assertLongHexString("0000000000000001", 0x1);
- }
-
- @Test public void hexStringWithManyLeadingZeros() throws IOException {
- assertLongHexString("00000000000000001", 0x1);
- assertLongHexString("0000000000000000ffffffffffffffff", 0xffffffffffffffffL);
- assertLongHexString("00000000000000007fffffffffffffff", 0x7fffffffffffffffL);
- assertLongHexString(repeat("0", SEGMENT_SIZE + 1) + "1", 0x1);
- }
-
- private void assertLongHexString(String s, long expected) throws IOException {
- sink.writeUtf8(s);
- sink.emit();
- long actual = source.readHexadecimalUnsignedLong();
- assertEquals(s + " --> " + expected, expected, actual);
- }
-
- @Test public void longHexStringAcrossSegment() throws IOException {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE - 8)).writeUtf8("FFFFFFFFFFFFFFFF");
- sink.emit();
- source.skip(SEGMENT_SIZE - 8);
- assertEquals(-1, source.readHexadecimalUnsignedLong());
- }
-
- @Test public void longHexStringTooLongThrows() throws IOException {
- try {
- sink.writeUtf8("fffffffffffffffff");
- sink.emit();
- source.readHexadecimalUnsignedLong();
- fail();
- } catch (NumberFormatException e) {
- assertEquals("Number too large: fffffffffffffffff", e.getMessage());
- }
- }
-
- @Test public void longHexStringTooShortThrows() throws IOException {
- try {
- sink.writeUtf8(" ");
- sink.emit();
- source.readHexadecimalUnsignedLong();
- fail();
- } catch (NumberFormatException e) {
- assertEquals("Expected leading [0-9a-fA-F] character but was 0x20", e.getMessage());
- }
- }
-
- @Test public void longHexEmptySourceThrows() throws IOException {
- try {
- sink.writeUtf8("");
- sink.emit();
- source.readHexadecimalUnsignedLong();
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void longDecimalString() throws IOException {
- assertLongDecimalString("-9223372036854775808", -9223372036854775808L);
- assertLongDecimalString("-1", -1L);
- assertLongDecimalString("0", 0L);
- assertLongDecimalString("1", 1L);
- assertLongDecimalString("9223372036854775807", 9223372036854775807L);
-
- assertLongDecimalString("00000001", 1L);
- assertLongDecimalString("-000001", -1L);
- }
-
- private void assertLongDecimalString(String s, long expected) throws IOException {
- sink.writeUtf8(s);
- sink.writeUtf8("zzz");
- sink.emit();
- long actual = source.readDecimalLong();
- assertEquals(s + " --> " + expected, expected, actual);
- assertEquals("zzz", source.readUtf8());
- }
-
- @Test public void longDecimalStringAcrossSegment() throws IOException {
- sink.writeUtf8(repeat("a", SEGMENT_SIZE - 8)).writeUtf8("1234567890123456");
- sink.writeUtf8("zzz");
- sink.emit();
- source.skip(SEGMENT_SIZE - 8);
- assertEquals(1234567890123456L, source.readDecimalLong());
- assertEquals("zzz", source.readUtf8());
- }
-
- @Test public void longDecimalStringTooLongThrows() throws IOException {
- try {
- sink.writeUtf8("12345678901234567890"); // Too many digits.
- sink.emit();
- source.readDecimalLong();
- fail();
- } catch (NumberFormatException e) {
- assertEquals("Number too large: 12345678901234567890", e.getMessage());
- }
- }
-
- @Test public void longDecimalStringTooHighThrows() throws IOException {
- try {
- sink.writeUtf8("9223372036854775808"); // Right size but cannot fit.
- sink.emit();
- source.readDecimalLong();
- fail();
- } catch (NumberFormatException e) {
- assertEquals("Number too large: 9223372036854775808", e.getMessage());
- }
- }
-
- @Test public void longDecimalStringTooLowThrows() throws IOException {
- try {
- sink.writeUtf8("-9223372036854775809"); // Right size but cannot fit.
- sink.emit();
- source.readDecimalLong();
- fail();
- } catch (NumberFormatException e) {
- assertEquals("Number too large: -9223372036854775809", e.getMessage());
- }
- }
-
- @Test public void longDecimalStringTooShortThrows() throws IOException {
- try {
- sink.writeUtf8(" ");
- sink.emit();
- source.readDecimalLong();
- fail();
- } catch (NumberFormatException e) {
- assertEquals("Expected leading [0-9] or '-' character but was 0x20", e.getMessage());
- }
- }
-
- @Test public void longDecimalEmptyThrows() throws IOException {
- try {
- sink.writeUtf8("");
- sink.emit();
- source.readDecimalLong();
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void codePoints() throws IOException {
- sink.write(ByteString.decodeHex("7f"));
- sink.emit();
- assertEquals(0x7f, source.readUtf8CodePoint());
-
- sink.write(ByteString.decodeHex("dfbf"));
- sink.emit();
- assertEquals(0x07ff, source.readUtf8CodePoint());
-
- sink.write(ByteString.decodeHex("efbfbf"));
- sink.emit();
- assertEquals(0xffff, source.readUtf8CodePoint());
-
- sink.write(ByteString.decodeHex("f48fbfbf"));
- sink.emit();
- assertEquals(0x10ffff, source.readUtf8CodePoint());
- }
-
- @Test public void decimalStringWithManyLeadingZeros() throws IOException {
- assertLongDecimalString("00000000000000001", 1);
- assertLongDecimalString("00000000000000009223372036854775807", 9223372036854775807L);
- assertLongDecimalString("-00000000000000009223372036854775808", -9223372036854775808L);
- assertLongDecimalString(repeat("0", SEGMENT_SIZE + 1) + "1", 1);
- }
-
- @Test public void select() throws IOException {
- Options options = Options.Companion.of(
- ByteString.encodeUtf8("ROCK"),
- ByteString.encodeUtf8("SCISSORS"),
- ByteString.encodeUtf8("PAPER"));
-
- sink.writeUtf8("PAPER,SCISSORS,ROCK");
- sink.emit();
- assertEquals(2, source.select(options));
- assertEquals(',', source.readByte());
- assertEquals(1, source.select(options));
- assertEquals(',', source.readByte());
- assertEquals(0, source.select(options));
- assertTrue(source.exhausted());
- }
-
- /** Note that this test crashes the VM on Android. */
- @Test public void selectSpanningMultipleSegments() throws IOException {
- ByteString commonPrefix = TestUtil.randomBytes(SEGMENT_SIZE + 10);
- ByteString a = new Buffer().write(commonPrefix).writeUtf8("a").readByteString();
- ByteString bc = new Buffer().write(commonPrefix).writeUtf8("bc").readByteString();
- ByteString bd = new Buffer().write(commonPrefix).writeUtf8("bd").readByteString();
- Options options = Options.Companion.of(a, bc, bd);
-
- sink.write(bd);
- sink.write(a);
- sink.write(bc);
- sink.emit();
-
- assertEquals(2, source.select(options));
- assertEquals(0, source.select(options));
- assertEquals(1, source.select(options));
- assertTrue(source.exhausted());
- }
-
- @Test public void selectNotFound() throws IOException {
- Options options = Options.Companion.of(
- ByteString.encodeUtf8("ROCK"),
- ByteString.encodeUtf8("SCISSORS"),
- ByteString.encodeUtf8("PAPER"));
-
- sink.writeUtf8("SPOCK");
- sink.emit();
- assertEquals(-1, source.select(options));
- assertEquals("SPOCK", source.readUtf8());
- }
-
- @Test public void selectValuesHaveCommonPrefix() throws IOException {
- Options options = Options.Companion.of(
- ByteString.encodeUtf8("abcd"),
- ByteString.encodeUtf8("abce"),
- ByteString.encodeUtf8("abcc"));
-
- sink.writeUtf8("abcc").writeUtf8("abcd").writeUtf8("abce");
- sink.emit();
- assertEquals(2, source.select(options));
- assertEquals(0, source.select(options));
- assertEquals(1, source.select(options));
- }
-
- @Test public void selectLongerThanSource() throws IOException {
- Options options = Options.Companion.of(
- ByteString.encodeUtf8("abcd"),
- ByteString.encodeUtf8("abce"),
- ByteString.encodeUtf8("abcc"));
- sink.writeUtf8("abc");
- sink.emit();
- assertEquals(-1, source.select(options));
- assertEquals("abc", source.readUtf8());
- }
-
- @Test public void selectReturnsFirstByteStringThatMatches() throws IOException {
- Options options = Options.Companion.of(
- ByteString.encodeUtf8("abcd"),
- ByteString.encodeUtf8("abc"),
- ByteString.encodeUtf8("abcde"));
- sink.writeUtf8("abcdef");
- sink.emit();
- assertEquals(0, source.select(options));
- assertEquals("ef", source.readUtf8());
- }
-
- @Test public void selectFromEmptySource() throws IOException {
- Options options = Options.Companion.of(
- ByteString.encodeUtf8("abc"),
- ByteString.encodeUtf8("def"));
- assertEquals(-1, source.select(options));
- }
-
- @Test public void selectNoByteStringsFromEmptySource() throws IOException {
- Options options = Options.of();
- assertEquals(-1, source.select(options));
- }
-
- @Test public void peek() throws IOException {
- sink.writeUtf8("abcdefghi");
- sink.emit();
-
- assertEquals("abc", source.readUtf8(3));
-
- BufferedSource peek = source.peek();
- assertEquals("def", peek.readUtf8(3));
- assertEquals("ghi", peek.readUtf8(3));
- assertFalse(peek.request(1));
-
- assertEquals("def", source.readUtf8(3));
- }
-
- @Test public void peekMultiple() throws IOException {
- sink.writeUtf8("abcdefghi");
- sink.emit();
-
- assertEquals("abc", source.readUtf8(3));
-
- BufferedSource peek1 = source.peek();
- BufferedSource peek2 = source.peek();
-
- assertEquals("def", peek1.readUtf8(3));
-
- assertEquals("def", peek2.readUtf8(3));
- assertEquals("ghi", peek2.readUtf8(3));
- assertFalse(peek2.request(1));
-
- assertEquals("ghi", peek1.readUtf8(3));
- assertFalse(peek1.request(1));
-
- assertEquals("def", source.readUtf8(3));
- }
-
- @Test public void peekLarge() throws IOException {
- sink.writeUtf8("abcdef");
- sink.writeUtf8(repeat("g", 2 * SEGMENT_SIZE));
- sink.writeUtf8("hij");
- sink.emit();
-
- assertEquals("abc", source.readUtf8(3));
-
- BufferedSource peek = source.peek();
- assertEquals("def", peek.readUtf8(3));
- peek.skip(2 * SEGMENT_SIZE);
- assertEquals("hij", peek.readUtf8(3));
- assertFalse(peek.request(1));
-
- assertEquals("def", source.readUtf8(3));
- source.skip(2 * SEGMENT_SIZE);
- assertEquals("hij", source.readUtf8(3));
- }
-
- @Test public void peekInvalid() throws IOException {
- sink.writeUtf8("abcdefghi");
- sink.emit();
-
- assertEquals("abc", source.readUtf8(3));
-
- BufferedSource peek = source.peek();
- assertEquals("def", peek.readUtf8(3));
- assertEquals("ghi", peek.readUtf8(3));
- assertFalse(peek.request(1));
-
- assertEquals("def", source.readUtf8(3));
-
- try {
- peek.readUtf8();
- fail();
- } catch (IllegalStateException e) {
- assertEquals("Peek source is invalid because upstream source was used", e.getMessage());
- }
- }
-
- @Test public void peekSegmentThenInvalid() throws IOException {
- sink.writeUtf8("abc");
- sink.writeUtf8(repeat("d", 2 * SEGMENT_SIZE));
- sink.emit();
-
- assertEquals("abc", source.readUtf8(3));
-
- // Peek a little data and skip the rest of the upstream source
- BufferedSource peek = source.peek();
- assertEquals("ddd", peek.readUtf8(3));
- source.readAll(Okio.blackhole());
-
- // Skip the rest of the buffered data
- peek.skip(peek.getBuffer().size());
-
- try {
- peek.readByte();
- fail();
- } catch (IllegalStateException e) {
- assertEquals("Peek source is invalid because upstream source was used", e.getMessage());
- }
- }
-
- @Test public void peekDoesntReadTooMuch() throws IOException {
- // 6 bytes in source's buffer plus 3 bytes upstream.
- sink.writeUtf8("abcdef");
- sink.emit();
- source.require(6L);
- sink.writeUtf8("ghi");
- sink.emit();
-
- BufferedSource peek = source.peek();
-
- // Read 3 bytes. This reads some of the buffered data.
- assertTrue(peek.request(3));
- if (!(source instanceof Buffer)) {
- assertEquals(6, source.getBuffer().size());
- assertEquals(6, peek.getBuffer().size());
- }
- assertEquals("abc", peek.readUtf8(3L));
-
- // Read 3 more bytes. This exhausts the buffered data.
- assertTrue(peek.request(3));
- if (!(source instanceof Buffer)) {
- assertEquals(6, source.getBuffer().size());
- assertEquals(3, peek.getBuffer().size());
- }
- assertEquals("def", peek.readUtf8(3L));
-
- // Read 3 more bytes. This draws new bytes.
- assertTrue(peek.request(3));
- assertEquals(9, source.getBuffer().size());
- assertEquals(3, peek.getBuffer().size());
- assertEquals("ghi", peek.readUtf8(3L));
- }
-
- @Test public void rangeEquals() throws IOException {
- sink.writeUtf8("A man, a plan, a canal. Panama.");
- sink.emit();
- assertTrue(source.rangeEquals(7 , ByteString.encodeUtf8("a plan")));
- assertTrue(source.rangeEquals(0 , ByteString.encodeUtf8("A man")));
- assertTrue(source.rangeEquals(24, ByteString.encodeUtf8("Panama")));
- assertFalse(source.rangeEquals(24, ByteString.encodeUtf8("Panama. Panama. Panama.")));
- }
-
- @Test public void rangeEqualsWithOffsetAndCount() throws IOException {
- sink.writeUtf8("A man, a plan, a canal. Panama.");
- sink.emit();
- assertTrue(source.rangeEquals(7 , ByteString.encodeUtf8("aaa plannn"), 2, 6));
- assertTrue(source.rangeEquals(0 , ByteString.encodeUtf8("AAA mannn"), 2, 5));
- assertTrue(source.rangeEquals(24, ByteString.encodeUtf8("PPPanamaaa"), 2, 6));
- }
-
- @Test public void rangeEqualsOnlyReadsUntilMismatch() throws IOException {
- assumeTrue(factory == Factory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE); // Other sources read in chunks anyway.
-
- sink.writeUtf8("A man, a plan, a canal. Panama.");
- sink.emit();
- assertFalse(source.rangeEquals(0, ByteString.encodeUtf8("A man.")));
- assertEquals("A man,", source.getBuffer().readUtf8());
- }
-
- @Test public void rangeEqualsArgumentValidation() throws IOException {
- // Negative source offset.
- assertFalse(source.rangeEquals(-1, ByteString.encodeUtf8("A")));
- // Negative bytes offset.
- assertFalse(source.rangeEquals(0, ByteString.encodeUtf8("A"), -1, 1));
- // Bytes offset longer than bytes length.
- assertFalse(source.rangeEquals(0, ByteString.encodeUtf8("A"), 2, 1));
- // Negative byte count.
- assertFalse(source.rangeEquals(0, ByteString.encodeUtf8("A"), 0, -1));
- // Byte count longer than bytes length.
- assertFalse(source.rangeEquals(0, ByteString.encodeUtf8("A"), 0, 2));
- // Bytes offset plus byte count longer than bytes length.
- assertFalse(source.rangeEquals(0, ByteString.encodeUtf8("A"), 1, 1));
- }
-
- @Test public void readNioBuffer() throws Exception {
- String expected = factory.isOneByteAtATime() ? "a" : "abcdefg";
- sink.writeUtf8("abcdefg");
- sink.emit();
-
- ByteBuffer nioByteBuffer = ByteBuffer.allocate(1024);
- int byteCount = source.read(nioByteBuffer);
- assertEquals(expected.length(), byteCount);
- assertEquals(expected.length(), nioByteBuffer.position());
- assertEquals(nioByteBuffer.capacity(), nioByteBuffer.limit());
-
- nioByteBuffer.flip();
- byte[] data = new byte[expected.length()];
- nioByteBuffer.get(data);
- assertEquals(expected, new String(data));
- }
-
- /** Note that this test crashes the VM on Android. */
- @Test public void readLargeNioBufferOnlyReadsOneSegment() throws Exception {
- String expected = factory.isOneByteAtATime()
- ? "a"
- : repeat("a", SEGMENT_SIZE);
- sink.writeUtf8(repeat("a", SEGMENT_SIZE * 4));
- sink.emit();
-
- ByteBuffer nioByteBuffer = ByteBuffer.allocate(SEGMENT_SIZE * 3);
- int byteCount = source.read(nioByteBuffer);
- assertEquals(expected.length(), byteCount);
- assertEquals(expected.length(), nioByteBuffer.position());
- assertEquals(nioByteBuffer.capacity(), nioByteBuffer.limit());
-
- nioByteBuffer.flip();
- byte[] data = new byte[expected.length()];
- nioByteBuffer.get(data);
- assertEquals(expected, new String(data));
- }
-
- @Test public void factorySegmentSizes() throws Exception {
- sink.writeUtf8("abc");
- sink.emit();
- source.require(3);
- if (factory.isOneByteAtATime()) {
- assertEquals(Arrays.asList(1, 1, 1), TestUtil.segmentSizes(source.getBuffer()));
- } else {
- assertEquals(Collections.singletonList(3), TestUtil.segmentSizes(source.getBuffer()));
- }
- }
-}
diff --git a/okio/src/jvmTest/java/okio/ByteStringJavaTest.java b/okio/src/jvmTest/java/okio/ByteStringJavaTest.java
deleted file mode 100644
index f1b6624e..00000000
--- a/okio/src/jvmTest/java/okio/ByteStringJavaTest.java
+++ /dev/null
@@ -1,633 +0,0 @@
-/*
- * Copyright 2014 Square Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Random;
-import kotlin.text.Charsets;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import static okio.TestUtil.assertByteArraysEquals;
-import static okio.TestUtil.assertEquivalent;
-import static okio.TestUtil.makeSegments;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-@RunWith(Parameterized.class)
-public final class ByteStringJavaTest {
- interface Factory {
- Factory BYTE_STRING = new Factory() {
- @Override public ByteString decodeHex(String hex) {
- return ByteString.decodeHex(hex);
- }
-
- @Override public ByteString encodeUtf8(String s) {
- return ByteString.encodeUtf8(s);
- }
- };
-
- Factory SEGMENTED_BYTE_STRING = new Factory() {
- @Override public ByteString decodeHex(String hex) {
- Buffer buffer = new Buffer();
- buffer.write(ByteString.decodeHex(hex));
- return buffer.snapshot();
- }
-
- @Override public ByteString encodeUtf8(String s) {
- Buffer buffer = new Buffer();
- buffer.writeUtf8(s);
- return buffer.snapshot();
- }
- };
-
- Factory ONE_BYTE_PER_SEGMENT = new Factory() {
- @Override public ByteString decodeHex(String hex) {
- return makeSegments(ByteString.decodeHex(hex));
- }
-
- @Override public ByteString encodeUtf8(String s) {
- return makeSegments(ByteString.encodeUtf8(s));
- }
- };
-
- ByteString decodeHex(String hex);
- ByteString encodeUtf8(String s);
- }
-
- @Parameters(name = "{1}")
- public static List<Object[]> parameters() {
- return Arrays.asList(
- new Object[] { Factory.BYTE_STRING, "ByteString" },
- new Object[] { Factory.SEGMENTED_BYTE_STRING, "SegmentedByteString" },
- new Object[] { Factory.ONE_BYTE_PER_SEGMENT, "SegmentedByteString (one-at-a-time)" });
- }
-
- @Parameter(0) public Factory factory;
- @Parameter(1) public String name;
-
- @Test public void ofCopy() {
- byte[] bytes = "Hello, World!".getBytes(Charsets.UTF_8);
- ByteString byteString = ByteString.of(bytes);
- // Verify that the bytes were copied out.
- bytes[4] = (byte) 'a';
- assertEquals("Hello, World!", byteString.utf8());
- }
-
- @Test public void ofCopyRange() {
- byte[] bytes = "Hello, World!".getBytes(Charsets.UTF_8);
- ByteString byteString = ByteString.of(bytes, 2, 9);
- // Verify that the bytes were copied out.
- bytes[4] = (byte) 'a';
- assertEquals("llo, Worl", byteString.utf8());
- }
-
- @Test public void ofByteBuffer() {
- byte[] bytes = "Hello, World!".getBytes(Charsets.UTF_8);
- ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
- byteBuffer.position(2).limit(11);
- ByteString byteString = ByteString.of(byteBuffer);
- // Verify that the bytes were copied out.
- byteBuffer.put(4, (byte) 'a');
- assertEquals("llo, Worl", byteString.utf8());
- }
-
- @Test public void getByte() throws Exception {
- ByteString byteString = factory.decodeHex("ab12");
- assertEquals(-85, byteString.getByte(0));
- assertEquals(18, byteString.getByte(1));
- }
-
- @Test public void getByteOutOfBounds() throws Exception {
- ByteString byteString = factory.decodeHex("ab12");
- try {
- byteString.getByte(2);
- fail();
- } catch (IndexOutOfBoundsException expected) {
- }
- }
-
- @Test public void startsWithByteString() throws Exception {
- ByteString byteString = factory.decodeHex("112233");
- assertTrue(byteString.startsWith(ByteString.decodeHex("")));
- assertTrue(byteString.startsWith(ByteString.decodeHex("11")));
- assertTrue(byteString.startsWith(ByteString.decodeHex("1122")));
- assertTrue(byteString.startsWith(ByteString.decodeHex("112233")));
- assertFalse(byteString.startsWith(ByteString.decodeHex("2233")));
- assertFalse(byteString.startsWith(ByteString.decodeHex("11223344")));
- assertFalse(byteString.startsWith(ByteString.decodeHex("112244")));
- }
-
- @Test public void endsWithByteString() throws Exception {
- ByteString byteString = factory.decodeHex("112233");
- assertTrue(byteString.endsWith(ByteString.decodeHex("")));
- assertTrue(byteString.endsWith(ByteString.decodeHex("33")));
- assertTrue(byteString.endsWith(ByteString.decodeHex("2233")));
- assertTrue(byteString.endsWith(ByteString.decodeHex("112233")));
- assertFalse(byteString.endsWith(ByteString.decodeHex("1122")));
- assertFalse(byteString.endsWith(ByteString.decodeHex("00112233")));
- assertFalse(byteString.endsWith(ByteString.decodeHex("002233")));
- }
-
- @Test public void startsWithByteArray() throws Exception {
- ByteString byteString = factory.decodeHex("112233");
- assertTrue(byteString.startsWith(ByteString.decodeHex("").toByteArray()));
- assertTrue(byteString.startsWith(ByteString.decodeHex("11").toByteArray()));
- assertTrue(byteString.startsWith(ByteString.decodeHex("1122").toByteArray()));
- assertTrue(byteString.startsWith(ByteString.decodeHex("112233").toByteArray()));
- assertFalse(byteString.startsWith(ByteString.decodeHex("2233").toByteArray()));
- assertFalse(byteString.startsWith(ByteString.decodeHex("11223344").toByteArray()));
- assertFalse(byteString.startsWith(ByteString.decodeHex("112244").toByteArray()));
- }
-
- @Test public void endsWithByteArray() throws Exception {
- ByteString byteString = factory.decodeHex("112233");
- assertTrue(byteString.endsWith(ByteString.decodeHex("").toByteArray()));
- assertTrue(byteString.endsWith(ByteString.decodeHex("33").toByteArray()));
- assertTrue(byteString.endsWith(ByteString.decodeHex("2233").toByteArray()));
- assertTrue(byteString.endsWith(ByteString.decodeHex("112233").toByteArray()));
- assertFalse(byteString.endsWith(ByteString.decodeHex("1122").toByteArray()));
- assertFalse(byteString.endsWith(ByteString.decodeHex("00112233").toByteArray()));
- assertFalse(byteString.endsWith(ByteString.decodeHex("002233").toByteArray()));
- }
-
- @Test public void indexOfByteString() throws Exception {
- ByteString byteString = factory.decodeHex("112233");
- assertEquals(0, byteString.indexOf(ByteString.decodeHex("112233")));
- assertEquals(0, byteString.indexOf(ByteString.decodeHex("1122")));
- assertEquals(0, byteString.indexOf(ByteString.decodeHex("11")));
- assertEquals(0, byteString.indexOf(ByteString.decodeHex("11"), 0));
- assertEquals(0, byteString.indexOf(ByteString.decodeHex("")));
- assertEquals(0, byteString.indexOf(ByteString.decodeHex(""), 0));
- assertEquals(1, byteString.indexOf(ByteString.decodeHex("2233")));
- assertEquals(1, byteString.indexOf(ByteString.decodeHex("22")));
- assertEquals(1, byteString.indexOf(ByteString.decodeHex("22"), 1));
- assertEquals(1, byteString.indexOf(ByteString.decodeHex(""), 1));
- assertEquals(2, byteString.indexOf(ByteString.decodeHex("33")));
- assertEquals(2, byteString.indexOf(ByteString.decodeHex("33"), 2));
- assertEquals(2, byteString.indexOf(ByteString.decodeHex(""), 2));
- assertEquals(3, byteString.indexOf(ByteString.decodeHex(""), 3));
- assertEquals(-1, byteString.indexOf(ByteString.decodeHex("112233"), 1));
- assertEquals(-1, byteString.indexOf(ByteString.decodeHex("44")));
- assertEquals(-1, byteString.indexOf(ByteString.decodeHex("11223344")));
- assertEquals(-1, byteString.indexOf(ByteString.decodeHex("112244")));
- assertEquals(-1, byteString.indexOf(ByteString.decodeHex("112233"), 1));
- assertEquals(-1, byteString.indexOf(ByteString.decodeHex("2233"), 2));
- assertEquals(-1, byteString.indexOf(ByteString.decodeHex("33"), 3));
- assertEquals(-1, byteString.indexOf(ByteString.decodeHex(""), 4));
- }
-
- @Test public void indexOfWithOffset() throws Exception {
- ByteString byteString = factory.decodeHex("112233112233");
- assertEquals(0, byteString.indexOf(ByteString.decodeHex("112233"), -1));
- assertEquals(0, byteString.indexOf(ByteString.decodeHex("112233"), 0));
- assertEquals(0, byteString.indexOf(ByteString.decodeHex("112233")));
- assertEquals(3, byteString.indexOf(ByteString.decodeHex("112233"), 1));
- assertEquals(3, byteString.indexOf(ByteString.decodeHex("112233"), 2));
- assertEquals(3, byteString.indexOf(ByteString.decodeHex("112233"), 3));
- assertEquals(-1, byteString.indexOf(ByteString.decodeHex("112233"), 4));
- }
-
- @Test public void indexOfByteArray() throws Exception {
- ByteString byteString = factory.decodeHex("112233");
- assertEquals(0, byteString.indexOf(ByteString.decodeHex("112233").toByteArray()));
- assertEquals(1, byteString.indexOf(ByteString.decodeHex("2233").toByteArray()));
- assertEquals(2, byteString.indexOf(ByteString.decodeHex("33").toByteArray()));
- assertEquals(-1, byteString.indexOf(ByteString.decodeHex("112244").toByteArray()));
- }
-
- @Test public void lastIndexOfByteString() throws Exception {
- ByteString byteString = factory.decodeHex("112233");
- assertEquals(0, byteString.lastIndexOf(ByteString.decodeHex("112233")));
- assertEquals(0, byteString.lastIndexOf(ByteString.decodeHex("1122")));
- assertEquals(0, byteString.lastIndexOf(ByteString.decodeHex("11")));
- assertEquals(0, byteString.lastIndexOf(ByteString.decodeHex("11"), 3));
- assertEquals(0, byteString.lastIndexOf(ByteString.decodeHex("11"), 0));
- assertEquals(0, byteString.lastIndexOf(ByteString.decodeHex(""), 0));
- assertEquals(1, byteString.lastIndexOf(ByteString.decodeHex("2233")));
- assertEquals(1, byteString.lastIndexOf(ByteString.decodeHex("22")));
- assertEquals(1, byteString.lastIndexOf(ByteString.decodeHex("22"), 3));
- assertEquals(1, byteString.lastIndexOf(ByteString.decodeHex("22"), 1));
- assertEquals(1, byteString.lastIndexOf(ByteString.decodeHex(""), 1));
- assertEquals(2, byteString.lastIndexOf(ByteString.decodeHex("33")));
- assertEquals(2, byteString.lastIndexOf(ByteString.decodeHex("33"), 3));
- assertEquals(2, byteString.lastIndexOf(ByteString.decodeHex("33"), 2));
- assertEquals(2, byteString.lastIndexOf(ByteString.decodeHex(""), 2));
- assertEquals(3, byteString.lastIndexOf(ByteString.decodeHex(""), 3));
- assertEquals(3, byteString.lastIndexOf(ByteString.decodeHex("")));
- assertEquals(-1, byteString.lastIndexOf(ByteString.decodeHex("112233"), -1));
- assertEquals(-1, byteString.lastIndexOf(ByteString.decodeHex("112233"), -2));
- assertEquals(-1, byteString.lastIndexOf(ByteString.decodeHex("44")));
- assertEquals(-1, byteString.lastIndexOf(ByteString.decodeHex("11223344")));
- assertEquals(-1, byteString.lastIndexOf(ByteString.decodeHex("112244")));
- assertEquals(-1, byteString.lastIndexOf(ByteString.decodeHex("2233"), 0));
- assertEquals(-1, byteString.lastIndexOf(ByteString.decodeHex("33"), 1));
- assertEquals(-1, byteString.lastIndexOf(ByteString.decodeHex(""), -1));
- }
-
- @Test public void lastIndexOfByteArray() throws Exception {
- ByteString byteString = factory.decodeHex("112233");
- assertEquals(0, byteString.lastIndexOf(ByteString.decodeHex("112233").toByteArray()));
- assertEquals(1, byteString.lastIndexOf(ByteString.decodeHex("2233").toByteArray()));
- assertEquals(2, byteString.lastIndexOf(ByteString.decodeHex("33").toByteArray()));
- assertEquals(3, byteString.lastIndexOf(ByteString.decodeHex("").toByteArray()));
- }
-
- @SuppressWarnings("SelfEquals")
- @Test public void equals() throws Exception {
- ByteString byteString = factory.decodeHex("000102");
- assertTrue(byteString.equals(byteString));
- assertTrue(byteString.equals(ByteString.decodeHex("000102")));
- assertTrue(factory.decodeHex("").equals(ByteString.EMPTY));
- assertTrue(factory.decodeHex("").equals(ByteString.of()));
- assertTrue(ByteString.EMPTY.equals(factory.decodeHex("")));
- assertTrue(ByteString.of().equals(factory.decodeHex("")));
- assertFalse(byteString.equals(new Object()));
- assertFalse(byteString.equals(ByteString.decodeHex("000201")));
- }
-
- private final String bronzeHorseman = "На берегу пустынных волн";
-
- @Test public void utf8() throws Exception {
- ByteString byteString = factory.encodeUtf8(bronzeHorseman);
- assertByteArraysEquals(byteString.toByteArray(), bronzeHorseman.getBytes(Charsets.UTF_8));
- assertTrue(byteString.equals(ByteString.of(bronzeHorseman.getBytes(Charsets.UTF_8))));
- assertEquals(byteString.utf8(), bronzeHorseman);
- }
-
- @Test public void encodeNullCharset() throws Exception {
- try {
- ByteString.encodeString("hello", null);
- fail();
- } catch (NullPointerException expected) {
- }
- }
-
- @Test public void encodeNullString() throws Exception {
- try {
- ByteString.encodeString(null, Charset.forName("UTF-8"));
- fail();
- } catch (NullPointerException expected) {
- }
- }
-
- @Test public void decodeNullCharset() throws Exception {
- try {
- ByteString.of().string(null);
- fail();
- } catch (NullPointerException expected) {
- }
- }
-
- @Test public void encodeDecodeStringUtf8() throws Exception {
- Charset utf8 = Charset.forName("UTF-8");
- ByteString byteString = ByteString.encodeString(bronzeHorseman, utf8);
- assertByteArraysEquals(byteString.toByteArray(), bronzeHorseman.getBytes(utf8));
- assertEquals(byteString, ByteString.decodeHex("d09dd0b020d0b1d0b5d180d0b5d0b3d18320d0bfd183d181"
- + "d182d18bd0bdd0bdd18bd18520d0b2d0bed0bbd0bd"));
- assertEquals(bronzeHorseman, byteString.string(utf8));
- }
-
- @Test public void encodeDecodeStringUtf16be() throws Exception {
- Charset utf16be = Charset.forName("UTF-16BE");
- ByteString byteString = ByteString.encodeString(bronzeHorseman, utf16be);
- assertByteArraysEquals(byteString.toByteArray(), bronzeHorseman.getBytes(utf16be));
- assertEquals(byteString, ByteString.decodeHex("041d043000200431043504400435043304430020043f0443"
- + "04410442044b043d043d044b044500200432043e043b043d"));
- assertEquals(bronzeHorseman, byteString.string(utf16be));
- }
-
- @Test public void encodeDecodeStringUtf32be() throws Exception {
- Charset utf32be = Charset.forName("UTF-32BE");
- ByteString byteString = ByteString.encodeString(bronzeHorseman, utf32be);
- assertByteArraysEquals(byteString.toByteArray(), bronzeHorseman.getBytes(utf32be));
- assertEquals(byteString, ByteString.decodeHex("0000041d0000043000000020000004310000043500000440"
- + "000004350000043300000443000000200000043f0000044300000441000004420000044b0000043d0000043d"
- + "0000044b0000044500000020000004320000043e0000043b0000043d"));
- assertEquals(bronzeHorseman, byteString.string(utf32be));
- }
-
- @Test public void encodeDecodeStringAsciiIsLossy() throws Exception {
- Charset ascii = Charset.forName("US-ASCII");
- ByteString byteString = ByteString.encodeString(bronzeHorseman, ascii);
- assertByteArraysEquals(byteString.toByteArray(), bronzeHorseman.getBytes(ascii));
- assertEquals(byteString,
- ByteString.decodeHex("3f3f203f3f3f3f3f3f203f3f3f3f3f3f3f3f3f203f3f3f3f"));
- assertEquals("?? ?????? ????????? ????", byteString.string(ascii));
- }
-
- @Test public void decodeMalformedStringReturnsReplacementCharacter() throws Exception {
- Charset utf16be = Charset.forName("UTF-16BE");
- String string = ByteString.decodeHex("04").string(utf16be);
- assertEquals("\ufffd", string);
- }
-
- @Test public void testHashCode() throws Exception {
- ByteString byteString = factory.decodeHex("0102");
- assertEquals(byteString.hashCode(), byteString.hashCode());
- assertEquals(byteString.hashCode(), ByteString.decodeHex("0102").hashCode());
- }
-
- @Test public void read() throws Exception {
- InputStream in = new ByteArrayInputStream("abc".getBytes(Charsets.UTF_8));
- assertEquals(ByteString.decodeHex("6162"), ByteString.read(in, 2));
- assertEquals(ByteString.decodeHex("63"), ByteString.read(in, 1));
- assertEquals(ByteString.of(), ByteString.read(in, 0));
- }
-
- @Test public void readAndToLowercase() throws Exception {
- InputStream in = new ByteArrayInputStream("ABC".getBytes(Charsets.UTF_8));
- assertEquals(ByteString.encodeUtf8("ab"), ByteString.read(in, 2).toAsciiLowercase());
- assertEquals(ByteString.encodeUtf8("c"), ByteString.read(in, 1).toAsciiLowercase());
- assertEquals(ByteString.EMPTY, ByteString.read(in, 0).toAsciiLowercase());
- }
-
- @Test public void toAsciiLowerCaseNoUppercase() throws Exception {
- ByteString s = factory.encodeUtf8("a1_+");
- assertEquals(s, s.toAsciiLowercase());
- if (factory == Factory.BYTE_STRING) {
- assertSame(s, s.toAsciiLowercase());
- }
- }
-
- @Test public void toAsciiAllUppercase() throws Exception {
- assertEquals(ByteString.encodeUtf8("ab"), factory.encodeUtf8("AB").toAsciiLowercase());
- }
-
- @Test public void toAsciiStartsLowercaseEndsUppercase() throws Exception {
- assertEquals(ByteString.encodeUtf8("abcd"), factory.encodeUtf8("abCD").toAsciiLowercase());
- }
-
- @Test public void readAndToUppercase() throws Exception {
- InputStream in = new ByteArrayInputStream("abc".getBytes(Charsets.UTF_8));
- assertEquals(ByteString.encodeUtf8("AB"), ByteString.read(in, 2).toAsciiUppercase());
- assertEquals(ByteString.encodeUtf8("C"), ByteString.read(in, 1).toAsciiUppercase());
- assertEquals(ByteString.EMPTY, ByteString.read(in, 0).toAsciiUppercase());
- }
-
- @Test public void toAsciiStartsUppercaseEndsLowercase() throws Exception {
- assertEquals(ByteString.encodeUtf8("ABCD"), factory.encodeUtf8("ABcd").toAsciiUppercase());
- }
-
- @Test public void substring() throws Exception {
- ByteString byteString = factory.encodeUtf8("Hello, World!");
-
- assertEquals(byteString.substring(0), byteString);
- assertEquals(byteString.substring(0, 5), ByteString.encodeUtf8("Hello"));
- assertEquals(byteString.substring(7), ByteString.encodeUtf8("World!"));
- assertEquals(byteString.substring(6, 6), ByteString.encodeUtf8(""));
- }
-
- @Test public void substringWithInvalidBounds() throws Exception {
- ByteString byteString = factory.encodeUtf8("Hello, World!");
-
- try {
- byteString.substring(-1);
- fail();
- } catch (IllegalArgumentException expected) {
- }
-
- try {
- byteString.substring(0, 14);
- fail();
- } catch (IllegalArgumentException expected) {
- }
-
- try {
- byteString.substring(8, 7);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test public void write() throws Exception {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- factory.decodeHex("616263").write(out);
- assertByteArraysEquals(new byte[] { 0x61, 0x62, 0x63 }, out.toByteArray());
- }
-
- @Test public void encodeBase64() {
- assertEquals("", factory.encodeUtf8("").base64());
- assertEquals("AA==", factory.encodeUtf8("\u0000").base64());
- assertEquals("AAA=", factory.encodeUtf8("\u0000\u0000").base64());
- assertEquals("AAAA", factory.encodeUtf8("\u0000\u0000\u0000").base64());
- assertEquals("SG93IG1hbnkgbGluZXMgb2YgY29kZSBhcmUgdGhlcmU/ICdib3V0IDIgbWlsbGlvbi4=",
- factory.encodeUtf8("How many lines of code are there? 'bout 2 million.").base64());
- }
-
- @Test public void encodeBase64Url() {
- assertEquals("", factory.encodeUtf8("").base64Url());
- assertEquals("AA==", factory.encodeUtf8("\u0000").base64Url());
- assertEquals("AAA=", factory.encodeUtf8("\u0000\u0000").base64Url());
- assertEquals("AAAA", factory.encodeUtf8("\u0000\u0000\u0000").base64Url());
- assertEquals("SG93IG1hbnkgbGluZXMgb2YgY29kZSBhcmUgdGhlcmU_ICdib3V0IDIgbWlsbGlvbi4=",
- factory.encodeUtf8("How many lines of code are there? 'bout 2 million.").base64Url());
- }
-
- @Test public void ignoreUnnecessaryPadding() {
- assertEquals("", ByteString.decodeBase64("====").utf8());
- assertEquals("\u0000\u0000\u0000", ByteString.decodeBase64("AAAA====").utf8());
- }
-
- @Test public void decodeBase64() {
- assertEquals("", ByteString.decodeBase64("").utf8());
- assertEquals(null, ByteString.decodeBase64("/===")); // Can't do anything with 6 bits!
- assertEquals(ByteString.decodeHex("ff"), ByteString.decodeBase64("//=="));
- assertEquals(ByteString.decodeHex("ff"), ByteString.decodeBase64("__=="));
- assertEquals(ByteString.decodeHex("ffff"), ByteString.decodeBase64("///="));
- assertEquals(ByteString.decodeHex("ffff"), ByteString.decodeBase64("___="));
- assertEquals(ByteString.decodeHex("ffffff"), ByteString.decodeBase64("////"));
- assertEquals(ByteString.decodeHex("ffffff"), ByteString.decodeBase64("____"));
- assertEquals(ByteString.decodeHex("ffffffffffff"), ByteString.decodeBase64("////////"));
- assertEquals(ByteString.decodeHex("ffffffffffff"), ByteString.decodeBase64("________"));
- assertEquals("What's to be scared about? It's just a little hiccup in the power...",
- ByteString.decodeBase64("V2hhdCdzIHRvIGJlIHNjYXJlZCBhYm91dD8gSXQncyBqdXN0IGEgbGl0dGxlIGhpY2"
- + "N1cCBpbiB0aGUgcG93ZXIuLi4=").utf8());
- // Uses two encoding styles. Malformed, but supported as a side-effect.
- assertEquals(ByteString.decodeHex("ffffff"), ByteString.decodeBase64("__//"));
- }
-
- @Test public void decodeBase64WithWhitespace() {
- assertEquals("\u0000\u0000\u0000", ByteString.decodeBase64(" AA AA ").utf8());
- assertEquals("\u0000\u0000\u0000", ByteString.decodeBase64(" AA A\r\nA ").utf8());
- assertEquals("\u0000\u0000\u0000", ByteString.decodeBase64("AA AA").utf8());
- assertEquals("\u0000\u0000\u0000", ByteString.decodeBase64(" AA AA ").utf8());
- assertEquals("\u0000\u0000\u0000", ByteString.decodeBase64(" AA A\r\nA ").utf8());
- assertEquals("\u0000\u0000\u0000", ByteString.decodeBase64("A AAA").utf8());
- assertEquals("", ByteString.decodeBase64(" ").utf8());
- }
-
- @Test public void encodeHex() throws Exception {
- assertEquals("000102", ByteString.of((byte) 0x0, (byte) 0x1, (byte) 0x2).hex());
- }
-
- @Test public void decodeHex() throws Exception {
- assertEquals(ByteString.of((byte) 0x0, (byte) 0x1, (byte) 0x2), ByteString.decodeHex("000102"));
- }
-
- @Test public void decodeHexOddNumberOfChars() throws Exception {
- try {
- ByteString.decodeHex("aaa");
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test public void decodeHexInvalidChar() throws Exception {
- try {
- ByteString.decodeHex("a\u0000");
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test public void toStringOnEmpty() {
- assertEquals("[size=0]", factory.decodeHex("").toString());
- }
-
- @Test public void toStringOnShortText() {
- assertEquals("[text=Tyrannosaur]",
- factory.encodeUtf8("Tyrannosaur").toString());
- assertEquals("[text=təˈranəˌsôr]",
- factory.decodeHex("74c999cb8872616ec999cb8c73c3b472").toString());
- }
-
- @Test public void toStringOnLongTextIsTruncated() {
- String raw = "Um, I'll tell you the problem with the scientific power that you're using here, "
- + "it didn't require any discipline to attain it. You read what others had done and you "
- + "took the next step. You didn't earn the knowledge for yourselves, so you don't take any "
- + "responsibility for it. You stood on the shoulders of geniuses to accomplish something "
- + "as fast as you could, and before you even knew what you had, you patented it, and "
- + "packaged it, and slapped it on a plastic lunchbox, and now you're selling it, you wanna "
- + "sell it.";
- assertEquals("[size=517 text=Um, I'll tell you the problem with the scientific power that "
- + "you…]", factory.encodeUtf8(raw).toString());
- String war = "Սm, I'll 𝓽𝖾ll ᶌօ𝘂 ᴛℎ℮ 𝜚𝕣०bl𝖾m wі𝕥𝒽 𝘵𝘩𝐞 𝓼𝙘𝐢𝔢𝓷𝗍𝜄𝚏𝑖c 𝛠𝝾w𝚎𝑟 𝕥h⍺𝞃 𝛄𝓸𝘂'𝒓𝗲 υ𝖘𝓲𝗇ɡ 𝕙𝚎𝑟e, "
- + "𝛊𝓽 ⅆ𝕚𝐝𝝿'𝗍 𝔯𝙚𝙦ᴜ𝜾𝒓𝘦 𝔞𝘯𝐲 ԁ𝜄𝑠𝚌ι𝘱lι𝒏e 𝑡𝜎 𝕒𝚝𝖙𝓪і𝞹 𝔦𝚝. 𝒀ο𝗎 𝔯𝑒⍺𝖉 w𝐡𝝰𝔱 𝞂𝞽һ𝓮𝓇ƽ հ𝖺𝖉 ⅾ𝛐𝝅ⅇ 𝝰πԁ 𝔂ᴑᴜ 𝓉ﮨ၀𝚔 "
- + "т𝒽𝑒 𝗇𝕖ⅹ𝚝 𝔰𝒕е𝓅. 𝘠ⲟ𝖚 𝖉ⅰԁ𝝕'τ 𝙚𝚊r𝞹 𝘵Ꮒ𝖾 𝝒𝐧هwl𝑒𝖉ƍ𝙚 𝓯૦r 𝔂𝞼𝒖𝕣𝑠𝕖l𝙫𝖊𝓼, 𐑈о y𝘰𝒖 ⅆە𝗇't 𝜏α𝒌𝕖 𝛂𝟉ℽ "
- + "𝐫ⅇ𝗌ⲣ๐ϖ𝖘ꙇᖯ𝓲l𝓲𝒕𝘆 𝐟𝞼𝘳 𝚤𝑡. 𝛶𝛔𝔲 s𝕥σσ𝐝 ﮩ𝕟 𝒕𝗁𝔢 𝘴𝐡𝜎ᴜlⅾ𝓮𝔯𝚜 𝛐𝙛 ᶃ𝚎ᴨᎥս𝚜𝘦𝓈 𝓽𝞸 a𝒄𝚌𝞸mρl𝛊ꜱ𝐡 𝓈𝚘m𝚎𝞃𝔥⍳𝞹𝔤 𝐚𝗌 𝖋a𝐬𝒕 "
- + "αs γ𝛐𝕦 𝔠ﻫ𝛖lԁ, 𝚊π𝑑 Ь𝑒𝙛૦𝓇𝘦 𝓎٥𝖚 ⅇvℯ𝝅 𝜅ո𝒆w w𝗵𝒂𝘁 ᶌ੦𝗎 h𝐚𝗱, 𝜸ﮨ𝒖 𝓹𝝰𝔱𝖾𝗇𝓽𝔢ⅆ і𝕥, 𝚊𝜛𝓭 𝓹𝖺ⅽϰ𝘢ℊеᏧ 𝑖𝞃, "
- + "𝐚𝛑ꓒ 𝙨l𝔞р𝘱𝔢𝓭 ɩ𝗍 ہ𝛑 𝕒 pl𝛂ѕᴛ𝗂𝐜 l𝞄ℼ𝔠𝒽𝑏ﮪ⨯, 𝔞ϖ𝒹 n𝛔w 𝛾𝐨𝞄'𝗿𝔢 ꜱ℮ll𝙞nɡ ɩ𝘁, 𝙮𝕠𝛖 w𝑎ℼ𝚗𝛂 𝕤𝓮ll 𝙞𝓉.";
- assertEquals( "[size=1496 text=Սm, I'll 𝓽𝖾ll ᶌօ𝘂 ᴛℎ℮ 𝜚𝕣०bl𝖾m wі𝕥𝒽 𝘵𝘩𝐞 𝓼𝙘𝐢𝔢𝓷𝗍𝜄𝚏𝑖c 𝛠𝝾w𝚎𝑟 𝕥h⍺𝞃 "
- + "𝛄𝓸𝘂…]", factory.encodeUtf8(war).toString());
- }
-
- @Test public void toStringOnTextWithNewlines() {
- // Instead of emitting a literal newline in the toString(), these are escaped as "\n".
- assertEquals("[text=a\\r\\nb\\nc\\rd\\\\e]",
- factory.encodeUtf8("a\r\nb\nc\rd\\e").toString());
- }
-
- @Test public void toStringOnData() {
- ByteString byteString = factory.decodeHex(""
- + "60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55"
- + "4bf0b54023c29b624de9ef9c2f931efc580f9afb");
- assertEquals("[hex="
- + "60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55"
- + "4bf0b54023c29b624de9ef9c2f931efc580f9afb]", byteString.toString());
- }
-
- @Test public void toStringOnLongDataIsTruncated() {
- ByteString byteString = factory.decodeHex(""
- + "60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55"
- + "4bf0b54023c29b624de9ef9c2f931efc580f9afba1");
- assertEquals("[size=65 hex="
- + "60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55"
- + "4bf0b54023c29b624de9ef9c2f931efc580f9afb…]", byteString.toString());
- }
-
- @Test public void javaSerializationTestNonEmpty() throws Exception {
- ByteString byteString = factory.encodeUtf8(bronzeHorseman);
- assertEquivalent(byteString, TestUtil.<ByteString>reserialize(byteString));
- }
-
- @Test public void javaSerializationTestEmpty() throws Exception {
- ByteString byteString = factory.decodeHex("");
- assertEquivalent(byteString, TestUtil.<ByteString>reserialize(byteString));
- }
-
- @Test public void compareToSingleBytes() throws Exception {
- List<ByteString> originalByteStrings = Arrays.asList(
- factory.decodeHex("00"),
- factory.decodeHex("01"),
- factory.decodeHex("7e"),
- factory.decodeHex("7f"),
- factory.decodeHex("80"),
- factory.decodeHex("81"),
- factory.decodeHex("fe"),
- factory.decodeHex("ff"));
-
- List<ByteString> sortedByteStrings = new ArrayList<>(originalByteStrings);
- Collections.shuffle(sortedByteStrings, new Random(0));
- Collections.sort(sortedByteStrings);
-
- assertEquals(originalByteStrings, sortedByteStrings);
- }
-
- @Test public void compareToMultipleBytes() throws Exception {
- List<ByteString> originalByteStrings = Arrays.asList(
- factory.decodeHex(""),
- factory.decodeHex("00"),
- factory.decodeHex("0000"),
- factory.decodeHex("000000"),
- factory.decodeHex("00000000"),
- factory.decodeHex("0000000000"),
- factory.decodeHex("0000000001"),
- factory.decodeHex("000001"),
- factory.decodeHex("00007f"),
- factory.decodeHex("0000ff"),
- factory.decodeHex("000100"),
- factory.decodeHex("000101"),
- factory.decodeHex("007f00"),
- factory.decodeHex("00ff00"),
- factory.decodeHex("010000"),
- factory.decodeHex("010001"),
- factory.decodeHex("01007f"),
- factory.decodeHex("0100ff"),
- factory.decodeHex("010100"),
- factory.decodeHex("01010000"),
- factory.decodeHex("0101000000"),
- factory.decodeHex("0101000001"),
- factory.decodeHex("010101"),
- factory.decodeHex("7f0000"),
- factory.decodeHex("7f0000ffff"),
- factory.decodeHex("ffffff"));
-
- List<ByteString> sortedByteStrings = new ArrayList<>(originalByteStrings);
- Collections.shuffle(sortedByteStrings, new Random(0));
- Collections.sort(sortedByteStrings);
-
- assertEquals(originalByteStrings, sortedByteStrings);
- }
-
- @Test public void asByteBuffer() {
- assertEquals(0x42, ByteString.of((byte) 0x41, (byte) 0x42, (byte) 0x43).asByteBuffer().get(1));
- }
-}
diff --git a/okio/src/jvmTest/java/okio/DeflaterSinkTest.java b/okio/src/jvmTest/java/okio/DeflaterSinkTest.java
deleted file mode 100644
index f0a31f00..00000000
--- a/okio/src/jvmTest/java/okio/DeflaterSinkTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.zip.Deflater;
-import java.util.zip.Inflater;
-import java.util.zip.InflaterInputStream;
-import org.junit.Test;
-
-import static kotlin.text.StringsKt.repeat;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static okio.TestUtil.randomBytes;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-public final class DeflaterSinkTest {
- @Test public void deflateWithClose() throws Exception {
- Buffer data = new Buffer();
- String original = "They're moving in herds. They do move in herds.";
- data.writeUtf8(original);
- Buffer sink = new Buffer();
- DeflaterSink deflaterSink = new DeflaterSink(sink, new Deflater());
- deflaterSink.write(data, data.size());
- deflaterSink.close();
- Buffer inflated = inflate(sink);
- assertEquals(original, inflated.readUtf8());
- }
-
- @Test public void deflateWithSyncFlush() throws Exception {
- String original = "Yes, yes, yes. That's why we're taking extreme precautions.";
- Buffer data = new Buffer();
- data.writeUtf8(original);
- Buffer sink = new Buffer();
- DeflaterSink deflaterSink = new DeflaterSink(sink, new Deflater());
- deflaterSink.write(data, data.size());
- deflaterSink.flush();
- Buffer inflated = inflate(sink);
- assertEquals(original, inflated.readUtf8());
- }
-
- @Test public void deflateWellCompressed() throws IOException {
- String original = repeat("a", 1024 * 1024);
- Buffer data = new Buffer();
- data.writeUtf8(original);
- Buffer sink = new Buffer();
- DeflaterSink deflaterSink = new DeflaterSink(sink, new Deflater());
- deflaterSink.write(data, data.size());
- deflaterSink.close();
- Buffer inflated = inflate(sink);
- assertEquals(original, inflated.readUtf8());
- }
-
- @Test public void deflatePoorlyCompressed() throws IOException {
- ByteString original = randomBytes(1024 * 1024);
- Buffer data = new Buffer();
- data.write(original);
- Buffer sink = new Buffer();
- DeflaterSink deflaterSink = new DeflaterSink(sink, new Deflater());
- deflaterSink.write(data, data.size());
- deflaterSink.close();
- Buffer inflated = inflate(sink);
- assertEquals(original, inflated.readByteString());
- }
-
- @Test public void multipleSegmentsWithoutCompression() throws IOException {
- Buffer buffer = new Buffer();
- Deflater deflater = new Deflater();
- deflater.setLevel(Deflater.NO_COMPRESSION);
- DeflaterSink deflaterSink = new DeflaterSink(buffer, deflater);
- int byteCount = SEGMENT_SIZE * 4;
- deflaterSink.write(new Buffer().writeUtf8(repeat("a", byteCount)), byteCount);
- deflaterSink.close();
- assertEquals(repeat("a", byteCount), inflate(buffer).readUtf8(byteCount));
- }
-
- @Test public void deflateIntoNonemptySink() throws Exception {
- String original = "They're moving in herds. They do move in herds.";
-
- // Exercise all possible offsets for the outgoing segment.
- for (int i = 0; i < SEGMENT_SIZE; i++) {
- Buffer data = new Buffer().writeUtf8(original);
- Buffer sink = new Buffer().writeUtf8(repeat("a", i));
-
- DeflaterSink deflaterSink = new DeflaterSink(sink, new Deflater());
- deflaterSink.write(data, data.size());
- deflaterSink.close();
-
- sink.skip(i);
- Buffer inflated = inflate(sink);
- assertEquals(original, inflated.readUtf8());
- }
- }
-
- /**
- * This test deflates a single segment of without compression because that's
- * the easiest way to force close() to emit a large amount of data to the
- * underlying sink.
- */
- @Test public void closeWithExceptionWhenWritingAndClosing() throws IOException {
- MockSink mockSink = new MockSink();
- mockSink.scheduleThrow(0, new IOException("first"));
- mockSink.scheduleThrow(1, new IOException("second"));
- Deflater deflater = new Deflater();
- deflater.setLevel(Deflater.NO_COMPRESSION);
- DeflaterSink deflaterSink = new DeflaterSink(mockSink, deflater);
- deflaterSink.write(new Buffer().writeUtf8(repeat("a", SEGMENT_SIZE)), SEGMENT_SIZE);
- try {
- deflaterSink.close();
- fail();
- } catch (IOException expected) {
- assertEquals("first", expected.getMessage());
- }
- mockSink.assertLogContains("close()");
- }
-
- /**
- * Uses streaming decompression to inflate {@code deflated}. The input must
- * either be finished or have a trailing sync flush.
- */
- private Buffer inflate(Buffer deflated) throws IOException {
- InputStream deflatedIn = deflated.inputStream();
- Inflater inflater = new Inflater();
- InputStream inflatedIn = new InflaterInputStream(deflatedIn, inflater);
- Buffer result = new Buffer();
- byte[] buffer = new byte[8192];
- while (!inflater.needsInput() || deflated.size() > 0 || deflatedIn.available() > 0) {
- int count = inflatedIn.read(buffer, 0, buffer.length);
- if (count != -1) {
- result.write(buffer, 0, count);
- }
- }
- return result;
- }
-}
diff --git a/okio/src/jvmTest/java/okio/ForwardingTimeoutTest.java b/okio/src/jvmTest/java/okio/ForwardingTimeoutTest.java
deleted file mode 100644
index 45536fc0..00000000
--- a/okio/src/jvmTest/java/okio/ForwardingTimeoutTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2018 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.util.concurrent.TimeUnit;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ForwardingTimeoutTest {
- @Test public void getAndSetDelegate() {
- Timeout timeout1 = new Timeout();
- Timeout timeout2 = new Timeout();
-
- ForwardingTimeout forwardingTimeout = new ForwardingTimeout(timeout1);
- forwardingTimeout.timeout(5, TimeUnit.SECONDS);
- assertThat(timeout1.timeoutNanos()).isNotEqualTo(0L);
- assertThat(timeout2.timeoutNanos()).isEqualTo(0L);
- forwardingTimeout.clearTimeout();
- assertThat(timeout1.timeoutNanos()).isEqualTo(0L);
- assertThat(timeout2.timeoutNanos()).isEqualTo(0L);
- assertThat(forwardingTimeout.delegate()).isEqualTo(timeout1);
-
- assertThat(forwardingTimeout.setDelegate(timeout2)).isEqualTo(forwardingTimeout);
- forwardingTimeout.timeout(5, TimeUnit.SECONDS);
- assertThat(timeout1.timeoutNanos()).isEqualTo(0L);
- assertThat(timeout2.timeoutNanos()).isNotEqualTo(0L);
- forwardingTimeout.clearTimeout();
- assertThat(timeout1.timeoutNanos()).isEqualTo(0L);
- assertThat(timeout2.timeoutNanos()).isEqualTo(0L);
- assertThat(forwardingTimeout.delegate()).isEqualTo(timeout2);
- }
-}
diff --git a/okio/src/jvmTest/java/okio/GzipSinkTest.java b/okio/src/jvmTest/java/okio/GzipSinkTest.java
deleted file mode 100644
index 848ff02c..00000000
--- a/okio/src/jvmTest/java/okio/GzipSinkTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.IOException;
-import org.junit.Test;
-
-import static kotlin.text.StringsKt.repeat;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-public final class GzipSinkTest {
- @Test public void gzipGunzip() throws Exception {
- Buffer data = new Buffer();
- String original = "It's a UNIX system! I know this!";
- data.writeUtf8(original);
- Buffer sink = new Buffer();
- GzipSink gzipSink = new GzipSink(sink);
- gzipSink.write(data, data.size());
- gzipSink.close();
- Buffer inflated = gunzip(sink);
- assertEquals(original, inflated.readUtf8());
- }
-
- @Test public void closeWithExceptionWhenWritingAndClosing() throws IOException {
- MockSink mockSink = new MockSink();
- mockSink.scheduleThrow(0, new IOException("first"));
- mockSink.scheduleThrow(1, new IOException("second"));
- GzipSink gzipSink = new GzipSink(mockSink);
- gzipSink.write(new Buffer().writeUtf8(repeat("a", SEGMENT_SIZE)), SEGMENT_SIZE);
- try {
- gzipSink.close();
- fail();
- } catch (IOException expected) {
- assertEquals("first", expected.getMessage());
- }
- mockSink.assertLogContains("close()");
- }
-
- private Buffer gunzip(Buffer gzipped) throws IOException {
- Buffer result = new Buffer();
- GzipSource source = new GzipSource(gzipped);
- while (source.read(result, Integer.MAX_VALUE) != -1) {
- }
- return result;
- }
-}
diff --git a/okio/src/jvmTest/java/okio/GzipSourceTest.java b/okio/src/jvmTest/java/okio/GzipSourceTest.java
deleted file mode 100644
index 69b81e3f..00000000
--- a/okio/src/jvmTest/java/okio/GzipSourceTest.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.IOException;
-import java.util.zip.CRC32;
-import org.junit.Test;
-
-import static kotlin.text.Charsets.UTF_8;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public final class GzipSourceTest {
-
- @Test public void gunzip() throws Exception {
- Buffer gzipped = new Buffer();
- gzipped.write(gzipHeader);
- gzipped.write(deflated);
- gzipped.write(gzipTrailer);
- assertGzipped(gzipped);
- }
-
- @Test public void gunzip_withHCRC() throws Exception {
- CRC32 hcrc = new CRC32();
- ByteString gzipHeader = gzipHeaderWithFlags((byte) 0x02);
- hcrc.update(gzipHeader.toByteArray());
-
- Buffer gzipped = new Buffer();
- gzipped.write(gzipHeader);
- gzipped.writeShort(TestUtil.reverseBytes((short) hcrc.getValue())); // little endian
- gzipped.write(deflated);
- gzipped.write(gzipTrailer);
- assertGzipped(gzipped);
- }
-
- @Test public void gunzip_withExtra() throws Exception {
- Buffer gzipped = new Buffer();
- gzipped.write(gzipHeaderWithFlags((byte) 0x04));
- gzipped.writeShort(TestUtil.reverseBytes((short) 7)); // little endian extra length
- gzipped.write("blubber".getBytes(UTF_8), 0, 7);
- gzipped.write(deflated);
- gzipped.write(gzipTrailer);
- assertGzipped(gzipped);
- }
-
- @Test public void gunzip_withName() throws Exception {
- Buffer gzipped = new Buffer();
- gzipped.write(gzipHeaderWithFlags((byte) 0x08));
- gzipped.write("foo.txt".getBytes(UTF_8), 0, 7);
- gzipped.writeByte(0); // zero-terminated
- gzipped.write(deflated);
- gzipped.write(gzipTrailer);
- assertGzipped(gzipped);
- }
-
- @Test public void gunzip_withComment() throws Exception {
- Buffer gzipped = new Buffer();
- gzipped.write(gzipHeaderWithFlags((byte) 0x10));
- gzipped.write("rubbish".getBytes(UTF_8), 0, 7);
- gzipped.writeByte(0); // zero-terminated
- gzipped.write(deflated);
- gzipped.write(gzipTrailer);
- assertGzipped(gzipped);
- }
-
- /**
- * For portability, it is a good idea to export the gzipped bytes and try running gzip. Ex.
- * {@code echo gzipped | base64 --decode | gzip -l -v}
- */
- @Test public void gunzip_withAll() throws Exception {
- Buffer gzipped = new Buffer();
- gzipped.write(gzipHeaderWithFlags((byte) 0x1c));
- gzipped.writeShort(TestUtil.reverseBytes((short) 7)); // little endian extra length
- gzipped.write("blubber".getBytes(UTF_8), 0, 7);
- gzipped.write("foo.txt".getBytes(UTF_8), 0, 7);
- gzipped.writeByte(0); // zero-terminated
- gzipped.write("rubbish".getBytes(UTF_8), 0, 7);
- gzipped.writeByte(0); // zero-terminated
- gzipped.write(deflated);
- gzipped.write(gzipTrailer);
- assertGzipped(gzipped);
- }
-
- private void assertGzipped(Buffer gzipped) throws IOException {
- Buffer gunzipped = gunzip(gzipped);
- assertEquals("It's a UNIX system! I know this!", gunzipped.readUtf8());
- }
-
- /**
- * Note that you cannot test this with old versions of gzip, as they interpret flag bit 1 as
- * CONTINUATION, not HCRC. For example, this is the case with the default gzip on osx.
- */
- @Test public void gunzipWhenHeaderCRCIncorrect() {
- Buffer gzipped = new Buffer();
- gzipped.write(gzipHeaderWithFlags((byte) 0x02));
- gzipped.writeShort((short) 0); // wrong HCRC!
- gzipped.write(deflated);
- gzipped.write(gzipTrailer);
-
- try {
- gunzip(gzipped);
- fail();
- } catch (IOException e) {
- assertEquals("FHCRC: actual 0x0000261d != expected 0x00000000", e.getMessage());
- }
- }
-
- @Test public void gunzipWhenCRCIncorrect() {
- Buffer gzipped = new Buffer();
- gzipped.write(gzipHeader);
- gzipped.write(deflated);
- gzipped.writeInt(TestUtil.reverseBytes(0x1234567)); // wrong CRC
- gzipped.write(gzipTrailer.toByteArray(), 3, 4);
-
- try {
- gunzip(gzipped);
- fail();
- } catch (IOException e) {
- assertEquals("CRC: actual 0x37ad8f8d != expected 0x01234567", e.getMessage());
- }
- }
-
- @Test public void gunzipWhenLengthIncorrect() {
- Buffer gzipped = new Buffer();
- gzipped.write(gzipHeader);
- gzipped.write(deflated);
- gzipped.write(gzipTrailer.toByteArray(), 0, 4);
- gzipped.writeInt(TestUtil.reverseBytes(0x123456)); // wrong length
-
- try {
- gunzip(gzipped);
- fail();
- } catch (IOException e) {
- assertEquals("ISIZE: actual 0x00000020 != expected 0x00123456", e.getMessage());
- }
- }
-
- @Test public void gunzipExhaustsSource() throws Exception {
- Buffer gzippedSource = new Buffer()
- .write(ByteString.decodeHex("1f8b08000000000000004b4c4a0600c241243503000000")); // 'abc'
-
- ExhaustableSource exhaustableSource = new ExhaustableSource(gzippedSource);
- BufferedSource gunzippedSource = Okio.buffer(new GzipSource(exhaustableSource));
-
- assertEquals('a', gunzippedSource.readByte());
- assertEquals('b', gunzippedSource.readByte());
- assertEquals('c', gunzippedSource.readByte());
- assertFalse(exhaustableSource.exhausted);
- assertEquals(-1, gunzippedSource.read(new Buffer(), 1));
- assertTrue(exhaustableSource.exhausted);
- }
-
- @Test public void gunzipThrowsIfSourceIsNotExhausted() throws Exception {
- Buffer gzippedSource = new Buffer()
- .write(ByteString.decodeHex("1f8b08000000000000004b4c4a0600c241243503000000")); // 'abc'
- gzippedSource.writeByte('d'); // This byte shouldn't be here!
-
- BufferedSource gunzippedSource = Okio.buffer(new GzipSource(gzippedSource));
-
- assertEquals('a', gunzippedSource.readByte());
- assertEquals('b', gunzippedSource.readByte());
- assertEquals('c', gunzippedSource.readByte());
- try {
- gunzippedSource.readByte();
- fail();
- } catch (IOException expected) {
- }
- }
-
- private ByteString gzipHeaderWithFlags(byte flags) {
- byte[] result = gzipHeader.toByteArray();
- result[3] = flags;
- return ByteString.of(result);
- }
-
- private final ByteString gzipHeader = ByteString.decodeHex("1f8b0800000000000000");
-
- // Deflated "It's a UNIX system! I know this!"
- private final ByteString deflated = ByteString.decodeHex(
- "f32c512f56485408f5f38c5028ae2c2e49cd5554f054c8cecb2f5728c9c82c560400");
-
- private final ByteString gzipTrailer = ByteString.decodeHex(""
- + "8d8fad37" // Checksum of deflated.
- + "20000000" // 32 in little endian.
- );
-
- private Buffer gunzip(Buffer gzipped) throws IOException {
- Buffer result = new Buffer();
- GzipSource source = new GzipSource(gzipped);
- while (source.read(result, Integer.MAX_VALUE) != -1) {
- }
- return result;
- }
-
- /** This source keeps track of whether its read has returned -1. */
- static class ExhaustableSource implements Source {
- private final Source source;
- private boolean exhausted;
-
- ExhaustableSource(Source source) {
- this.source = source;
- }
-
- @Override public long read(Buffer sink, long byteCount) throws IOException {
- long result = source.read(sink, byteCount);
- if (result == -1) exhausted = true;
- return result;
- }
-
- @Override public Timeout timeout() {
- return source.timeout();
- }
-
- @Override public void close() throws IOException {
- source.close();
- }
- }
-}
diff --git a/okio/src/jvmTest/java/okio/InflaterSourceTest.java b/okio/src/jvmTest/java/okio/InflaterSourceTest.java
deleted file mode 100644
index 0486638d..00000000
--- a/okio/src/jvmTest/java/okio/InflaterSourceTest.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.util.List;
-import java.util.zip.DeflaterOutputStream;
-import java.util.zip.Inflater;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-import static kotlin.text.StringsKt.repeat;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static okio.TestUtil.randomBytes;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
-
-@RunWith(Parameterized.class)
-public final class InflaterSourceTest {
- /**
- * Use a parameterized test to control how many bytes the InflaterSource gets with each request
- * for more bytes.
- */
- @Parameters(name = "{0}")
- public static List<Object[]> parameters() {
- return BufferedSourceFactory.Companion.getPARAMETERIZED_TEST_VALUES();
- }
-
- public final BufferedSourceFactory bufferFactory;
- public BufferedSink deflatedSink;
- public BufferedSource deflatedSource;
-
- public InflaterSourceTest(BufferedSourceFactory bufferFactory) {
- this.bufferFactory = bufferFactory;
- resetDeflatedSourceAndSink();
- }
-
- private void resetDeflatedSourceAndSink() {
- BufferedSourceFactory.Pipe pipe = bufferFactory.pipe();
- this.deflatedSink = pipe.getSink();
- this.deflatedSource = pipe.getSource();
- }
-
- @Test public void inflate() throws Exception {
- decodeBase64("eJxzz09RyEjNKVAoLdZRKE9VL0pVyMxTKMlIVchIzEspVshPU0jNS8/MS00tKtYDAF6CD5s=");
- Buffer inflated = inflate(deflatedSource);
- assertEquals("God help us, we're in the hands of engineers.", inflated.readUtf8());
- }
-
- @Test public void inflateTruncated() throws Exception {
- decodeBase64("eJxzz09RyEjNKVAoLdZRKE9VL0pVyMxTKMlIVchIzEspVshPU0jNS8/MS00tKtYDAF6CDw==");
- try {
- inflate(deflatedSource);
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void inflateWellCompressed() throws Exception {
- decodeBase64("eJztwTEBAAAAwqCs61/CEL5AAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8BtFeWvE=");
- String original = repeat("a", 1024 * 1024);
- deflate(ByteString.encodeUtf8(original));
- Buffer inflated = inflate(deflatedSource);
- assertEquals(original, inflated.readUtf8());
- }
-
- @Test public void inflatePoorlyCompressed() throws Exception {
- assumeFalse(bufferFactory.isOneByteAtATime()); // 8 GiB for 1 byte per segment!
-
- ByteString original = randomBytes(1024 * 1024);
- deflate(original);
- Buffer inflated = inflate(deflatedSource);
- assertEquals(original, inflated.readByteString());
- }
-
- @Test public void inflateIntoNonemptySink() throws Exception {
- for (int i = 0; i < SEGMENT_SIZE; i++) {
- resetDeflatedSourceAndSink();
- Buffer inflated = new Buffer().writeUtf8(repeat("a", i));
- deflate(ByteString.encodeUtf8("God help us, we're in the hands of engineers."));
- InflaterSource source = new InflaterSource(deflatedSource, new Inflater());
- while (source.read(inflated, Integer.MAX_VALUE) != -1) {
- }
- inflated.skip(i);
- assertEquals("God help us, we're in the hands of engineers.", inflated.readUtf8());
- }
- }
-
- @Test public void inflateSingleByte() throws Exception {
- Buffer inflated = new Buffer();
- decodeBase64("eJxzz09RyEjNKVAoLdZRKE9VL0pVyMxTKMlIVchIzEspVshPU0jNS8/MS00tKtYDAF6CD5s=");
- InflaterSource source = new InflaterSource(deflatedSource, new Inflater());
- source.read(inflated, 1);
- source.close();
- assertEquals("G", inflated.readUtf8());
- assertEquals(0, inflated.size());
- }
-
- @Test public void inflateByteCount() throws Exception {
- assumeFalse(bufferFactory.isOneByteAtATime()); // This test assumes one step.
-
- Buffer inflated = new Buffer();
- decodeBase64("eJxzz09RyEjNKVAoLdZRKE9VL0pVyMxTKMlIVchIzEspVshPU0jNS8/MS00tKtYDAF6CD5s=");
- InflaterSource source = new InflaterSource(deflatedSource, new Inflater());
- source.read(inflated, 11);
- source.close();
- assertEquals("God help us", inflated.readUtf8());
- assertEquals(0, inflated.size());
- }
-
- @Test public void sourceExhaustedPrematurelyOnRead() throws Exception {
- // Deflate 0 bytes of data that lacks the in-stream terminator.
- decodeBase64("eJwAAAD//w==");
-
- Buffer inflated = new Buffer();
- Inflater inflater = new Inflater();
- InflaterSource source = new InflaterSource(deflatedSource, inflater);
- assertThat(deflatedSource.exhausted()).isFalse();
- try {
- source.read(inflated, Long.MAX_VALUE);
- fail();
- } catch (EOFException expected) {
- assertThat(expected).hasMessage("source exhausted prematurely");
- }
-
- // Despite the exception, the read() call made forward progress on the underlying stream!
- assertThat(deflatedSource.exhausted()).isTrue();
- }
-
- /**
- * Confirm that {@link InflaterSource#readOrInflate} consumes a byte on each call even if it
- * doesn't produce a byte on every call.
- */
- @Test public void readOrInflateMakesByteByByteProgress() throws Exception {
- // Deflate 0 bytes of data that lacks the in-stream terminator.
- decodeBase64("eJwAAAD//w==");
- int deflatedByteCount = 7;
-
- Buffer inflated = new Buffer();
- Inflater inflater = new Inflater();
- InflaterSource source = new InflaterSource(deflatedSource, inflater);
- assertThat(deflatedSource.exhausted()).isFalse();
-
- if (bufferFactory.isOneByteAtATime()) {
- for (int i = 0; i < deflatedByteCount; i++) {
- assertThat(inflater.getBytesRead()).isEqualTo(i);
- assertThat(source.readOrInflate(inflated, Long.MAX_VALUE)).isEqualTo(0L);
- }
- } else {
- assertThat(source.readOrInflate(inflated, Long.MAX_VALUE)).isEqualTo(0L);
- }
-
- assertThat(inflater.getBytesRead()).isEqualTo(deflatedByteCount);
- assertThat(deflatedSource.exhausted());
- }
-
- private void decodeBase64(String s) throws IOException {
- deflatedSink.write(ByteString.decodeBase64(s));
- deflatedSink.flush();
- }
-
- /** Use DeflaterOutputStream to deflate source. */
- private void deflate(ByteString source) throws IOException {
- Sink sink = Okio.sink(new DeflaterOutputStream(deflatedSink.outputStream()));
- sink.write(new Buffer().write(source), source.size());
- sink.close();
- }
-
- /** Returns a new buffer containing the inflated contents of {@code deflated}. */
- private Buffer inflate(BufferedSource deflated) throws IOException {
- Buffer result = new Buffer();
- InflaterSource source = new InflaterSource(deflated, new Inflater());
- while (source.read(result, Integer.MAX_VALUE) != -1) {
- }
- return result;
- }
-}
diff --git a/okio/src/jvmTest/java/okio/LargeStreamsTest.java b/okio/src/jvmTest/java/okio/LargeStreamsTest.java
deleted file mode 100644
index b9be1a2e..00000000
--- a/okio/src/jvmTest/java/okio/LargeStreamsTest.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2016 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.zip.Deflater;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-import org.junit.Test;
-
-import static okio.TestUtil.SEGMENT_SIZE;
-import static okio.TestUtil.randomSource;
-import static org.junit.Assert.assertEquals;
-
-/** Slow running tests that run a large amount of data through a stream. */
-public final class LargeStreamsTest {
- /** 4 GiB plus 1 byte. This is greater than what can be expressed in an unsigned int. */
- public static final long FOUR_GIB_PLUS_ONE = 0x100000001L;
-
- /** SHA-256 of {@code TestUtil.randomSource(FOUR_GIB_PLUS_ONE)}. */
- public static final ByteString SHA256_RANDOM_FOUR_GIB_PLUS_1 = ByteString.decodeHex(
- "9654947a655c5efc445502fd1bf11117d894b7812b7974fde8ca4a02c5066315");
-
- @Test public void test() throws Exception {
- Pipe pipe = new Pipe(1024 * 1024);
-
- Future<Long> future = readAllAndCloseAsync(randomSource(FOUR_GIB_PLUS_ONE), pipe.sink());
-
- HashingSink hashingSink = HashingSink.sha256(Okio.blackhole());
- readAllAndClose(pipe.source(), hashingSink);
-
- assertEquals(FOUR_GIB_PLUS_ONE, (long) future.get());
- assertEquals(SHA256_RANDOM_FOUR_GIB_PLUS_1, hashingSink.hash());
- }
-
- /** Note that this test hangs on Android. */
- @Test public void gzipSource() throws Exception {
- Pipe pipe = new Pipe(1024 * 1024);
-
- OutputStream gzipOut = new GZIPOutputStream(Okio.buffer(pipe.sink()).outputStream()) {
- {
- // Disable compression to speed up a slow test. Improved from 141s to 33s on one machine.
- def.setLevel(Deflater.NO_COMPRESSION);
- }
- };
- Future<Long> future = readAllAndCloseAsync(
- randomSource(FOUR_GIB_PLUS_ONE), Okio.sink(gzipOut));
-
- HashingSink hashingSink = HashingSink.sha256(Okio.blackhole());
- GzipSource gzipSource = new GzipSource(pipe.source());
- readAllAndClose(gzipSource, hashingSink);
-
- assertEquals(FOUR_GIB_PLUS_ONE, (long) future.get());
- assertEquals(SHA256_RANDOM_FOUR_GIB_PLUS_1, hashingSink.hash());
- }
-
- /** Note that this test hangs on Android. */
- @Test public void gzipSink() throws Exception {
- Pipe pipe = new Pipe(1024 * 1024);
-
- GzipSink gzipSink = new GzipSink(pipe.sink());
-
- // Disable compression to speed up a slow test. Improved from 141s to 35s on one machine.
- gzipSink.deflater().setLevel(Deflater.NO_COMPRESSION);
- Future<Long> future = readAllAndCloseAsync(randomSource(FOUR_GIB_PLUS_ONE), gzipSink);
-
- HashingSink hashingSink = HashingSink.sha256(Okio.blackhole());
- GZIPInputStream gzipIn = new GZIPInputStream(Okio.buffer(pipe.source()).inputStream());
- readAllAndClose(Okio.source(gzipIn), hashingSink);
-
- assertEquals(FOUR_GIB_PLUS_ONE, (long) future.get());
- assertEquals(SHA256_RANDOM_FOUR_GIB_PLUS_1, hashingSink.hash());
- }
-
- /** Reads all bytes from {@code source} and writes them to {@code sink}. */
- private Long readAllAndClose(Source source, Sink sink) throws IOException {
- long result = 0L;
- Buffer buffer = new Buffer();
- for (long count; (count = source.read(buffer, SEGMENT_SIZE)) != -1L; result += count) {
- sink.write(buffer, count);
- }
- source.close();
- sink.close();
- return result;
- }
-
- /** Calls {@link #readAllAndClose} on a background thread. */
- private Future<Long> readAllAndCloseAsync(final Source source, final Sink sink) {
- ExecutorService executor = Executors.newSingleThreadExecutor();
- try {
- return executor.submit(new Callable<Long>() {
- @Override public Long call() throws Exception {
- return readAllAndClose(source, sink);
- }
- });
- } finally {
- executor.shutdown();
- }
- }
-}
diff --git a/okio/src/jvmTest/java/okio/NioTest.java b/okio/src/jvmTest/java/okio/NioTest.java
deleted file mode 100644
index aec17733..00000000
--- a/okio/src/jvmTest/java/okio/NioTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2018 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.File;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
-import java.nio.file.StandardOpenOption;
-import kotlin.text.Charsets;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
-
-/** Test interop between our beloved Okio and java.nio. */
-public final class NioTest {
- @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
- @Test public void sourceIsOpen() throws Exception {
- BufferedSource source = Okio.buffer((Source) new Buffer());
- assertTrue(source.isOpen());
- source.close();
- assertFalse(source.isOpen());
- }
-
- @Test public void sinkIsOpen() throws Exception {
- BufferedSink sink = Okio.buffer((Sink) new Buffer());
- assertTrue(sink.isOpen());
- sink.close();
- assertFalse(sink.isOpen());
- }
-
- @Test public void writableChannelNioFile() throws Exception {
- File file = temporaryFolder.newFile();
- FileChannel fileChannel = FileChannel.open(file.toPath(), StandardOpenOption.WRITE);
- testWritableByteChannel(fileChannel);
-
- BufferedSource emitted = Okio.buffer(Okio.source(file));
- assertEquals("defghijklmnopqrstuvw", emitted.readUtf8());
- emitted.close();
- }
-
- @Test public void writableChannelBuffer() throws Exception {
- Buffer buffer = new Buffer();
- testWritableByteChannel(buffer);
- assertEquals("defghijklmnopqrstuvw", buffer.readUtf8());
- }
-
- @Test public void writableChannelBufferedSink() throws Exception {
- Buffer buffer = new Buffer();
- BufferedSink bufferedSink = Okio.buffer((Sink) buffer);
- testWritableByteChannel(bufferedSink);
- assertEquals("defghijklmnopqrstuvw", buffer.readUtf8());
- }
-
- @Test public void readableChannelNioFile() throws Exception {
- File file = temporaryFolder.newFile();
-
- BufferedSink initialData = Okio.buffer(Okio.sink(file));
- initialData.writeUtf8("abcdefghijklmnopqrstuvwxyz");
- initialData.close();
-
- FileChannel fileChannel = FileChannel.open(file.toPath(), StandardOpenOption.READ);
- testReadableByteChannel(fileChannel);
- }
-
- @Test public void readableChannelBuffer() throws Exception {
- Buffer buffer = new Buffer();
- buffer.writeUtf8("abcdefghijklmnopqrstuvwxyz");
-
- testReadableByteChannel(buffer);
- }
-
- @Test public void readableChannelBufferedSource() throws Exception {
- Buffer buffer = new Buffer();
- BufferedSource bufferedSource = Okio.buffer((Source) buffer);
- buffer.writeUtf8("abcdefghijklmnopqrstuvwxyz");
-
- testReadableByteChannel(bufferedSource);
- }
-
- /**
- * Does some basic writes to {@code channel}. We execute this against both Okio's channels and
- * also a standard implementation from the JDK to confirm that their behavior is consistent.
- */
- private void testWritableByteChannel(WritableByteChannel channel) throws Exception {
- assertTrue(channel.isOpen());
-
- ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
- byteBuffer.put("abcdefghijklmnopqrstuvwxyz".getBytes(Charsets.UTF_8));
- byteBuffer.flip();
- byteBuffer.position(3);
- byteBuffer.limit(23);
-
- int byteCount = channel.write(byteBuffer);
- assertEquals(20, byteCount);
- assertEquals(23, byteBuffer.position());
- assertEquals(23, byteBuffer.limit());
-
- channel.close();
- assertEquals(channel instanceof Buffer, channel.isOpen()); // Buffer.close() does nothing.
- }
-
- /**
- * Does some basic reads from {@code channel}. We execute this against both Okio's channels and
- * also a standard implementation from the JDK to confirm that their behavior is consistent.
- */
- private void testReadableByteChannel(ReadableByteChannel channel) throws Exception {
- assertTrue(channel.isOpen());
-
- ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
- byteBuffer.position(3);
- byteBuffer.limit(23);
-
- int byteCount = channel.read(byteBuffer);
- assertEquals(20, byteCount);
- assertEquals(23, byteBuffer.position());
- assertEquals(23, byteBuffer.limit());
-
- channel.close();
- assertEquals(channel instanceof Buffer, channel.isOpen()); // Buffer.close() does nothing.
-
- byteBuffer.flip();
- byteBuffer.position(3);
- byte[] data = new byte[byteBuffer.remaining()];
- byteBuffer.get(data);
- assertEquals("abcdefghijklmnopqrst", new String(data, Charsets.UTF_8));
- }
-}
diff --git a/okio/src/jvmTest/java/okio/OkioTest.java b/okio/src/jvmTest/java/okio/OkioTest.java
deleted file mode 100644
index 71a44470..00000000
--- a/okio/src/jvmTest/java/okio/OkioTest.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import static kotlin.text.Charsets.UTF_8;
-import static kotlin.text.StringsKt.repeat;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static okio.TestUtil.assertNoEmptySegments;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public final class OkioTest {
- @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
- @Test public void readWriteFile() throws Exception {
- File file = temporaryFolder.newFile();
-
- BufferedSink sink = Okio.buffer(Okio.sink(file));
- sink.writeUtf8("Hello, java.io file!");
- sink.close();
- assertTrue(file.exists());
- assertEquals(20, file.length());
-
- BufferedSource source = Okio.buffer(Okio.source(file));
- assertEquals("Hello, java.io file!", source.readUtf8());
- source.close();
- }
-
- @Test public void appendFile() throws Exception {
- File file = temporaryFolder.newFile();
-
- BufferedSink sink = Okio.buffer(Okio.appendingSink(file));
- sink.writeUtf8("Hello, ");
- sink.close();
- assertTrue(file.exists());
- assertEquals(7, file.length());
-
- sink = Okio.buffer(Okio.appendingSink(file));
- sink.writeUtf8("java.io file!");
- sink.close();
- assertEquals(20, file.length());
-
- BufferedSource source = Okio.buffer(Okio.source(file));
- assertEquals("Hello, java.io file!", source.readUtf8());
- source.close();
- }
-
- @Test public void readWritePath() throws Exception {
- Path path = temporaryFolder.newFile().toPath();
-
- BufferedSink sink = Okio.buffer(Okio.sink(path));
- sink.writeUtf8("Hello, java.nio file!");
- sink.close();
- assertTrue(Files.exists(path));
- assertEquals(21, Files.size(path));
-
- BufferedSource source = Okio.buffer(Okio.source(path));
- assertEquals("Hello, java.nio file!", source.readUtf8());
- source.close();
- }
-
- @Test public void sinkFromOutputStream() throws Exception {
- Buffer data = new Buffer();
- data.writeUtf8("a");
- data.writeUtf8(repeat("b", 9998));
- data.writeUtf8("c");
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- Sink sink = Okio.sink(out);
- sink.write(data, 3);
- assertEquals("abb", out.toString("UTF-8"));
- sink.write(data, data.size());
- assertEquals("a" + repeat("b", 9998) + "c", out.toString("UTF-8"));
- }
-
- @Test public void sourceFromInputStream() throws Exception {
- InputStream in = new ByteArrayInputStream(
- ("a" + repeat("b", SEGMENT_SIZE * 2) + "c").getBytes(UTF_8));
-
- // Source: ab...bc
- Source source = Okio.source(in);
- Buffer sink = new Buffer();
-
- // Source: b...bc. Sink: abb.
- assertEquals(3, source.read(sink, 3));
- assertEquals("abb", sink.readUtf8(3));
-
- // Source: b...bc. Sink: b...b.
- assertEquals(SEGMENT_SIZE, source.read(sink, 20000));
- assertEquals(repeat("b", SEGMENT_SIZE), sink.readUtf8());
-
- // Source: b...bc. Sink: b...bc.
- assertEquals(SEGMENT_SIZE - 1, source.read(sink, 20000));
- assertEquals(repeat("b", SEGMENT_SIZE - 2) + "c", sink.readUtf8());
-
- // Source and sink are empty.
- assertEquals(-1, source.read(sink, 1));
- }
-
- @Test public void sourceFromInputStreamWithSegmentSize() throws Exception {
- InputStream in = new ByteArrayInputStream(new byte[SEGMENT_SIZE]);
- Source source = Okio.source(in);
- Buffer sink = new Buffer();
-
- assertEquals(SEGMENT_SIZE, source.read(sink, SEGMENT_SIZE));
- assertEquals(-1, source.read(sink, SEGMENT_SIZE));
-
- assertNoEmptySegments(sink);
- }
-
- @Test public void sourceFromInputStreamBounds() throws Exception {
- Source source = Okio.source(new ByteArrayInputStream(new byte[100]));
- try {
- source.read(new Buffer(), -1);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test public void bufferSinkThrowsOnNull() {
- try {
- Okio.buffer((Sink) null);
- fail();
- } catch (NullPointerException expected) {
- }
- }
-
- @Test public void bufferSourceThrowsOnNull() {
- try {
- Okio.buffer((Source) null);
- fail();
- } catch (NullPointerException expected) {
- }
- }
-
- @Test public void blackhole() throws Exception {
- Buffer data = new Buffer();
- data.writeUtf8("blackhole");
-
- Sink blackhole = Okio.blackhole();
- blackhole.write(data, 5);
-
- assertEquals("hole", data.readUtf8());
- }
-}
diff --git a/okio/src/jvmTest/java/okio/PipeTest.java b/okio/src/jvmTest/java/okio/PipeTest.java
deleted file mode 100644
index 030e6ba0..00000000
--- a/okio/src/jvmTest/java/okio/PipeTest.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * Copyright (C) 2016 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.util.Random;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import org.junit.After;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public final class PipeTest {
- final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
-
- @After public void tearDown() throws Exception {
- executorService.shutdown();
- }
-
- @Test public void test() throws Exception {
- Pipe pipe = new Pipe(6);
- pipe.sink().write(new Buffer().writeUtf8("abc"), 3L);
-
- Source source = pipe.source();
- Buffer readBuffer = new Buffer();
- assertEquals(3L, source.read(readBuffer, 6L));
- assertEquals("abc", readBuffer.readUtf8());
-
- pipe.sink().close();
- assertEquals(-1L, source.read(readBuffer, 6L));
-
- source.close();
- }
-
- /**
- * A producer writes the first 16 MiB of bytes generated by {@code new Random(0)} to a sink, and a
- * consumer consumes them. Both compute hashes of their data to confirm that they're as expected.
- */
- @Test public void largeDataset() throws Exception {
- final Pipe pipe = new Pipe(1000L); // An awkward size to force producer/consumer exchange.
- final long totalBytes = 16L * 1024L * 1024L;
- ByteString expectedHash = ByteString.decodeHex("7c3b224bea749086babe079360cf29f98d88262d");
-
- // Write data to the sink.
- Future<ByteString> sinkHash = executorService.submit(new Callable<ByteString>() {
- @Override public ByteString call() throws Exception {
- HashingSink hashingSink = HashingSink.sha1(pipe.sink());
- Random random = new Random(0);
- byte[] data = new byte[8192];
-
- Buffer buffer = new Buffer();
- for (long i = 0L; i < totalBytes; i += data.length) {
- random.nextBytes(data);
- buffer.write(data);
- hashingSink.write(buffer, buffer.size());
- }
-
- hashingSink.close();
- return hashingSink.hash();
- }
- });
-
- // Read data from the source.
- Future<ByteString> sourceHash = executorService.submit(new Callable<ByteString>() {
- @Override public ByteString call() throws Exception {
- Buffer blackhole = new Buffer();
- HashingSink hashingSink = HashingSink.sha1(blackhole);
-
- Buffer buffer = new Buffer();
- while (pipe.source().read(buffer, Long.MAX_VALUE) != -1) {
- hashingSink.write(buffer, buffer.size());
- blackhole.clear();
- }
-
- pipe.source().close();
- return hashingSink.hash();
- }
- });
-
- assertEquals(expectedHash, sinkHash.get());
- assertEquals(expectedHash, sourceHash.get());
- }
-
- @Test public void sinkTimeout() throws Exception {
- TestUtil.INSTANCE.assumeNotWindows();
-
- Pipe pipe = new Pipe(3);
- pipe.sink().timeout().timeout(1000, TimeUnit.MILLISECONDS);
- pipe.sink().write(new Buffer().writeUtf8("abc"), 3L);
- double start = now();
- try {
- pipe.sink().write(new Buffer().writeUtf8("def"), 3L);
- fail();
- } catch (InterruptedIOException expected) {
- assertEquals("timeout", expected.getMessage());
- }
- assertElapsed(1000.0, start);
-
- Buffer readBuffer = new Buffer();
- assertEquals(3L, pipe.source().read(readBuffer, 6L));
- assertEquals("abc", readBuffer.readUtf8());
- }
-
- @Test public void sourceTimeout() throws Exception {
- TestUtil.INSTANCE.assumeNotWindows();
-
- Pipe pipe = new Pipe(3L);
- pipe.source().timeout().timeout(1000, TimeUnit.MILLISECONDS);
- double start = now();
- Buffer readBuffer = new Buffer();
- try {
- pipe.source().read(readBuffer, 6L);
- fail();
- } catch (InterruptedIOException expected) {
- assertEquals("timeout", expected.getMessage());
- }
- assertElapsed(1000.0, start);
- assertEquals(0, readBuffer.size());
- }
-
- /**
- * The writer is writing 12 bytes as fast as it can to a 3 byte buffer. The reader alternates
- * sleeping 1000 ms, then reading 3 bytes. That should make for an approximate timeline like
- * this:
- *
- * 0: writer writes 'abc', blocks 0: reader sleeps until 1000
- * 1000: reader reads 'abc', sleeps until 2000
- * 1000: writer writes 'def', blocks
- * 2000: reader reads 'def', sleeps until 3000
- * 2000: writer writes 'ghi', blocks
- * 3000: reader reads 'ghi', sleeps until 4000
- * 3000: writer writes 'jkl', returns
- * 4000: reader reads 'jkl', returns
- *
- * Because the writer is writing to a buffer, it finishes before the reader does.
- */
- @Test public void sinkBlocksOnSlowReader() throws Exception {
- final Pipe pipe = new Pipe(3L);
- executorService.execute(new Runnable() {
- @Override public void run() {
- try {
- Buffer buffer = new Buffer();
- Thread.sleep(1000L);
- assertEquals(3, pipe.source().read(buffer, Long.MAX_VALUE));
- assertEquals("abc", buffer.readUtf8());
- Thread.sleep(1000L);
- assertEquals(3, pipe.source().read(buffer, Long.MAX_VALUE));
- assertEquals("def", buffer.readUtf8());
- Thread.sleep(1000L);
- assertEquals(3, pipe.source().read(buffer, Long.MAX_VALUE));
- assertEquals("ghi", buffer.readUtf8());
- Thread.sleep(1000L);
- assertEquals(3, pipe.source().read(buffer, Long.MAX_VALUE));
- assertEquals("jkl", buffer.readUtf8());
- } catch (IOException | InterruptedException e) {
- throw new AssertionError();
- }
- }
- });
-
- double start = now();
- pipe.sink().write(new Buffer().writeUtf8("abcdefghijkl"), 12);
- assertElapsed(3000.0, start);
- }
-
- @Test public void sinkWriteFailsByClosedReader() throws Exception {
- final Pipe pipe = new Pipe(3L);
- executorService.schedule(new Runnable() {
- @Override public void run() {
- try {
- pipe.source().close();
- } catch (IOException e) {
- throw new AssertionError();
- }
- }
- }, 1000, TimeUnit.MILLISECONDS);
-
- double start = now();
- try {
- pipe.sink().write(new Buffer().writeUtf8("abcdef"), 6);
- fail();
- } catch (IOException expected) {
- assertEquals("source is closed", expected.getMessage());
- assertElapsed(1000.0, start);
- }
- }
-
- @Test public void sinkFlushDoesntWaitForReader() throws Exception {
- Pipe pipe = new Pipe(100L);
- pipe.sink().write(new Buffer().writeUtf8("abc"), 3);
- pipe.sink().flush();
-
- BufferedSource bufferedSource = Okio.buffer(pipe.source());
- assertEquals("abc", bufferedSource.readUtf8(3));
- }
-
- @Test public void sinkFlushFailsIfReaderIsClosedBeforeAllDataIsRead() throws Exception {
- Pipe pipe = new Pipe(100L);
- pipe.sink().write(new Buffer().writeUtf8("abc"), 3);
- pipe.source().close();
- try {
- pipe.sink().flush();
- fail();
- } catch (IOException expected) {
- assertEquals("source is closed", expected.getMessage());
- }
- }
-
- @Test public void sinkCloseFailsIfReaderIsClosedBeforeAllDataIsRead() throws Exception {
- Pipe pipe = new Pipe(100L);
- pipe.sink().write(new Buffer().writeUtf8("abc"), 3);
- pipe.source().close();
- try {
- pipe.sink().close();
- fail();
- } catch (IOException expected) {
- assertEquals("source is closed", expected.getMessage());
- }
- }
-
- @Test public void sinkClose() throws Exception {
- Pipe pipe = new Pipe(100L);
- pipe.sink().close();
- try {
- pipe.sink().write(new Buffer().writeUtf8("abc"), 3);
- fail();
- } catch (IllegalStateException expected) {
- assertEquals("closed", expected.getMessage());
- }
- try {
- pipe.sink().flush();
- fail();
- } catch (IllegalStateException expected) {
- assertEquals("closed", expected.getMessage());
- }
- }
-
- @Test public void sinkMultipleClose() throws Exception {
- Pipe pipe = new Pipe(100L);
- pipe.sink().close();
- pipe.sink().close();
- }
-
- @Test public void sinkCloseDoesntWaitForSourceRead() throws Exception {
- Pipe pipe = new Pipe(100L);
- pipe.sink().write(new Buffer().writeUtf8("abc"), 3);
- pipe.sink().close();
-
- BufferedSource bufferedSource = Okio.buffer(pipe.source());
- assertEquals("abc", bufferedSource.readUtf8());
- assertTrue(bufferedSource.exhausted());
- }
-
- @Test public void sourceClose() throws Exception {
- Pipe pipe = new Pipe(100L);
- pipe.source().close();
- try {
- pipe.source().read(new Buffer(), 3);
- fail();
- } catch (IllegalStateException expected) {
- assertEquals("closed", expected.getMessage());
- }
- }
-
- @Test public void sourceMultipleClose() throws Exception {
- Pipe pipe = new Pipe(100L);
- pipe.source().close();
- pipe.source().close();
- }
-
- @Test public void sourceReadUnblockedByClosedSink() throws Exception {
- final Pipe pipe = new Pipe(3L);
- executorService.schedule(new Runnable() {
- @Override public void run() {
- try {
- pipe.sink().close();
- } catch (IOException e) {
- throw new AssertionError();
- }
- }
- }, 1000, TimeUnit.MILLISECONDS);
-
- double start = now();
- Buffer readBuffer = new Buffer();
- assertEquals(-1, pipe.source().read(readBuffer, Long.MAX_VALUE));
- assertEquals(0, readBuffer.size());
- assertElapsed(1000.0, start);
- }
-
- /**
- * The writer has 12 bytes to write. It alternates sleeping 1000 ms, then writing 3 bytes. The
- * reader is reading as fast as it can. That should make for an approximate timeline like this:
- *
- * 0: writer sleeps until 1000
- * 0: reader blocks
- * 1000: writer writes 'abc', sleeps until 2000
- * 1000: reader reads 'abc'
- * 2000: writer writes 'def', sleeps until 3000
- * 2000: reader reads 'def'
- * 3000: writer writes 'ghi', sleeps until 4000
- * 3000: reader reads 'ghi'
- * 4000: writer writes 'jkl', returns
- * 4000: reader reads 'jkl', returns
- */
- @Test public void sourceBlocksOnSlowWriter() throws Exception {
- final Pipe pipe = new Pipe(100L);
- executorService.execute(new Runnable() {
- @Override public void run() {
- try {
- Thread.sleep(1000L);
- pipe.sink().write(new Buffer().writeUtf8("abc"), 3);
- Thread.sleep(1000L);
- pipe.sink().write(new Buffer().writeUtf8("def"), 3);
- Thread.sleep(1000L);
- pipe.sink().write(new Buffer().writeUtf8("ghi"), 3);
- Thread.sleep(1000L);
- pipe.sink().write(new Buffer().writeUtf8("jkl"), 3);
- } catch (IOException | InterruptedException e) {
- throw new AssertionError();
- }
- }
- });
-
- double start = now();
- Buffer readBuffer = new Buffer();
-
- assertEquals(3, pipe.source().read(readBuffer, Long.MAX_VALUE));
- assertEquals("abc", readBuffer.readUtf8());
- assertElapsed(1000.0, start);
-
- assertEquals(3, pipe.source().read(readBuffer, Long.MAX_VALUE));
- assertEquals("def", readBuffer.readUtf8());
- assertElapsed(2000.0, start);
-
- assertEquals(3, pipe.source().read(readBuffer, Long.MAX_VALUE));
- assertEquals("ghi", readBuffer.readUtf8());
- assertElapsed(3000.0, start);
-
- assertEquals(3, pipe.source().read(readBuffer, Long.MAX_VALUE));
- assertEquals("jkl", readBuffer.readUtf8());
- assertElapsed(4000.0, start);
- }
-
- /** Returns the nanotime in milliseconds as a double for measuring timeouts. */
- private double now() {
- return System.nanoTime() / 1000000.0d;
- }
-
- /**
- * Fails the test unless the time from start until now is duration, accepting differences in
- * -50..+450 milliseconds.
- */
- private void assertElapsed(double duration, double start) {
- assertEquals(duration, now() - start - 200d, 250.0);
- }
-}
diff --git a/okio/src/jvmTest/java/okio/ReadUtf8LineTest.java b/okio/src/jvmTest/java/okio/ReadUtf8LineTest.java
deleted file mode 100644
index 9cc177f1..00000000
--- a/okio/src/jvmTest/java/okio/ReadUtf8LineTest.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import static kotlin.text.StringsKt.repeat;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-@RunWith(Parameterized.class)
-public final class ReadUtf8LineTest {
- private interface Factory {
- BufferedSource create(Buffer data);
- }
-
- @Parameterized.Parameters(name = "{0}")
- public static List<Object[]> parameters() {
- return Arrays.asList(
- new Object[] { new Factory() {
- @Override public BufferedSource create(Buffer data) {
- return data;
- }
-
- @Override public String toString() {
- return "Buffer";
- }
- }},
- new Object[] { new Factory() {
- @Override public BufferedSource create(Buffer data) {
- return new RealBufferedSource(data);
- }
-
- @Override public String toString() {
- return "RealBufferedSource";
- }
- }},
- new Object[] { new Factory() {
- @Override public BufferedSource create(Buffer data) {
- return new RealBufferedSource(new ForwardingSource(data) {
- @Override public long read(Buffer sink, long byteCount) throws IOException {
- return super.read(sink, Math.min(1, byteCount));
- }
- });
- }
-
- @Override public String toString() {
- return "Slow RealBufferedSource";
- }
- }}
- );
- }
-
- @Parameterized.Parameter
- public Factory factory;
-
- private Buffer data;
- private BufferedSource source;
-
- @Before public void setUp() {
- data = new Buffer();
- source = factory.create(data);
- }
-
- @Test public void readLines() throws IOException {
- data.writeUtf8("abc\ndef\n");
- assertEquals("abc", source.readUtf8LineStrict());
- assertEquals("def", source.readUtf8LineStrict());
- try {
- source.readUtf8LineStrict();
- fail();
- } catch (EOFException expected) {
- assertEquals("\\n not found: limit=0 content=…", expected.getMessage());
- }
- }
-
- @Test public void readUtf8LineStrictWithLimits() throws IOException {
- int[] lens = {1, SEGMENT_SIZE - 2, SEGMENT_SIZE - 1, SEGMENT_SIZE, SEGMENT_SIZE * 10};
- for (int len : lens) {
- data.writeUtf8(repeat("a", len)).writeUtf8("\n");
- assertEquals(len, source.readUtf8LineStrict(len).length());
- source.readUtf8();
-
- data.writeUtf8(repeat("a", len)).writeUtf8("\n").writeUtf8(repeat("a", len));
- assertEquals(len, source.readUtf8LineStrict(len).length());
- source.readUtf8();
-
- data.writeUtf8(repeat("a", len)).writeUtf8("\r\n");
- assertEquals(len, source.readUtf8LineStrict(len).length());
- source.readUtf8();
-
- data.writeUtf8(repeat("a", len)).writeUtf8("\r\n").writeUtf8(repeat("a", len));
- assertEquals(len, source.readUtf8LineStrict(len).length());
- source.readUtf8();
- }
- }
-
- @Test public void readUtf8LineStrictNoBytesConsumedOnFailure() throws IOException {
- data.writeUtf8("abc\n");
- try {
- source.readUtf8LineStrict(2);
- fail();
- } catch (EOFException expected) {
- assertTrue(expected.getMessage().startsWith("\\n not found: limit=2 content=61626"));
- }
- assertEquals("abc", source.readUtf8LineStrict(3));
- }
-
- @Test public void readUtf8LineStrictEmptyString() throws IOException {
- data.writeUtf8("\r\nabc");
- assertEquals("", source.readUtf8LineStrict(0));
- assertEquals("abc", source.readUtf8());
- }
-
- @Test public void readUtf8LineStrictNonPositive() throws IOException {
- data.writeUtf8("\r\n");
- try {
- source.readUtf8LineStrict(-1);
- fail("Expected failure: limit must be greater than 0");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test public void eofExceptionProvidesLimitedContent() throws IOException {
- data.writeUtf8("aaaaaaaabbbbbbbbccccccccdddddddde");
- try {
- source.readUtf8LineStrict();
- fail();
- } catch (EOFException expected) {
- assertEquals("\\n not found: limit=33 content=616161616161616162626262626262626363636363636363"
- + "6464646464646464…", expected.getMessage());
- }
- }
-
- @Test public void newlineAtEnd() throws IOException {
- data.writeUtf8("abc\n");
- assertEquals("abc", source.readUtf8LineStrict(3));
- assertTrue(source.exhausted());
-
- data.writeUtf8("abc\r\n");
- assertEquals("abc", source.readUtf8LineStrict(3));
- assertTrue(source.exhausted());
-
- data.writeUtf8("abc\r");
- try {
- source.readUtf8LineStrict(3);
- fail();
- } catch (EOFException expected) {
- assertEquals("\\n not found: limit=3 content=6162630d…", expected.getMessage());
- }
- source.readUtf8();
-
- data.writeUtf8("abc");
- try {
- source.readUtf8LineStrict(3);
- fail();
- } catch (EOFException expected) {
- assertEquals("\\n not found: limit=3 content=616263…", expected.getMessage());
- }
- }
-
- @Test public void emptyLines() throws IOException {
- data.writeUtf8("\n\n\n");
- assertEquals("", source.readUtf8LineStrict());
- assertEquals("", source.readUtf8LineStrict());
- assertEquals("", source.readUtf8LineStrict());
- assertTrue(source.exhausted());
- }
-
- @Test public void crDroppedPrecedingLf() throws IOException {
- data.writeUtf8("abc\r\ndef\r\nghi\rjkl\r\n");
- assertEquals("abc", source.readUtf8LineStrict());
- assertEquals("def", source.readUtf8LineStrict());
- assertEquals("ghi\rjkl", source.readUtf8LineStrict());
- }
-
- @Test public void bufferedReaderCompatible() throws IOException {
- data.writeUtf8("abc\ndef");
- assertEquals("abc", source.readUtf8Line());
- assertEquals("def", source.readUtf8Line());
- assertEquals(null, source.readUtf8Line());
- }
-
- @Test public void bufferedReaderCompatibleWithTrailingNewline() throws IOException {
- data.writeUtf8("abc\ndef\n");
- assertEquals("abc", source.readUtf8Line());
- assertEquals("def", source.readUtf8Line());
- assertEquals(null, source.readUtf8Line());
- }
-}
diff --git a/okio/src/jvmTest/java/okio/SocketTimeoutTest.java b/okio/src/jvmTest/java/okio/SocketTimeoutTest.java
deleted file mode 100644
index 6a6aadcd..00000000
--- a/okio/src/jvmTest/java/okio/SocketTimeoutTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketTimeoutException;
-import java.util.concurrent.TimeUnit;
-import org.junit.Test;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public final class SocketTimeoutTest {
-
- // The size of the socket buffers to use. Less than half the data transferred during tests to
- // ensure send and receive buffers are flooded and any necessary blocking behavior takes place.
- private static final int SOCKET_BUFFER_SIZE = 256 * 1024;
- private static final int ONE_MB = 1024 * 1024;
-
- @Test public void readWithoutTimeout() throws Exception {
- Socket socket = socket(ONE_MB, 0);
- BufferedSource source = Okio.buffer(Okio.source(socket));
- source.timeout().timeout(5000, TimeUnit.MILLISECONDS);
- source.require(ONE_MB);
- socket.close();
- }
-
- @Test public void readWithTimeout() throws Exception {
- Socket socket = socket(0, 0);
- BufferedSource source = Okio.buffer(Okio.source(socket));
- source.timeout().timeout(250, TimeUnit.MILLISECONDS);
- try {
- source.require(ONE_MB);
- fail();
- } catch (SocketTimeoutException expected) {
- }
- socket.close();
- }
-
- @Test public void writeWithoutTimeout() throws Exception {
- Socket socket = socket(0, ONE_MB);
- Sink sink = Okio.buffer(Okio.sink(socket));
- sink.timeout().timeout(500, TimeUnit.MILLISECONDS);
- byte[] data = new byte[ONE_MB];
- sink.write(new Buffer().write(data), data.length);
- sink.flush();
- socket.close();
- }
-
- @Test public void writeWithTimeout() throws Exception {
- Socket socket = socket(0, 0);
- Sink sink = Okio.sink(socket);
- sink.timeout().timeout(500, TimeUnit.MILLISECONDS);
- byte[] data = new byte[ONE_MB];
- long start = System.nanoTime();
- try {
- sink.write(new Buffer().write(data), data.length);
- sink.flush();
- fail();
- } catch (SocketTimeoutException expected) {
- }
- long elapsed = System.nanoTime() - start;
- socket.close();
-
- assertTrue("elapsed: " + elapsed, TimeUnit.NANOSECONDS.toMillis(elapsed) >= 500);
- assertTrue("elapsed: " + elapsed, TimeUnit.NANOSECONDS.toMillis(elapsed) <= 750);
- }
-
- /**
- * Returns a socket that can read {@code readableByteCount} incoming bytes and
- * will accept {@code writableByteCount} written bytes. The socket will idle
- * for 5 seconds when the required data has been read and written.
- */
- static Socket socket(final int readableByteCount, final int writableByteCount) throws IOException {
- final ServerSocket serverSocket = new ServerSocket(0);
- serverSocket.setReuseAddress(true);
- serverSocket.setReceiveBufferSize(SOCKET_BUFFER_SIZE);
-
- Thread peer = new Thread("peer") {
- @Override public void run() {
- Socket socket = null;
- try {
- socket = serverSocket.accept();
- socket.setSendBufferSize(SOCKET_BUFFER_SIZE);
- writeFully(socket.getOutputStream(), readableByteCount);
- readFully(socket.getInputStream(), writableByteCount);
- Thread.sleep(5000); // Sleep 5 seconds so the peer can close the connection.
- } catch (Exception ignored) {
- } finally {
- try {
- if (socket != null) socket.close();
- } catch (IOException ignored) {
- }
- }
- }
- };
- peer.start();
-
- Socket socket = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort());
- socket.setReceiveBufferSize(SOCKET_BUFFER_SIZE);
- socket.setSendBufferSize(SOCKET_BUFFER_SIZE);
- return socket;
- }
-
- private static void writeFully(OutputStream out, int byteCount) throws IOException {
- out.write(new byte[byteCount]);
- out.flush();
- }
-
- private static byte[] readFully(InputStream in, int byteCount) throws IOException {
- int count = 0;
- byte[] result = new byte[byteCount];
- while (count < byteCount) {
- int read = in.read(result, count, result.length - count);
- if (read == -1) throw new EOFException();
- count += read;
- }
- return result;
- }
-}
diff --git a/okio/src/jvmTest/java/okio/Utf8Test.java b/okio/src/jvmTest/java/okio/Utf8Test.java
deleted file mode 100644
index 63e0b7d4..00000000
--- a/okio/src/jvmTest/java/okio/Utf8Test.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.EOFException;
-import kotlin.text.Charsets;
-import org.junit.Test;
-
-import static kotlin.text.StringsKt.repeat;
-import static okio.TestUtil.REPLACEMENT_CODE_POINT;
-import static okio.TestUtil.SEGMENT_SIZE;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public final class Utf8Test {
- @Test public void oneByteCharacters() throws Exception {
- assertEncoded("00", 0x00); // Smallest 1-byte character.
- assertEncoded("20", ' ');
- assertEncoded("7e", '~');
- assertEncoded("7f", 0x7f); // Largest 1-byte character.
- }
-
- @Test public void twoByteCharacters() throws Exception {
- assertEncoded("c280", 0x0080); // Smallest 2-byte character.
- assertEncoded("c3bf", 0x00ff);
- assertEncoded("c480", 0x0100);
- assertEncoded("dfbf", 0x07ff); // Largest 2-byte character.
- }
-
- @Test public void threeByteCharacters() throws Exception {
- assertEncoded("e0a080", 0x0800); // Smallest 3-byte character.
- assertEncoded("e0bfbf", 0x0fff);
- assertEncoded("e18080", 0x1000);
- assertEncoded("e1bfbf", 0x1fff);
- assertEncoded("ed8080", 0xd000);
- assertEncoded("ed9fbf", 0xd7ff); // Largest character lower than the min surrogate.
- assertEncoded("ee8080", 0xe000); // Smallest character greater than the max surrogate.
- assertEncoded("eebfbf", 0xefff);
- assertEncoded("ef8080", 0xf000);
- assertEncoded("efbfbf", 0xffff); // Largest 3-byte character.
- }
-
- @Test public void fourByteCharacters() throws Exception {
- assertEncoded("f0908080", 0x010000); // Smallest surrogate pair.
- assertEncoded("f48fbfbf", 0x10ffff); // Largest code point expressible by UTF-16.
- }
-
- @Test public void danglingHighSurrogate() throws Exception {
- assertStringEncoded("3f", "\ud800"); // "?"
- }
-
- @Test public void lowSurrogateWithoutHighSurrogate() throws Exception {
- assertStringEncoded("3f", "\udc00"); // "?"
- }
-
- @Test public void highSurrogateFollowedByNonSurrogate() throws Exception {
- assertStringEncoded("3f61", "\ud800\u0061"); // "?a": Following character is too low.
- assertStringEncoded("3fee8080", "\ud800\ue000"); // "?\ue000": Following character is too high.
- }
-
- @Test public void doubleLowSurrogate() throws Exception {
- assertStringEncoded("3f3f", "\udc00\udc00"); // "??"
- }
-
- @Test public void doubleHighSurrogate() throws Exception {
- assertStringEncoded("3f3f", "\ud800\ud800"); // "??"
- }
-
- @Test public void highSurrogateLowSurrogate() throws Exception {
- assertStringEncoded("3f3f", "\udc00\ud800"); // "??"
- }
-
- @Test public void multipleSegmentString() throws Exception {
- String a = repeat("a", SEGMENT_SIZE + SEGMENT_SIZE + 1);
- Buffer encoded = new Buffer().writeUtf8(a);
- Buffer expected = new Buffer().write(a.getBytes(Charsets.UTF_8));
- assertEquals(expected, encoded);
- }
-
- @Test public void stringSpansSegments() throws Exception {
- Buffer buffer = new Buffer();
- String a = repeat("a", SEGMENT_SIZE - 1);
- String b = "bb";
- String c = repeat("c", SEGMENT_SIZE - 1);
- buffer.writeUtf8(a);
- buffer.writeUtf8(b);
- buffer.writeUtf8(c);
- assertEquals(a + b + c, buffer.readUtf8());
- }
-
- @Test public void readEmptyBufferThrowsEofException() throws Exception {
- Buffer buffer = new Buffer();
- try {
- buffer.readUtf8CodePoint();
- fail();
- } catch (EOFException expected) {
- }
- }
-
- @Test public void readLeadingContinuationByteReturnsReplacementCharacter() throws Exception {
- Buffer buffer = new Buffer();
- buffer.writeByte(0xbf);
- assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint());
- assertTrue(buffer.exhausted());
- }
-
- @Test public void readMissingContinuationBytesThrowsEofException() throws Exception {
- Buffer buffer = new Buffer();
- buffer.writeByte(0xdf);
- try {
- buffer.readUtf8CodePoint();
- fail();
- } catch (EOFException expected) {
- }
- assertFalse(buffer.exhausted()); // Prefix byte wasn't consumed.
- }
-
- @Test public void readTooLargeCodepointReturnsReplacementCharacter() throws Exception {
- // 5-byte and 6-byte code points are not supported.
- Buffer buffer = new Buffer();
- buffer.write(ByteString.decodeHex("f888808080"));
- assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint());
- assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint());
- assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint());
- assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint());
- assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint());
- assertTrue(buffer.exhausted());
- }
-
- @Test public void readNonContinuationBytesReturnsReplacementCharacter() throws Exception {
- // Use a non-continuation byte where a continuation byte is expected.
- Buffer buffer = new Buffer();
- buffer.write(ByteString.decodeHex("df20"));
- assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint());
- assertEquals(0x20, buffer.readUtf8CodePoint()); // Non-continuation character not consumed.
- assertTrue(buffer.exhausted());
- }
-
- @Test public void readCodePointBeyondUnicodeMaximum() throws Exception {
- // A 4-byte encoding with data above the U+10ffff Unicode maximum.
- Buffer buffer = new Buffer();
- buffer.write(ByteString.decodeHex("f4908080"));
- assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint());
- assertTrue(buffer.exhausted());
- }
-
- @Test public void readSurrogateCodePoint() throws Exception {
- Buffer buffer = new Buffer();
- buffer.write(ByteString.decodeHex("eda080"));
- assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint());
- assertTrue(buffer.exhausted());
- buffer.write(ByteString.decodeHex("edbfbf"));
- assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint());
- assertTrue(buffer.exhausted());
- }
-
- @Test public void readOverlongCodePoint() throws Exception {
- // Use 2 bytes to encode data that only needs 1 byte.
- Buffer buffer = new Buffer();
- buffer.write(ByteString.decodeHex("c080"));
- assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint());
- assertTrue(buffer.exhausted());
- }
-
- @Test public void writeSurrogateCodePoint() throws Exception {
- assertStringEncoded("ed9fbf", "\ud7ff"); // Below lowest surrogate is okay.
- assertStringEncoded("3f", "\ud800"); // Lowest surrogate gets '?'.
- assertStringEncoded("3f", "\udfff"); // Highest surrogate gets '?'.
- assertStringEncoded("ee8080", "\ue000"); // Above highest surrogate is okay.
- }
-
- @Test public void writeCodePointBeyondUnicodeMaximum() throws Exception {
- Buffer buffer = new Buffer();
- try {
- buffer.writeUtf8CodePoint(0x110000);
- fail();
- } catch (IllegalArgumentException expected) {
- assertEquals("Unexpected code point: 0x110000", expected.getMessage());
- }
- }
-
- @Test public void size() throws Exception {
- assertEquals(0, Utf8.size(""));
- assertEquals(3, Utf8.size("abc"));
- assertEquals(16, Utf8.size("təˈranəˌsôr"));
- }
-
- @Test public void sizeWithBounds() throws Exception {
- assertEquals(0, Utf8.size("", 0, 0));
- assertEquals(0, Utf8.size("abc", 0, 0));
- assertEquals(1, Utf8.size("abc", 1, 2));
- assertEquals(2, Utf8.size("abc", 0, 2));
- assertEquals(3, Utf8.size("abc", 0, 3));
- assertEquals(16, Utf8.size("təˈranəˌsôr", 0, 11));
- assertEquals(5, Utf8.size("təˈranəˌsôr", 3, 7));
- }
-
- @Test public void sizeBoundsCheck() throws Exception {
- try {
- Utf8.size(null, 0, 0);
- fail();
- } catch (NullPointerException expected) {
- }
- try {
- Utf8.size("abc", -1, 2);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- try {
- Utf8.size("abc", 2, 1);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- try {
- Utf8.size("abc", 1, 4);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
-
- private void assertEncoded(String hex, int... codePoints) throws Exception {
- assertCodePointEncoded(hex, codePoints);
- assertCodePointDecoded(hex, codePoints);
- assertStringEncoded(hex, new String(codePoints, 0, codePoints.length));
- }
-
- private void assertCodePointEncoded(String hex, int... codePoints) throws Exception {
- Buffer buffer = new Buffer();
- for (int codePoint : codePoints) {
- buffer.writeUtf8CodePoint(codePoint);
- }
- assertEquals(buffer.readByteString(), ByteString.decodeHex(hex));
- }
-
- private void assertCodePointDecoded(String hex, int... codePoints) throws Exception {
- Buffer buffer = new Buffer().write(ByteString.decodeHex(hex));
- for (int codePoint : codePoints) {
- assertEquals(codePoint, buffer.readUtf8CodePoint());
- }
- assertTrue(buffer.exhausted());
- }
-
- private void assertStringEncoded(String hex, String string) throws Exception {
- ByteString expectedUtf8 = ByteString.decodeHex(hex);
-
- // Confirm our expectations are consistent with the platform.
- ByteString platformUtf8 = ByteString.of(string.getBytes("UTF-8"));
- assertEquals(expectedUtf8, platformUtf8);
-
- // Confirm our implementation matches those expectations.
- ByteString actualUtf8 = new Buffer().writeUtf8(string).readByteString();
- assertEquals(expectedUtf8, actualUtf8);
-
- // Confirm we are consistent when writing one code point at a time.
- Buffer bufferUtf8 = new Buffer();
- for (int i = 0; i < string.length(); ) {
- int c = string.codePointAt(i);
- bufferUtf8.writeUtf8CodePoint(c);
- i += Character.charCount(c);
- }
- assertEquals(expectedUtf8, bufferUtf8.readByteString());
-
- // Confirm we are consistent when measuring lengths.
- assertEquals(expectedUtf8.size(), Utf8.size(string));
- assertEquals(expectedUtf8.size(), Utf8.size(string, 0, string.length()));
- }
-}
diff --git a/okio/src/jvmTest/java/okio/WaitUntilNotifiedTest.java b/okio/src/jvmTest/java/okio/WaitUntilNotifiedTest.java
deleted file mode 100644
index e4405289..00000000
--- a/okio/src/jvmTest/java/okio/WaitUntilNotifiedTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2016 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okio;
-
-import java.io.InterruptedIOException;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import org.junit.After;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public final class WaitUntilNotifiedTest {
- final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(0);
-
- @After public void tearDown() {
- executorService.shutdown();
- }
-
- @Test public synchronized void notified() throws InterruptedIOException {
- Timeout timeout = new Timeout();
- timeout.timeout(5000, TimeUnit.MILLISECONDS);
-
- double start = now();
- executorService.schedule(new Runnable() {
- @Override public void run() {
- synchronized (WaitUntilNotifiedTest.this) {
- WaitUntilNotifiedTest.this.notify();
- }
- }
- }, 1000, TimeUnit.MILLISECONDS);
-
- timeout.waitUntilNotified(this);
- assertElapsed(1000.0, start);
- }
-
- @Test public synchronized void timeout() {
- TestUtil.INSTANCE.assumeNotWindows();
-
- Timeout timeout = new Timeout();
- timeout.timeout(1000, TimeUnit.MILLISECONDS);
- double start = now();
- try {
- timeout.waitUntilNotified(this);
- fail();
- } catch (InterruptedIOException expected) {
- assertEquals("timeout", expected.getMessage());
- }
- assertElapsed(1000.0, start);
- }
-
- @Test public synchronized void deadline() {
- TestUtil.INSTANCE.assumeNotWindows();
-
- Timeout timeout = new Timeout();
- timeout.deadline(1000, TimeUnit.MILLISECONDS);
- double start = now();
- try {
- timeout.waitUntilNotified(this);
- fail();
- } catch (InterruptedIOException expected) {
- assertEquals("timeout", expected.getMessage());
- }
- assertElapsed(1000.0, start);
- }
-
- @Test public synchronized void deadlineBeforeTimeout() {
- TestUtil.INSTANCE.assumeNotWindows();
-
- Timeout timeout = new Timeout();
- timeout.timeout(5000, TimeUnit.MILLISECONDS);
- timeout.deadline(1000, TimeUnit.MILLISECONDS);
- double start = now();
- try {
- timeout.waitUntilNotified(this);
- fail();
- } catch (InterruptedIOException expected) {
- assertEquals("timeout", expected.getMessage());
- }
- assertElapsed(1000.0, start);
- }
-
- @Test public synchronized void timeoutBeforeDeadline() {
- TestUtil.INSTANCE.assumeNotWindows();
-
- Timeout timeout = new Timeout();
- timeout.timeout(1000, TimeUnit.MILLISECONDS);
- timeout.deadline(5000, TimeUnit.MILLISECONDS);
- double start = now();
- try {
- timeout.waitUntilNotified(this);
- fail();
- } catch (InterruptedIOException expected) {
- assertEquals("timeout", expected.getMessage());
- }
- assertElapsed(1000.0, start);
- }
-
- @Test public synchronized void deadlineAlreadyReached() {
- TestUtil.INSTANCE.assumeNotWindows();
-
- Timeout timeout = new Timeout();
- timeout.deadlineNanoTime(System.nanoTime());
- double start = now();
- try {
- timeout.waitUntilNotified(this);
- fail();
- } catch (InterruptedIOException expected) {
- assertEquals("timeout", expected.getMessage());
- }
- assertElapsed(0.0, start);
- }
-
- @Test public synchronized void threadInterrupted() {
- TestUtil.INSTANCE.assumeNotWindows();
-
- Timeout timeout = new Timeout();
- double start = now();
- Thread.currentThread().interrupt();
- try {
- timeout.waitUntilNotified(this);
- fail();
- } catch (InterruptedIOException expected) {
- assertEquals("interrupted", expected.getMessage());
- assertTrue(Thread.interrupted());
- }
- assertElapsed(0.0, start);
- }
-
- @Test public synchronized void threadInterruptedOnThrowIfReached() throws Exception {
- TestUtil.INSTANCE.assumeNotWindows();
-
- Timeout timeout = new Timeout();
- Thread.currentThread().interrupt();
- try {
- timeout.throwIfReached();
- fail();
- } catch (InterruptedIOException expected) {
- assertEquals("interrupted", expected.getMessage());
- assertTrue(Thread.interrupted());
- }
- }
-
- /** Returns the nanotime in milliseconds as a double for measuring timeouts. */
- private double now() {
- return System.nanoTime() / 1000000.0d;
- }
-
- /**
- * Fails the test unless the time from start until now is duration, accepting differences in
- * -50..+450 milliseconds.
- */
- private void assertElapsed(double duration, double start) {
- assertEquals(duration, now() - start - 200d, 250.0);
- }
-}
diff --git a/okio/src/jvmTest/kotlin/okio/AsyncTimeoutTest.kt b/okio/src/jvmTest/kotlin/okio/AsyncTimeoutTest.kt
new file mode 100644
index 00000000..f3a90a2b
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/AsyncTimeoutTest.kt
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.IOException
+import java.io.InterruptedIOException
+import java.util.Random
+import java.util.concurrent.LinkedBlockingDeque
+import java.util.concurrent.TimeUnit
+import okio.ByteString.Companion.of
+import okio.TestUtil.bufferWithRandomSegmentLayout
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+
+/**
+ * This test uses four timeouts of varying durations: 250ms, 500ms, 750ms and
+ * 1000ms, named 'a', 'b', 'c' and 'd'.
+ */
+class AsyncTimeoutTest {
+ private val timedOut = LinkedBlockingDeque<AsyncTimeout>()
+ private val a = RecordingAsyncTimeout()
+ private val b = RecordingAsyncTimeout()
+ private val c = RecordingAsyncTimeout()
+ private val d = RecordingAsyncTimeout()
+
+ @Before
+ fun setUp() {
+ a.timeout(250, TimeUnit.MILLISECONDS)
+ b.timeout(500, TimeUnit.MILLISECONDS)
+ c.timeout(750, TimeUnit.MILLISECONDS)
+ d.timeout(1000, TimeUnit.MILLISECONDS)
+ }
+
+ @Test
+ fun zeroTimeoutIsNoTimeout() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.timeout(0, TimeUnit.MILLISECONDS)
+ timeout.enter()
+ Thread.sleep(250)
+ assertFalse(timeout.exit())
+ assertTimedOut()
+ }
+
+ @Test
+ fun singleInstanceTimedOut() {
+ a.enter()
+ Thread.sleep(500)
+ assertTrue(a.exit())
+ assertTimedOut(a)
+ }
+
+ @Test
+ fun singleInstanceNotTimedOut() {
+ b.enter()
+ Thread.sleep(250)
+ b.exit()
+ assertFalse(b.exit())
+ assertTimedOut()
+ }
+
+ @Test
+ fun instancesAddedAtEnd() {
+ a.enter()
+ b.enter()
+ c.enter()
+ d.enter()
+ Thread.sleep(1250)
+ assertTrue(a.exit())
+ assertTrue(b.exit())
+ assertTrue(c.exit())
+ assertTrue(d.exit())
+ assertTimedOut(a, b, c, d)
+ }
+
+ @Test
+ fun instancesAddedAtFront() {
+ d.enter()
+ c.enter()
+ b.enter()
+ a.enter()
+ Thread.sleep(1250)
+ assertTrue(d.exit())
+ assertTrue(c.exit())
+ assertTrue(b.exit())
+ assertTrue(a.exit())
+ assertTimedOut(a, b, c, d)
+ }
+
+ @Test
+ fun instancesRemovedAtFront() {
+ a.enter()
+ b.enter()
+ c.enter()
+ d.enter()
+ assertFalse(a.exit())
+ assertFalse(b.exit())
+ assertFalse(c.exit())
+ assertFalse(d.exit())
+ assertTimedOut()
+ }
+
+ @Test
+ fun instancesRemovedAtEnd() {
+ a.enter()
+ b.enter()
+ c.enter()
+ d.enter()
+ assertFalse(d.exit())
+ assertFalse(c.exit())
+ assertFalse(b.exit())
+ assertFalse(a.exit())
+ assertTimedOut()
+ }
+
+ @Test
+ fun doubleEnter() {
+ a.enter()
+ try {
+ a.enter()
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ }
+
+ @Test
+ fun reEnter() {
+ a.timeout(10, TimeUnit.SECONDS)
+ a.enter()
+ assertFalse(a.exit())
+ a.enter()
+ assertFalse(a.exit())
+ }
+
+ @Test
+ fun reEnterAfterTimeout() {
+ a.timeout(1, TimeUnit.MILLISECONDS)
+ a.enter()
+ Assert.assertSame(a, timedOut.take())
+ assertTrue(a.exit())
+ a.enter()
+ assertFalse(a.exit())
+ }
+
+ @Test
+ fun deadlineOnly() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.deadline(250, TimeUnit.MILLISECONDS)
+ timeout.enter()
+ Thread.sleep(500)
+ assertTrue(timeout.exit())
+ assertTimedOut(timeout)
+ }
+
+ @Test
+ fun deadlineBeforeTimeout() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.deadline(250, TimeUnit.MILLISECONDS)
+ timeout.timeout(750, TimeUnit.MILLISECONDS)
+ timeout.enter()
+ Thread.sleep(500)
+ assertTrue(timeout.exit())
+ assertTimedOut(timeout)
+ }
+
+ @Test
+ fun deadlineAfterTimeout() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ timeout.deadline(750, TimeUnit.MILLISECONDS)
+ timeout.enter()
+ Thread.sleep(500)
+ assertTrue(timeout.exit())
+ assertTimedOut(timeout)
+ }
+
+ @Test
+ fun deadlineStartsBeforeEnter() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.deadline(500, TimeUnit.MILLISECONDS)
+ Thread.sleep(500)
+ timeout.enter()
+ Thread.sleep(250)
+ assertTrue(timeout.exit())
+ assertTimedOut(timeout)
+ }
+
+ @Test
+ fun deadlineInThePast() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.deadlineNanoTime(System.nanoTime() - 1)
+ timeout.enter()
+ Thread.sleep(250)
+ assertTrue(timeout.exit())
+ assertTimedOut(timeout)
+ }
+
+ @Test
+ fun wrappedSinkTimesOut() {
+ val sink: Sink = object : ForwardingSink(Buffer()) {
+ override fun write(source: Buffer, byteCount: Long) {
+ Thread.sleep(500)
+ }
+ }
+ val timeout = AsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ val timeoutSink = timeout.sink(sink)
+ val data = Buffer().writeUtf8("a")
+ try {
+ timeoutSink.write(data, 1)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ }
+ }
+
+ @Test
+ fun wrappedSinkFlushTimesOut() {
+ val sink: Sink = object : ForwardingSink(Buffer()) {
+ override fun flush() {
+ Thread.sleep(500)
+ }
+ }
+ val timeout = AsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ val timeoutSink = timeout.sink(sink)
+ try {
+ timeoutSink.flush()
+ fail()
+ } catch (expected: InterruptedIOException) {
+ }
+ }
+
+ @Test
+ fun wrappedSinkCloseTimesOut() {
+ val sink: Sink = object : ForwardingSink(Buffer()) {
+ override fun close() {
+ Thread.sleep(500)
+ }
+ }
+ val timeout = AsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ val timeoutSink = timeout.sink(sink)
+ try {
+ timeoutSink.close()
+ fail()
+ } catch (expected: InterruptedIOException) {
+ }
+ }
+
+ @Test
+ fun wrappedSourceTimesOut() {
+ val source: Source = object : ForwardingSource(Buffer()) {
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ Thread.sleep(500)
+ return -1
+ }
+ }
+ val timeout = AsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ val timeoutSource = timeout.source(source)
+ try {
+ timeoutSource.read(Buffer(), 0)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ }
+ }
+
+ @Test
+ fun wrappedSourceCloseTimesOut() {
+ val source: Source = object : ForwardingSource(Buffer()) {
+ override fun close() {
+ Thread.sleep(500)
+ }
+ }
+ val timeout = AsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ val timeoutSource = timeout.source(source)
+ try {
+ timeoutSource.close()
+ fail()
+ } catch (expected: InterruptedIOException) {
+ }
+ }
+
+ @Test
+ fun wrappedThrowsWithTimeout() {
+ val sink: Sink = object : ForwardingSink(Buffer()) {
+ override fun write(source: Buffer, byteCount: Long) {
+ Thread.sleep(500)
+ throw IOException("exception and timeout")
+ }
+ }
+ val timeout = AsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ val timeoutSink = timeout.sink(sink)
+ val data = Buffer().writeUtf8("a")
+ try {
+ timeoutSink.write(data, 1)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ assertEquals("exception and timeout", expected.cause!!.message)
+ }
+ }
+
+ @Test
+ fun wrappedThrowsWithoutTimeout() {
+ val sink: Sink = object : ForwardingSink(Buffer()) {
+ override fun write(source: Buffer, byteCount: Long) {
+ throw IOException("no timeout occurred")
+ }
+ }
+ val timeout = AsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ val timeoutSink = timeout.sink(sink)
+ val data = Buffer().writeUtf8("a")
+ try {
+ timeoutSink.write(data, 1)
+ fail()
+ } catch (expected: IOException) {
+ assertEquals("no timeout occurred", expected.message)
+ }
+ }
+
+ /**
+ * We had a bug where writing a very large buffer would fail with an
+ * unexpected timeout because although the sink was making steady forward
+ * progress, doing it all as a single write caused a timeout.
+ */
+ @Ignore("Flaky")
+ @Test
+ fun sinkSplitsLargeWrites() {
+ val data = ByteArray(512 * 1024)
+ val dice = Random(0)
+ dice.nextBytes(data)
+ val source = bufferWithRandomSegmentLayout(dice, data)
+ val target = Buffer()
+ val sink: Sink = object : ForwardingSink(Buffer()) {
+ override fun write(source: Buffer, byteCount: Long) {
+ Thread.sleep(byteCount / 500) // ~500 KiB/s.
+ target.write(source, byteCount)
+ }
+ }
+
+ // Timeout after 250 ms of inactivity.
+ val timeout = AsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ val timeoutSink = timeout.sink(sink)
+
+ // Transmit 500 KiB of data, which should take ~1 second. But expect no timeout!
+ timeoutSink.write(source, source.size)
+
+ // The data should all have arrived.
+ assertEquals(of(*data), target.readByteString())
+ }
+
+ @Test
+ fun enterCancelSleepExit() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ timeout.enter()
+ timeout.cancel()
+ Thread.sleep(500)
+
+ // Call didn't time out because the timeout was canceled.
+ assertFalse(timeout.exit())
+ assertTimedOut()
+ }
+
+ @Test
+ fun enterCancelCancelSleepExit() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ timeout.enter()
+ timeout.cancel()
+ timeout.cancel()
+ Thread.sleep(500)
+
+ // Call didn't time out because the timeout was canceled.
+ assertFalse(timeout.exit())
+ assertTimedOut()
+ }
+
+ @Test
+ fun enterSleepCancelExit() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ timeout.enter()
+ Thread.sleep(500)
+ timeout.cancel()
+
+ // Call timed out because the cancel was too late.
+ assertTrue(timeout.exit())
+ assertTimedOut(timeout)
+ }
+
+ @Test
+ fun enterSleepCancelCancelExit() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ timeout.enter()
+ Thread.sleep(500)
+ timeout.cancel()
+ timeout.cancel()
+
+ // Call timed out because both cancels were too late.
+ assertTrue(timeout.exit())
+ assertTimedOut(timeout)
+ }
+
+ @Test
+ fun enterCancelSleepCancelExit() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ timeout.enter()
+ timeout.cancel()
+ Thread.sleep(500)
+ timeout.cancel()
+
+ // Call didn't time out because the timeout was canceled.
+ assertFalse(timeout.exit())
+ assertTimedOut()
+ }
+
+ @Test
+ fun cancelEnterSleepExit() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ timeout.cancel()
+ timeout.enter()
+ Thread.sleep(500)
+
+ // Call timed out because the cancel was too early.
+ assertTrue(timeout.exit())
+ assertTimedOut(timeout)
+ }
+
+ @Test
+ fun cancelEnterCancelSleepExit() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+ timeout.cancel()
+ timeout.enter()
+ timeout.cancel()
+ Thread.sleep(500)
+
+ // Call didn't time out because the timeout was canceled.
+ assertFalse(timeout.exit())
+ assertTimedOut()
+ }
+
+ @Test
+ fun enterCancelSleepExitEnterSleepExit() {
+ val timeout = RecordingAsyncTimeout()
+ timeout.timeout(250, TimeUnit.MILLISECONDS)
+
+ // First call doesn't time out because we cancel it.
+ timeout.enter()
+ timeout.cancel()
+ Thread.sleep(500)
+ assertFalse(timeout.exit())
+ assertTimedOut()
+
+ // Second call does time out because it isn't canceled a second time.
+ timeout.enter()
+ Thread.sleep(500)
+ assertTrue(timeout.exit())
+ assertTimedOut(timeout)
+ }
+
+ /** Asserts which timeouts fired, and in which order. */
+ private fun assertTimedOut(vararg expected: Timeout) {
+ assertEquals(expected.toList(), timedOut.toList())
+ timedOut.clear()
+ }
+
+ internal inner class RecordingAsyncTimeout : AsyncTimeout() {
+ override fun timedOut() {
+ timedOut.add(this)
+ }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/AwaitSignalTest.kt b/okio/src/jvmTest/kotlin/okio/AwaitSignalTest.kt
new file mode 100644
index 00000000..a04e8cfe
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/AwaitSignalTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2023 Block Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.InterruptedIOException
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.ReentrantLock
+import okio.TestUtil.assumeNotWindows
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class AwaitSignalTest(
+ factory: TimeoutFactory,
+) {
+ private val timeout = factory.newTimeout()
+ val executorService = TestingExecutors.newScheduledExecutorService(0)
+
+ val lock: ReentrantLock = ReentrantLock()
+ val condition: Condition = lock.newCondition()
+
+ @After
+ fun tearDown() {
+ executorService.shutdown()
+ }
+
+ @Test
+ fun signaled() = lock.withLock {
+ timeout.timeout(5000, TimeUnit.MILLISECONDS)
+ val start = now()
+ executorService.schedule(
+ { lock.withLock { condition.signal() } },
+ 1000,
+ TimeUnit.MILLISECONDS,
+ )
+ timeout.awaitSignal(condition)
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ fun timeout() = lock.withLock {
+ assumeNotWindows()
+ timeout.timeout(1000, TimeUnit.MILLISECONDS)
+ val start = now()
+ try {
+ timeout.awaitSignal(condition)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ fun deadline() = lock.withLock {
+ assumeNotWindows()
+ timeout.deadline(1000, TimeUnit.MILLISECONDS)
+ val start = now()
+ try {
+ timeout.awaitSignal(condition)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ fun deadlineBeforeTimeout() = lock.withLock {
+ assumeNotWindows()
+ timeout.timeout(5000, TimeUnit.MILLISECONDS)
+ timeout.deadline(1000, TimeUnit.MILLISECONDS)
+ val start = now()
+ try {
+ timeout.awaitSignal(condition)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ fun timeoutBeforeDeadline() = lock.withLock {
+ assumeNotWindows()
+ timeout.timeout(1000, TimeUnit.MILLISECONDS)
+ timeout.deadline(5000, TimeUnit.MILLISECONDS)
+ val start = now()
+ try {
+ timeout.awaitSignal(condition)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ fun deadlineAlreadyReached() = lock.withLock {
+ assumeNotWindows()
+ timeout.deadlineNanoTime(System.nanoTime())
+ val start = now()
+ try {
+ timeout.awaitSignal(condition)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(0.0, start)
+ }
+
+ @Test
+ fun threadInterrupted() = lock.withLock {
+ assumeNotWindows()
+ val start = now()
+ Thread.currentThread().interrupt()
+ try {
+ timeout.awaitSignal(condition)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("interrupted", expected.message)
+ assertTrue(Thread.interrupted())
+ }
+ assertElapsed(0.0, start)
+ }
+
+ @Test
+ fun threadInterruptedOnThrowIfReached() = lock.withLock {
+ assumeNotWindows()
+ Thread.currentThread().interrupt()
+ try {
+ timeout.throwIfReached()
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("interrupted", expected.message)
+ assertTrue(Thread.interrupted())
+ }
+ }
+
+ @Test
+ fun cancelBeforeWaitDoesNothing() = lock.withLock {
+ timeout.timeout(1000, TimeUnit.MILLISECONDS)
+ timeout.cancel()
+ val start = now()
+ try {
+ timeout.awaitSignal(condition)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ fun canceledTimeoutDoesNotThrowWhenNotNotifiedOnTime() = lock.withLock {
+ assumeNotWindows()
+ timeout.timeout(1000, TimeUnit.MILLISECONDS)
+ timeout.cancelLater(500)
+
+ val start = now()
+ timeout.awaitSignal(condition) // Returns early but doesn't throw.
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ @Synchronized
+ fun multipleCancelsAreIdempotent() = lock.withLock {
+ timeout.timeout(1000, TimeUnit.MILLISECONDS)
+ timeout.cancelLater(250)
+ timeout.cancelLater(500)
+ timeout.cancelLater(750)
+
+ val start = now()
+ timeout.awaitSignal(condition) // Returns early but doesn't throw.
+ assertElapsed(1000.0, start)
+ }
+
+ /** Returns the nanotime in milliseconds as a double for measuring timeouts. */
+ private fun now(): Double {
+ return System.nanoTime() / 1000000.0
+ }
+
+ /**
+ * Fails the test unless the time from start until now is duration, accepting differences in
+ * -50..+450 milliseconds.
+ */
+ private fun assertElapsed(duration: Double, start: Double) {
+ assertEquals(duration, now() - start - 200.0, 250.0)
+ }
+
+ private fun Timeout.cancelLater(delay: Long) {
+ executorService.schedule(
+ { cancel() },
+ delay,
+ TimeUnit.MILLISECONDS,
+ )
+ }
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters(): List<Array<out Any?>> = TimeoutFactory.entries.map { arrayOf(it) }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/BufferCursorKotlinTest.kt b/okio/src/jvmTest/kotlin/okio/BufferCursorKotlinTest.kt
index ddbc4c53..0b50b005 100644
--- a/okio/src/jvmTest/kotlin/okio/BufferCursorKotlinTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/BufferCursorKotlinTest.kt
@@ -15,6 +15,11 @@
*/
package okio
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotSame
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
import okio.Buffer.UnsafeCursor
import okio.TestUtil.deepCopy
import org.junit.Assume.assumeTrue
@@ -23,11 +28,6 @@ import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameter
import org.junit.runners.Parameterized.Parameters
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertNotSame
-import kotlin.test.assertSame
-import kotlin.test.assertTrue
@RunWith(Parameterized::class)
class BufferCursorKotlinTest {
@@ -97,7 +97,7 @@ class BufferCursorKotlinTest {
buffer.readAndWriteUnsafe().use { cursor ->
while (cursor.next() != -1) {
- cursor.data!!.fill('x'.toByte(), cursor.start, cursor.end)
+ cursor.data!!.fill('x'.code.toByte(), cursor.start, cursor.end)
}
}
diff --git a/okio/src/jvmTest/kotlin/okio/BufferCursorTest.kt b/okio/src/jvmTest/kotlin/okio/BufferCursorTest.kt
new file mode 100644
index 00000000..54873286
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/BufferCursorTest.kt
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.util.Arrays
+import okio.ByteString.Companion.of
+import okio.TestUtil.SEGMENT_SIZE
+import okio.TestUtil.deepCopy
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.fail
+import org.junit.Assume.assumeTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class BufferCursorTest(
+ private var bufferFactory: BufferFactory,
+) {
+
+ @Test
+ fun apiExample() {
+ val buffer = Buffer()
+ buffer.readAndWriteUnsafe().use { cursor ->
+ cursor.resizeBuffer(1000000)
+ do {
+ Arrays.fill(cursor.data, cursor.start, cursor.end, 'x'.code.toByte())
+ } while (cursor.next() != -1)
+ cursor.seek(3)
+ cursor.data!![cursor.start] = 'o'.code.toByte()
+ cursor.seek(1)
+ cursor.data!![cursor.start] = 'o'.code.toByte()
+ cursor.resizeBuffer(4)
+ }
+ assertEquals(Buffer().writeUtf8("xoxo"), buffer)
+ }
+
+ @Test
+ fun accessSegmentBySegment() {
+ val buffer = bufferFactory.newBuffer()
+ buffer.readUnsafe().use { cursor ->
+ val actual = Buffer()
+ while (cursor.next().toLong() != -1L) {
+ actual.write(cursor.data!!, cursor.start, cursor.end - cursor.start)
+ }
+ assertEquals(buffer, actual)
+ }
+ }
+
+ @Test
+ fun seekToNegativeOneSeeksBeforeFirstSegment() {
+ val buffer = bufferFactory.newBuffer()
+ buffer.readUnsafe().use { cursor ->
+ cursor.seek(-1L)
+ assertEquals(-1, cursor.offset)
+ assertNull(cursor.data)
+ assertEquals(-1, cursor.start.toLong())
+ assertEquals(-1, cursor.end.toLong())
+ cursor.next()
+ assertEquals(0, cursor.offset)
+ }
+ }
+
+ @Test
+ fun accessByteByByte() {
+ val buffer = bufferFactory.newBuffer()
+ buffer.readUnsafe().use { cursor ->
+ val actual = ByteArray(buffer.size.toInt())
+ for (i in 0 until buffer.size) {
+ cursor.seek(i)
+ actual[i.toInt()] = cursor.data!![cursor.start]
+ }
+ assertEquals(of(*actual), buffer.snapshot())
+ }
+ }
+
+ @Test
+ fun accessByteByByteReverse() {
+ val buffer = bufferFactory.newBuffer()
+ buffer.readUnsafe().use { cursor ->
+ val actual = ByteArray(buffer.size.toInt())
+ for (i in (buffer.size - 1).toInt() downTo 0) {
+ cursor.seek(i.toLong())
+ actual[i] = cursor.data!![cursor.start]
+ }
+ assertEquals(of(*actual), buffer.snapshot())
+ }
+ }
+
+ @Test
+ fun accessByteByByteAlwaysResettingToZero() {
+ val buffer = bufferFactory.newBuffer()
+ buffer.readUnsafe().use { cursor ->
+ val actual = ByteArray(buffer.size.toInt())
+ for (i in 0 until buffer.size) {
+ cursor.seek(i)
+ actual[i.toInt()] = cursor.data!![cursor.start]
+ cursor.seek(0L)
+ }
+ assertEquals(of(*actual), buffer.snapshot())
+ }
+ }
+
+ @Test
+ fun segmentBySegmentNavigation() {
+ val buffer = bufferFactory.newBuffer()
+ val cursor = buffer.readUnsafe()
+ assertEquals(-1, cursor.offset)
+ try {
+ var lastOffset = cursor.offset
+ while (cursor.next().toLong() != -1L) {
+ Assert.assertTrue(cursor.offset > lastOffset)
+ lastOffset = cursor.offset
+ }
+ assertEquals(buffer.size, cursor.offset)
+ assertNull(cursor.data)
+ assertEquals(-1, cursor.start.toLong())
+ assertEquals(-1, cursor.end.toLong())
+ } finally {
+ cursor.close()
+ }
+ }
+
+ @Test
+ fun seekWithinSegment() {
+ assumeTrue(bufferFactory === BufferFactory.SMALL_SEGMENTED_BUFFER)
+ val buffer = bufferFactory.newBuffer()
+ assertEquals("abcdefghijkl", buffer.clone().readUtf8())
+ buffer.readUnsafe().use { cursor ->
+ assertEquals(2, cursor.seek(5).toLong()) // 2 for 2 bytes left in the segment: "fg".
+ assertEquals(5, cursor.offset)
+ assertEquals(2, (cursor.end - cursor.start).toLong())
+ assertEquals('d'.code.toLong(), Char(cursor.data!![cursor.start - 2].toUShort()).code.toLong()) // Out of bounds!
+ assertEquals('e'.code.toLong(), Char(cursor.data!![cursor.start - 1].toUShort()).code.toLong()) // Out of bounds!
+ assertEquals('f'.code.toLong(), Char(cursor.data!![cursor.start].toUShort()).code.toLong())
+ assertEquals('g'.code.toLong(), Char(cursor.data!![cursor.start + 1].toUShort()).code.toLong())
+ }
+ }
+
+ @Test
+ fun acquireAndRelease() {
+ val buffer = bufferFactory.newBuffer()
+ val cursor = Buffer.UnsafeCursor()
+
+ // Nothing initialized before acquire.
+ assertEquals(-1, cursor.offset)
+ assertNull(cursor.data)
+ assertEquals(-1, cursor.start.toLong())
+ assertEquals(-1, cursor.end.toLong())
+ buffer.readUnsafe(cursor)
+ cursor.close()
+
+ // Nothing initialized after close.
+ assertEquals(-1, cursor.offset)
+ assertNull(cursor.data)
+ assertEquals(-1, cursor.start.toLong())
+ assertEquals(-1, cursor.end.toLong())
+ }
+
+ @Test
+ fun doubleAcquire() {
+ val buffer = bufferFactory.newBuffer()
+ try {
+ buffer.readUnsafe().use { cursor ->
+ buffer.readUnsafe(cursor)
+ fail()
+ }
+ } catch (expected: IllegalStateException) {
+ }
+ }
+
+ @Test
+ fun releaseWithoutAcquire() {
+ val cursor = Buffer.UnsafeCursor()
+ try {
+ cursor.close()
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ }
+
+ @Test
+ fun releaseAfterRelease() {
+ val buffer = bufferFactory.newBuffer()
+ val cursor = buffer.readUnsafe()
+ cursor.close()
+ try {
+ cursor.close()
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ }
+
+ @Test
+ fun enlarge() {
+ val buffer = bufferFactory.newBuffer()
+ val originalSize = buffer.size
+ val expected = deepCopy(buffer)
+ expected.writeUtf8("abc")
+ buffer.readAndWriteUnsafe().use { cursor ->
+ assertEquals(originalSize, cursor.resizeBuffer(originalSize + 3))
+ cursor.seek(originalSize)
+ cursor.data!![cursor.start] = 'a'.code.toByte()
+ cursor.seek(originalSize + 1)
+ cursor.data!![cursor.start] = 'b'.code.toByte()
+ cursor.seek(originalSize + 2)
+ cursor.data!![cursor.start] = 'c'.code.toByte()
+ }
+ assertEquals(expected, buffer)
+ }
+
+ @Test
+ fun enlargeByManySegments() {
+ val buffer = bufferFactory.newBuffer()
+ val originalSize = buffer.size
+ val expected = deepCopy(buffer)
+ expected.writeUtf8("x".repeat(1000000))
+ buffer.readAndWriteUnsafe().use { cursor ->
+ cursor.resizeBuffer(originalSize + 1000000)
+ cursor.seek(originalSize)
+ do {
+ Arrays.fill(cursor.data, cursor.start, cursor.end, 'x'.code.toByte())
+ } while (cursor.next() != -1)
+ }
+ assertEquals(expected, buffer)
+ }
+
+ @Test
+ fun resizeNotAcquired() {
+ val cursor = Buffer.UnsafeCursor()
+ try {
+ cursor.resizeBuffer(10)
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ }
+
+ @Test
+ fun expandNotAcquired() {
+ val cursor = Buffer.UnsafeCursor()
+ try {
+ cursor.expandBuffer(10)
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ }
+
+ @Test
+ fun resizeAcquiredReadOnly() {
+ val buffer = bufferFactory.newBuffer()
+ try {
+ buffer.readUnsafe().use { cursor ->
+ cursor.resizeBuffer(10)
+ fail()
+ }
+ } catch (expected: IllegalStateException) {
+ }
+ }
+
+ @Test
+ fun expandAcquiredReadOnly() {
+ val buffer = bufferFactory.newBuffer()
+ try {
+ buffer.readUnsafe().use { cursor ->
+ cursor.expandBuffer(10)
+ fail()
+ }
+ } catch (expected: IllegalStateException) {
+ }
+ }
+
+ @Test
+ fun shrink() {
+ val buffer = bufferFactory.newBuffer()
+ assumeTrue(buffer.size > 3)
+ val originalSize = buffer.size
+ val expected = Buffer()
+ deepCopy(buffer).copyTo(expected, 0, originalSize - 3)
+ buffer.readAndWriteUnsafe().use { cursor ->
+ assertEquals(originalSize, cursor.resizeBuffer(originalSize - 3))
+ }
+ assertEquals(expected, buffer)
+ }
+
+ @Test
+ fun shrinkByManySegments() {
+ val buffer = bufferFactory.newBuffer()
+ assumeTrue(buffer.size <= 1000000)
+ val originalSize = buffer.size
+ val toShrink = Buffer()
+ toShrink.writeUtf8("x".repeat(1000000))
+ deepCopy(buffer).copyTo(toShrink, 0, originalSize)
+ val cursor = Buffer.UnsafeCursor()
+ toShrink.readAndWriteUnsafe(cursor)
+ try {
+ cursor.resizeBuffer(originalSize)
+ } finally {
+ cursor.close()
+ }
+ val expected = Buffer()
+ expected.writeUtf8("x".repeat(originalSize.toInt()))
+ assertEquals(expected, toShrink)
+ }
+
+ @Test
+ fun shrinkAdjustOffset() {
+ val buffer = bufferFactory.newBuffer()
+ assumeTrue(buffer.size > 4)
+ buffer.readAndWriteUnsafe().use { cursor ->
+ cursor.seek(buffer.size - 1)
+ cursor.resizeBuffer(3)
+ assertEquals(3, cursor.offset)
+ assertNull(cursor.data)
+ assertEquals(-1, cursor.start.toLong())
+ assertEquals(-1, cursor.end.toLong())
+ }
+ }
+
+ @Test
+ fun resizeToSameSizeSeeksToEnd() {
+ val buffer = bufferFactory.newBuffer()
+ val originalSize = buffer.size
+ buffer.readAndWriteUnsafe().use { cursor ->
+ cursor.seek(buffer.size / 2)
+ assertEquals(originalSize, buffer.size)
+ cursor.resizeBuffer(originalSize)
+ assertEquals(originalSize, buffer.size)
+ assertEquals(originalSize, cursor.offset)
+ assertNull(cursor.data)
+ assertEquals(-1, cursor.start.toLong())
+ assertEquals(-1, cursor.end.toLong())
+ }
+ }
+
+ @Test
+ fun resizeEnlargeMovesCursorToOldSize() {
+ val buffer = bufferFactory.newBuffer()
+ val originalSize = buffer.size
+ val expected = deepCopy(buffer)
+ expected.writeUtf8("a")
+ buffer.readAndWriteUnsafe().use { cursor ->
+ cursor.seek(buffer.size / 2)
+ assertEquals(originalSize, buffer.size)
+ cursor.resizeBuffer(originalSize + 1)
+ assertEquals(originalSize, cursor.offset)
+ assertNotNull(cursor.data)
+ assertNotEquals(-1, cursor.start.toLong())
+ assertEquals((cursor.start + 1).toLong(), cursor.end.toLong())
+ cursor.data!![cursor.start] = 'a'.code.toByte()
+ }
+ assertEquals(expected, buffer)
+ }
+
+ @Test
+ fun resizeShrinkMovesCursorToEnd() {
+ val buffer = bufferFactory.newBuffer()
+ assumeTrue(buffer.size > 0)
+ val originalSize = buffer.size
+ buffer.readAndWriteUnsafe().use { cursor ->
+ cursor.seek(buffer.size / 2)
+ assertEquals(originalSize, buffer.size)
+ cursor.resizeBuffer(originalSize - 1)
+ assertEquals(originalSize - 1, cursor.offset)
+ assertNull(cursor.data)
+ assertEquals(-1, cursor.start.toLong())
+ assertEquals(-1, cursor.end.toLong())
+ }
+ }
+
+ @Test
+ fun expand() {
+ val buffer = bufferFactory.newBuffer()
+ val originalSize = buffer.size
+ val expected = deepCopy(buffer)
+ expected.writeUtf8("abcde")
+ buffer.readAndWriteUnsafe().use { cursor ->
+ cursor.expandBuffer(5)
+ for (i in 0..4) {
+ cursor.data!![cursor.start + i] = ('a'.code + i).toByte()
+ }
+ cursor.resizeBuffer(originalSize + 5)
+ }
+ assertEquals(expected, buffer)
+ }
+
+ @Test
+ fun expandSameSegment() {
+ val buffer = bufferFactory.newBuffer()
+ val originalSize = buffer.size
+ assumeTrue(originalSize > 0)
+ buffer.readAndWriteUnsafe().use { cursor ->
+ cursor.seek(originalSize - 1)
+ val originalEnd = cursor.end
+ assumeTrue(originalEnd < SEGMENT_SIZE)
+ val addedByteCount = cursor.expandBuffer(1)
+ assertEquals((SEGMENT_SIZE - originalEnd).toLong(), addedByteCount)
+ assertEquals(originalSize + addedByteCount, buffer.size)
+ assertEquals(originalSize, cursor.offset)
+ assertEquals(originalEnd.toLong(), cursor.start.toLong())
+ assertEquals(SEGMENT_SIZE.toLong(), cursor.end.toLong())
+ }
+ }
+
+ @Test
+ fun expandNewSegment() {
+ val buffer = bufferFactory.newBuffer()
+ val originalSize = buffer.size
+ buffer.readAndWriteUnsafe().use { cursor ->
+ val addedByteCount = cursor.expandBuffer(SEGMENT_SIZE)
+ assertEquals(SEGMENT_SIZE.toLong(), addedByteCount)
+ assertEquals(originalSize, cursor.offset)
+ assertEquals(0, cursor.start.toLong())
+ assertEquals(SEGMENT_SIZE.toLong(), cursor.end.toLong())
+ }
+ }
+
+ @Test
+ fun expandMovesOffsetToOldSize() {
+ val buffer = bufferFactory.newBuffer()
+ val originalSize = buffer.size
+ buffer.readAndWriteUnsafe().use { cursor ->
+ cursor.seek(buffer.size / 2)
+ assertEquals(originalSize, buffer.size)
+ val addedByteCount = cursor.expandBuffer(5)
+ assertEquals(originalSize + addedByteCount, buffer.size)
+ assertEquals(originalSize, cursor.offset)
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun parameters(): List<Array<Any>> {
+ val result = mutableListOf<Array<Any>>()
+ for (bufferFactory in BufferFactory.values()) {
+ result += arrayOf(bufferFactory)
+ }
+ return result
+ }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/BufferFactory.kt b/okio/src/jvmTest/kotlin/okio/BufferFactory.kt
index e1533d23..0e6ce906 100644
--- a/okio/src/jvmTest/kotlin/okio/BufferFactory.kt
+++ b/okio/src/jvmTest/kotlin/okio/BufferFactory.kt
@@ -15,9 +15,9 @@
*/
package okio
+import java.util.Random
import okio.TestUtil.bufferWithRandomSegmentLayout
import okio.TestUtil.bufferWithSegments
-import java.util.Random
enum class BufferFactory {
EMPTY {
@@ -59,7 +59,8 @@ enum class BufferFactory {
return bufferWithRandomSegmentLayout(dice, largeByteArray)
}
- };
+ },
+ ;
@Throws(Exception::class)
abstract fun newBuffer(): Buffer
diff --git a/okio/src/jvmTest/kotlin/okio/BufferKotlinTest.kt b/okio/src/jvmTest/kotlin/okio/BufferKotlinTest.kt
index eda7989d..3bc8193c 100644
--- a/okio/src/jvmTest/kotlin/okio/BufferKotlinTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/BufferKotlinTest.kt
@@ -15,16 +15,16 @@
*/
package okio
+import kotlin.test.assertFailsWith
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
-import kotlin.test.assertFailsWith
class BufferKotlinTest {
@Test fun get() {
val actual = Buffer().writeUtf8("abc")
- assertThat(actual[0]).isEqualTo('a'.toByte())
- assertThat(actual[1]).isEqualTo('b'.toByte())
- assertThat(actual[2]).isEqualTo('c'.toByte())
+ assertThat(actual[0]).isEqualTo('a'.code.toByte())
+ assertThat(actual[1]).isEqualTo('b'.code.toByte())
+ assertThat(actual[2]).isEqualTo('c'.code.toByte())
assertFailsWith<IndexOutOfBoundsException> {
actual[-1]
}
diff --git a/okio/src/jvmTest/kotlin/okio/BufferTest.kt b/okio/src/jvmTest/kotlin/okio/BufferTest.kt
new file mode 100644
index 00000000..6f3501bd
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/BufferTest.kt
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.EOFException
+import java.io.InputStream
+import java.util.Arrays
+import java.util.Random
+import kotlin.text.Charsets.UTF_8
+import okio.ByteString.Companion.decodeHex
+import okio.TestUtil.SEGMENT_POOL_MAX_SIZE
+import okio.TestUtil.SEGMENT_SIZE
+import okio.TestUtil.assertNoEmptySegments
+import okio.TestUtil.bufferWithRandomSegmentLayout
+import okio.TestUtil.segmentPoolByteCount
+import okio.TestUtil.segmentSizes
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+
+/**
+ * Tests solely for the behavior of Buffer's implementation. For generic BufferedSink or
+ * BufferedSource behavior use BufferedSinkTest or BufferedSourceTest, respectively.
+ */
+class BufferTest {
+ @Test
+ fun readAndWriteUtf8() {
+ val buffer = Buffer()
+ buffer.writeUtf8("ab")
+ assertEquals(2, buffer.size)
+ buffer.writeUtf8("cdef")
+ assertEquals(6, buffer.size)
+ assertEquals("abcd", buffer.readUtf8(4))
+ assertEquals(2, buffer.size)
+ assertEquals("ef", buffer.readUtf8(2))
+ assertEquals(0, buffer.size)
+ try {
+ buffer.readUtf8(1)
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ /** Buffer's toString is the same as ByteString's. */
+ @Test
+ fun bufferToString() {
+ assertEquals("[size=0]", Buffer().toString())
+ assertEquals(
+ "[text=a\\r\\nb\\nc\\rd\\\\e]",
+ Buffer().writeUtf8("a\r\nb\nc\rd\\e").toString(),
+ )
+ assertEquals(
+ "[text=Tyrannosaur]",
+ Buffer().writeUtf8("Tyrannosaur").toString(),
+ )
+ assertEquals(
+ "[text=təˈranəˌsôr]",
+ Buffer()
+ .write("74c999cb8872616ec999cb8c73c3b472".decodeHex())
+ .toString(),
+ )
+ assertEquals(
+ "[hex=0000000000000000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000]",
+ Buffer().write(ByteArray(64)).toString(),
+ )
+ }
+
+ @Test
+ fun multipleSegmentBuffers() {
+ val buffer = Buffer()
+ buffer.writeUtf8("a".repeat(1000))
+ buffer.writeUtf8("b".repeat(2500))
+ buffer.writeUtf8("c".repeat(5000))
+ buffer.writeUtf8("d".repeat(10000))
+ buffer.writeUtf8("e".repeat(25000))
+ buffer.writeUtf8("f".repeat(50000))
+ assertEquals("a".repeat(999), buffer.readUtf8(999)) // a...a
+ assertEquals("a" + "b".repeat(2500) + "c", buffer.readUtf8(2502)) // ab...bc
+ assertEquals("c".repeat(4998), buffer.readUtf8(4998)) // c...c
+ assertEquals("c" + "d".repeat(10000) + "e", buffer.readUtf8(10002)) // cd...de
+ assertEquals("e".repeat(24998), buffer.readUtf8(24998)) // e...e
+ assertEquals("e" + "f".repeat(50000), buffer.readUtf8(50001)) // ef...f
+ assertEquals(0, buffer.size)
+ }
+
+ @Test
+ fun fillAndDrainPool() {
+ val buffer = Buffer()
+
+ // Take 2 * MAX_SIZE segments. This will drain the pool, even if other tests filled it.
+ buffer.write(ByteArray(SEGMENT_POOL_MAX_SIZE))
+ buffer.write(ByteArray(SEGMENT_POOL_MAX_SIZE))
+ assertEquals(0, segmentPoolByteCount().toLong())
+
+ // Recycle MAX_SIZE segments. They're all in the pool.
+ buffer.skip(SEGMENT_POOL_MAX_SIZE.toLong())
+ assertEquals(SEGMENT_POOL_MAX_SIZE.toLong(), segmentPoolByteCount().toLong())
+
+ // Recycle MAX_SIZE more segments. The pool is full so they get garbage collected.
+ buffer.skip(SEGMENT_POOL_MAX_SIZE.toLong())
+ assertEquals(SEGMENT_POOL_MAX_SIZE.toLong(), segmentPoolByteCount().toLong())
+
+ // Take MAX_SIZE segments to drain the pool.
+ buffer.write(ByteArray(SEGMENT_POOL_MAX_SIZE))
+ assertEquals(0, segmentPoolByteCount().toLong())
+
+ // Take MAX_SIZE more segments. The pool is drained so these will need to be allocated.
+ buffer.write(ByteArray(SEGMENT_POOL_MAX_SIZE))
+ assertEquals(0, segmentPoolByteCount().toLong())
+ }
+
+ @Test
+ fun moveBytesBetweenBuffersShareSegment() {
+ val size: Int = SEGMENT_SIZE / 2 - 1
+ val segmentSizes = moveBytesBetweenBuffers("a".repeat(size), "b".repeat(size))
+ assertEquals(Arrays.asList(size * 2), segmentSizes)
+ }
+
+ @Test
+ fun moveBytesBetweenBuffersReassignSegment() {
+ val size: Int = SEGMENT_SIZE / 2 + 1
+ val segmentSizes = moveBytesBetweenBuffers("a".repeat(size), "b".repeat(size))
+ assertEquals(Arrays.asList(size, size), segmentSizes)
+ }
+
+ @Test
+ fun moveBytesBetweenBuffersMultipleSegments() {
+ val size: Int = 3 * SEGMENT_SIZE + 1
+ val segmentSizes = moveBytesBetweenBuffers("a".repeat(size), "b".repeat(size))
+ assertEquals(
+ listOf(
+ SEGMENT_SIZE,
+ SEGMENT_SIZE,
+ SEGMENT_SIZE,
+ 1,
+ SEGMENT_SIZE,
+ SEGMENT_SIZE,
+ SEGMENT_SIZE,
+ 1,
+ ),
+ segmentSizes,
+ )
+ }
+
+ private fun moveBytesBetweenBuffers(vararg contents: String): List<Int> {
+ val expected = StringBuilder()
+ val buffer = Buffer()
+ for (s in contents) {
+ val source = Buffer()
+ source.writeUtf8(s)
+ buffer.writeAll(source)
+ expected.append(s)
+ }
+ val segmentSizes = segmentSizes(buffer)
+ assertEquals(expected.toString(), buffer.readUtf8(expected.length.toLong()))
+ return segmentSizes
+ }
+
+ /** The big part of source's first segment is being moved. */
+ @Test
+ fun writeSplitSourceBufferLeft() {
+ val writeSize: Int = SEGMENT_SIZE / 2 + 1
+ val sink = Buffer()
+ sink.writeUtf8("b".repeat(SEGMENT_SIZE - 10))
+ val source = Buffer()
+ source.writeUtf8("a".repeat(SEGMENT_SIZE * 2))
+ sink.write(source, writeSize.toLong())
+ assertEquals(Arrays.asList<Int>(SEGMENT_SIZE - 10, writeSize), segmentSizes(sink))
+ assertEquals(Arrays.asList<Int>(SEGMENT_SIZE - writeSize, SEGMENT_SIZE), segmentSizes(source))
+ }
+
+ /** The big part of source's first segment is staying put. */
+ @Test
+ fun writeSplitSourceBufferRight() {
+ val writeSize: Int = SEGMENT_SIZE / 2 - 1
+ val sink = Buffer()
+ sink.writeUtf8("b".repeat(SEGMENT_SIZE - 10))
+ val source = Buffer()
+ source.writeUtf8("a".repeat(SEGMENT_SIZE * 2))
+ sink.write(source, writeSize.toLong())
+ assertEquals(Arrays.asList<Int>(SEGMENT_SIZE - 10, writeSize), segmentSizes(sink))
+ assertEquals(Arrays.asList<Int>(SEGMENT_SIZE - writeSize, SEGMENT_SIZE), segmentSizes(source))
+ }
+
+ @Test
+ fun writePrefixDoesntSplit() {
+ val sink = Buffer()
+ sink.writeUtf8("b".repeat(10))
+ val source = Buffer()
+ source.writeUtf8("a".repeat(SEGMENT_SIZE * 2))
+ sink.write(source, 20)
+ assertEquals(mutableListOf(30), segmentSizes(sink))
+ assertEquals(Arrays.asList<Int>(SEGMENT_SIZE - 20, SEGMENT_SIZE), segmentSizes(source))
+ assertEquals(30, sink.size)
+ assertEquals((SEGMENT_SIZE * 2 - 20).toLong(), source.size)
+ }
+
+ @Test
+ fun writePrefixDoesntSplitButRequiresCompact() {
+ val sink = Buffer()
+ sink.writeUtf8("b".repeat(SEGMENT_SIZE - 10)) // limit = size - 10
+ sink.readUtf8((SEGMENT_SIZE - 20).toLong()) // pos = size = 20
+ val source = Buffer()
+ source.writeUtf8("a".repeat(SEGMENT_SIZE * 2))
+ sink.write(source, 20)
+ assertEquals(mutableListOf(30), segmentSizes(sink))
+ assertEquals(Arrays.asList<Int>(SEGMENT_SIZE - 20, SEGMENT_SIZE), segmentSizes(source))
+ assertEquals(30, sink.size)
+ assertEquals((SEGMENT_SIZE * 2 - 20).toLong(), source.size)
+ }
+
+ @Test
+ fun copyToSpanningSegments() {
+ val source = Buffer()
+ source.writeUtf8("a".repeat(SEGMENT_SIZE * 2))
+ source.writeUtf8("b".repeat(SEGMENT_SIZE * 2))
+ val out = ByteArrayOutputStream()
+ source.copyTo(out, 10, (SEGMENT_SIZE * 3).toLong())
+ assertEquals(
+ "a".repeat(SEGMENT_SIZE * 2 - 10) + "b".repeat(SEGMENT_SIZE + 10),
+ out.toString(),
+ )
+ assertEquals(
+ "a".repeat(SEGMENT_SIZE * 2) + "b".repeat(SEGMENT_SIZE * 2),
+ source.readUtf8((SEGMENT_SIZE * 4).toLong()),
+ )
+ }
+
+ @Test
+ fun copyToStream() {
+ val buffer = Buffer().writeUtf8("hello, world!")
+ val out = ByteArrayOutputStream()
+ buffer.copyTo(out)
+ val outString = out.toByteArray().toString(UTF_8)
+ assertEquals("hello, world!", outString)
+ assertEquals("hello, world!", buffer.readUtf8())
+ }
+
+ @Test
+ fun writeToSpanningSegments() {
+ val buffer = Buffer()
+ buffer.writeUtf8("a".repeat(SEGMENT_SIZE * 2))
+ buffer.writeUtf8("b".repeat(SEGMENT_SIZE * 2))
+ val out = ByteArrayOutputStream()
+ buffer.skip(10)
+ buffer.writeTo(out, (SEGMENT_SIZE * 3).toLong())
+ assertEquals(
+ "a".repeat(SEGMENT_SIZE * 2 - 10) + "b".repeat(SEGMENT_SIZE + 10),
+ out.toString(),
+ )
+ assertEquals("b".repeat(SEGMENT_SIZE - 10), buffer.readUtf8(buffer.size))
+ }
+
+ @Test
+ fun writeToStream() {
+ val buffer = Buffer().writeUtf8("hello, world!")
+ val out = ByteArrayOutputStream()
+ buffer.writeTo(out)
+ val outString = out.toByteArray().toString(UTF_8)
+ assertEquals("hello, world!", outString)
+ assertEquals(0, buffer.size)
+ }
+
+ @Test
+ fun readFromStream() {
+ val `in`: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8))
+ val buffer = Buffer()
+ buffer.readFrom(`in`)
+ val out = buffer.readUtf8()
+ assertEquals("hello, world!", out)
+ }
+
+ @Test
+ fun readFromSpanningSegments() {
+ val `in`: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8))
+ val buffer = Buffer().writeUtf8("a".repeat(SEGMENT_SIZE - 10))
+ buffer.readFrom(`in`)
+ val out = buffer.readUtf8()
+ assertEquals("a".repeat(SEGMENT_SIZE - 10) + "hello, world!", out)
+ }
+
+ @Test
+ fun readFromStreamWithCount() {
+ val `in`: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8))
+ val buffer = Buffer()
+ buffer.readFrom(`in`, 10)
+ val out = buffer.readUtf8()
+ assertEquals("hello, wor", out)
+ }
+
+ @Test
+ fun readFromDoesNotLeaveEmptyTailSegment() {
+ val buffer = Buffer()
+ buffer.readFrom(ByteArrayInputStream(ByteArray(SEGMENT_SIZE)))
+ assertNoEmptySegments(buffer)
+ }
+
+ @Test
+ fun moveAllRequestedBytesWithRead() {
+ val sink = Buffer()
+ sink.writeUtf8("a".repeat(10))
+ val source = Buffer()
+ source.writeUtf8("b".repeat(15))
+ assertEquals(10, source.read(sink, 10))
+ assertEquals(20, sink.size)
+ assertEquals(5, source.size)
+ assertEquals("a".repeat(10) + "b".repeat(10), sink.readUtf8(20))
+ }
+
+ @Test
+ fun moveFewerThanRequestedBytesWithRead() {
+ val sink = Buffer()
+ sink.writeUtf8("a".repeat(10))
+ val source = Buffer()
+ source.writeUtf8("b".repeat(20))
+ assertEquals(20, source.read(sink, 25))
+ assertEquals(30, sink.size)
+ assertEquals(0, source.size)
+ assertEquals("a".repeat(10) + "b".repeat(20), sink.readUtf8(30))
+ }
+
+ @Test
+ fun indexOfWithOffset() {
+ val buffer = Buffer()
+ val halfSegment: Int = SEGMENT_SIZE / 2
+ buffer.writeUtf8("a".repeat(halfSegment))
+ buffer.writeUtf8("b".repeat(halfSegment))
+ buffer.writeUtf8("c".repeat(halfSegment))
+ buffer.writeUtf8("d".repeat(halfSegment))
+ assertEquals(0, buffer.indexOf('a'.code.toByte(), 0))
+ assertEquals((halfSegment - 1).toLong(), buffer.indexOf('a'.code.toByte(), (halfSegment - 1).toLong()))
+ assertEquals(halfSegment.toLong(), buffer.indexOf('b'.code.toByte(), (halfSegment - 1).toLong()))
+ assertEquals((halfSegment * 2).toLong(), buffer.indexOf('c'.code.toByte(), (halfSegment - 1).toLong()))
+ assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.code.toByte(), (halfSegment - 1).toLong()))
+ assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.code.toByte(), (halfSegment * 2).toLong()))
+ assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.code.toByte(), (halfSegment * 3).toLong()))
+ assertEquals((halfSegment * 4 - 1).toLong(), buffer.indexOf('d'.code.toByte(), (halfSegment * 4 - 1).toLong()))
+ }
+
+ @Test
+ fun byteAt() {
+ val buffer = Buffer()
+ buffer.writeUtf8("a")
+ buffer.writeUtf8("b".repeat(SEGMENT_SIZE))
+ buffer.writeUtf8("c")
+ assertEquals('a'.code.toLong(), buffer[0].toLong())
+ assertEquals('a'.code.toLong(), buffer[0].toLong()) // getByte doesn't mutate!
+ assertEquals('c'.code.toLong(), buffer[buffer.size - 1].toLong())
+ assertEquals('b'.code.toLong(), buffer[buffer.size - 2].toLong())
+ assertEquals('b'.code.toLong(), buffer[buffer.size - 3].toLong())
+ }
+
+ @Test
+ fun getByteOfEmptyBuffer() {
+ val buffer = Buffer()
+ try {
+ buffer[0]
+ fail()
+ } catch (expected: IndexOutOfBoundsException) {
+ }
+ }
+
+ @Test
+ fun writePrefixToEmptyBuffer() {
+ val sink = Buffer()
+ val source = Buffer()
+ source.writeUtf8("abcd")
+ sink.write(source, 2)
+ assertEquals("ab", sink.readUtf8(2))
+ }
+
+ @Test
+ fun cloneDoesNotObserveWritesToOriginal() {
+ val original = Buffer()
+ val clone = original.clone()
+ original.writeUtf8("abc")
+ assertEquals(0, clone.size)
+ }
+
+ @Test
+ fun cloneDoesNotObserveReadsFromOriginal() {
+ val original = Buffer()
+ original.writeUtf8("abc")
+ val clone = original.clone()
+ assertEquals("abc", original.readUtf8(3))
+ assertEquals(3, clone.size)
+ assertEquals("ab", clone.readUtf8(2))
+ }
+
+ @Test
+ fun originalDoesNotObserveWritesToClone() {
+ val original = Buffer()
+ val clone = original.clone()
+ clone.writeUtf8("abc")
+ assertEquals(0, original.size)
+ }
+
+ @Test
+ fun originalDoesNotObserveReadsFromClone() {
+ val original = Buffer()
+ original.writeUtf8("abc")
+ val clone = original.clone()
+ assertEquals("abc", clone.readUtf8(3))
+ assertEquals(3, original.size)
+ assertEquals("ab", original.readUtf8(2))
+ }
+
+ @Test
+ fun cloneMultipleSegments() {
+ val original = Buffer()
+ original.writeUtf8("a".repeat(SEGMENT_SIZE * 3))
+ val clone = original.clone()
+ original.writeUtf8("b".repeat(SEGMENT_SIZE * 3))
+ clone.writeUtf8("c".repeat(SEGMENT_SIZE * 3))
+ assertEquals(
+ "a".repeat(SEGMENT_SIZE * 3) + "b".repeat(SEGMENT_SIZE * 3),
+ original.readUtf8((SEGMENT_SIZE * 6).toLong()),
+ )
+ assertEquals(
+ "a".repeat(SEGMENT_SIZE * 3) + "c".repeat(SEGMENT_SIZE * 3),
+ clone.readUtf8((SEGMENT_SIZE * 6).toLong()),
+ )
+ }
+
+ @Test
+ fun equalsAndHashCodeEmpty() {
+ val a = Buffer()
+ val b = Buffer()
+ assertEquals(a, b)
+ assertEquals(a.hashCode().toLong(), b.hashCode().toLong())
+ }
+
+ @Test
+ fun equalsAndHashCode() {
+ val a = Buffer().writeUtf8("dog")
+ val b = Buffer().writeUtf8("hotdog")
+ Assert.assertNotEquals(a, b)
+ Assert.assertNotEquals(a.hashCode().toLong(), b.hashCode().toLong())
+ b.readUtf8(3) // Leaves b containing 'dog'.
+ assertEquals(a, b)
+ assertEquals(a.hashCode().toLong(), b.hashCode().toLong())
+ }
+
+ @Test
+ fun equalsAndHashCodeSpanningSegments() {
+ val data = ByteArray(1024 * 1024)
+ val dice = Random(0)
+ dice.nextBytes(data)
+ val a = bufferWithRandomSegmentLayout(dice, data)
+ val b = bufferWithRandomSegmentLayout(dice, data)
+ assertEquals(a, b)
+ assertEquals(a.hashCode().toLong(), b.hashCode().toLong())
+ data[data.size / 2]++ // Change a single byte.
+ val c = bufferWithRandomSegmentLayout(dice, data)
+ Assert.assertNotEquals(a, c)
+ Assert.assertNotEquals(a.hashCode().toLong(), c.hashCode().toLong())
+ }
+
+ @Test
+ fun bufferInputStreamByteByByte() {
+ val source = Buffer()
+ source.writeUtf8("abc")
+ val `in` = source.inputStream()
+ assertEquals(3, `in`.available().toLong())
+ assertEquals('a'.code.toLong(), `in`.read().toLong())
+ assertEquals('b'.code.toLong(), `in`.read().toLong())
+ assertEquals('c'.code.toLong(), `in`.read().toLong())
+ assertEquals(-1, `in`.read().toLong())
+ assertEquals(0, `in`.available().toLong())
+ }
+
+ @Test
+ fun bufferInputStreamBulkReads() {
+ val source = Buffer()
+ source.writeUtf8("abc")
+ val byteArray = ByteArray(4)
+ byteArray.fill(-5)
+ val `in` = source.inputStream()
+ assertEquals(3, `in`.read(byteArray).toLong())
+ assertEquals("[97, 98, 99, -5]", Arrays.toString(byteArray))
+ byteArray.fill(-7)
+ assertEquals(-1, `in`.read(byteArray).toLong())
+ assertEquals("[-7, -7, -7, -7]", Arrays.toString(byteArray))
+ }
+
+ /**
+ * When writing data that's already buffered, there's no reason to page the
+ * data by segment.
+ */
+ @Test
+ fun readAllWritesAllSegmentsAtOnce() {
+ val write1 = Buffer().writeUtf8(
+ "" +
+ "a".repeat(SEGMENT_SIZE) +
+ "b".repeat(SEGMENT_SIZE) +
+ "c".repeat(SEGMENT_SIZE),
+ )
+ val source = Buffer().writeUtf8(
+ "" +
+ "a".repeat(SEGMENT_SIZE) +
+ "b".repeat(SEGMENT_SIZE) +
+ "c".repeat(SEGMENT_SIZE),
+ )
+ val mockSink = MockSink()
+ assertEquals((SEGMENT_SIZE * 3).toLong(), source.readAll(mockSink))
+ assertEquals(0, source.size)
+ mockSink.assertLog("write(" + write1 + ", " + write1.size + ")")
+ }
+
+ @Test
+ fun writeAllMultipleSegments() {
+ val source = Buffer().writeUtf8("a".repeat(SEGMENT_SIZE * 3))
+ val sink = Buffer()
+ assertEquals((SEGMENT_SIZE * 3).toLong(), sink.writeAll(source))
+ assertEquals(0, source.size)
+ assertEquals("a".repeat(SEGMENT_SIZE * 3), sink.readUtf8())
+ }
+
+ @Test
+ fun copyTo() {
+ val source = Buffer()
+ source.writeUtf8("party")
+ val target = Buffer()
+ source.copyTo(target, 1, 3)
+ assertEquals("art", target.readUtf8())
+ assertEquals("party", source.readUtf8())
+ }
+
+ @Test
+ fun copyToOnSegmentBoundary() {
+ val `as` = "a".repeat(SEGMENT_SIZE)
+ val bs = "b".repeat(SEGMENT_SIZE)
+ val cs = "c".repeat(SEGMENT_SIZE)
+ val ds = "d".repeat(SEGMENT_SIZE)
+ val source = Buffer()
+ source.writeUtf8(`as`)
+ source.writeUtf8(bs)
+ source.writeUtf8(cs)
+ val target = Buffer()
+ target.writeUtf8(ds)
+ source.copyTo(target, `as`.length.toLong(), (bs.length + cs.length).toLong())
+ assertEquals(ds + bs + cs, target.readUtf8())
+ }
+
+ @Test
+ fun copyToOffSegmentBoundary() {
+ val `as` = "a".repeat(SEGMENT_SIZE - 1)
+ val bs = "b".repeat(SEGMENT_SIZE + 2)
+ val cs = "c".repeat(SEGMENT_SIZE - 4)
+ val ds = "d".repeat(SEGMENT_SIZE + 8)
+ val source = Buffer()
+ source.writeUtf8(`as`)
+ source.writeUtf8(bs)
+ source.writeUtf8(cs)
+ val target = Buffer()
+ target.writeUtf8(ds)
+ source.copyTo(target, `as`.length.toLong(), (bs.length + cs.length).toLong())
+ assertEquals(ds + bs + cs, target.readUtf8())
+ }
+
+ @Test
+ fun copyToSourceAndTargetCanBeTheSame() {
+ val `as` = "a".repeat(SEGMENT_SIZE)
+ val bs = "b".repeat(SEGMENT_SIZE)
+ val source = Buffer()
+ source.writeUtf8(`as`)
+ source.writeUtf8(bs)
+ source.copyTo(source, 0, source.size)
+ assertEquals(`as` + bs + `as` + bs, source.readUtf8())
+ }
+
+ @Test
+ fun copyToEmptySource() {
+ val source = Buffer()
+ val target = Buffer().writeUtf8("aaa")
+ source.copyTo(target, 0L, 0L)
+ assertEquals("", source.readUtf8())
+ assertEquals("aaa", target.readUtf8())
+ }
+
+ @Test
+ fun copyToEmptyTarget() {
+ val source = Buffer().writeUtf8("aaa")
+ val target = Buffer()
+ source.copyTo(target, 0L, 3L)
+ assertEquals("aaa", source.readUtf8())
+ assertEquals("aaa", target.readUtf8())
+ }
+
+ @Test
+ fun snapshotReportsAccurateSize() {
+ val buf = Buffer().write(byteArrayOf(0, 1, 2, 3))
+ assertEquals(1, buf.snapshot(1).size.toLong())
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/BufferedSinkJavaTest.kt b/okio/src/jvmTest/kotlin/okio/BufferedSinkJavaTest.kt
new file mode 100644
index 00000000..5faf7605
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/BufferedSinkJavaTest.kt
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.IOException
+import okio.TestUtil.SEGMENT_SIZE
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+
+/**
+ * Tests solely for the behavior of RealBufferedSink's implementation. For generic
+ * BufferedSink behavior use BufferedSinkTest.
+ */
+class BufferedSinkJavaTest {
+ @Test
+ fun inputStreamCloses() {
+ val sink = (Buffer() as Sink).buffer()
+ val out = sink.outputStream()
+ out.close()
+ try {
+ sink.writeUtf8("Hi!")
+ fail()
+ } catch (e: IllegalStateException) {
+ assertEquals("closed", e.message)
+ }
+ }
+
+ @Test
+ fun bufferedSinkEmitsTailWhenItIsComplete() {
+ val sink = Buffer()
+ val bufferedSink = (sink as Sink).buffer()
+ bufferedSink.writeUtf8("a".repeat(SEGMENT_SIZE - 1))
+ assertEquals(0, sink.size)
+ bufferedSink.writeByte(0)
+ assertEquals(SEGMENT_SIZE.toLong(), sink.size)
+ assertEquals(0, bufferedSink.buffer.size)
+ }
+
+ @Test
+ fun bufferedSinkEmitMultipleSegments() {
+ val sink = Buffer()
+ val bufferedSink = (sink as Sink).buffer()
+ bufferedSink.writeUtf8("a".repeat(SEGMENT_SIZE * 4 - 1))
+ assertEquals((SEGMENT_SIZE * 3).toLong(), sink.size)
+ assertEquals((SEGMENT_SIZE - 1).toLong(), bufferedSink.buffer.size)
+ }
+
+ @Test
+ fun bufferedSinkFlush() {
+ val sink = Buffer()
+ val bufferedSink = (sink as Sink).buffer()
+ bufferedSink.writeByte('a'.code)
+ assertEquals(0, sink.size)
+ bufferedSink.flush()
+ assertEquals(0, bufferedSink.buffer.size)
+ assertEquals(1, sink.size)
+ }
+
+ @Test
+ fun bytesEmittedToSinkWithFlush() {
+ val sink = Buffer()
+ val bufferedSink = (sink as Sink).buffer()
+ bufferedSink.writeUtf8("abc")
+ bufferedSink.flush()
+ assertEquals(3, sink.size)
+ }
+
+ @Test
+ fun bytesNotEmittedToSinkWithoutFlush() {
+ val sink = Buffer()
+ val bufferedSink = (sink as Sink).buffer()
+ bufferedSink.writeUtf8("abc")
+ assertEquals(0, sink.size)
+ }
+
+ @Test
+ fun bytesEmittedToSinkWithEmit() {
+ val sink = Buffer()
+ val bufferedSink = (sink as Sink).buffer()
+ bufferedSink.writeUtf8("abc")
+ bufferedSink.emit()
+ assertEquals(3, sink.size)
+ }
+
+ @Test
+ fun completeSegmentsEmitted() {
+ val sink = Buffer()
+ val bufferedSink = (sink as Sink).buffer()
+ bufferedSink.writeUtf8("a".repeat(SEGMENT_SIZE * 3))
+ assertEquals((SEGMENT_SIZE * 3).toLong(), sink.size)
+ }
+
+ @Test
+ fun incompleteSegmentsNotEmitted() {
+ val sink = Buffer()
+ val bufferedSink = (sink as Sink).buffer()
+ bufferedSink.writeUtf8("a".repeat(SEGMENT_SIZE * 3 - 1))
+ assertEquals((SEGMENT_SIZE * 2).toLong(), sink.size)
+ }
+
+ @Test
+ fun closeWithExceptionWhenWriting() {
+ val mockSink = MockSink()
+ mockSink.scheduleThrow(0, IOException())
+ val bufferedSink = mockSink.buffer()
+ bufferedSink.writeByte('a'.code)
+ try {
+ bufferedSink.close()
+ fail()
+ } catch (expected: IOException) {
+ }
+ mockSink.assertLog("write([text=a], 1)", "close()")
+ }
+
+ @Test
+ fun closeWithExceptionWhenClosing() {
+ val mockSink = MockSink()
+ mockSink.scheduleThrow(1, IOException())
+ val bufferedSink = mockSink.buffer()
+ bufferedSink.writeByte('a'.code)
+ try {
+ bufferedSink.close()
+ fail()
+ } catch (expected: IOException) {
+ }
+ mockSink.assertLog("write([text=a], 1)", "close()")
+ }
+
+ @Test
+ fun closeWithExceptionWhenWritingAndClosing() {
+ val mockSink = MockSink()
+ mockSink.scheduleThrow(0, IOException("first"))
+ mockSink.scheduleThrow(1, IOException("second"))
+ val bufferedSink = mockSink.buffer()
+ bufferedSink.writeByte('a'.code)
+ try {
+ bufferedSink.close()
+ fail()
+ } catch (expected: IOException) {
+ assertEquals("first", expected.message)
+ }
+ mockSink.assertLog("write([text=a], 1)", "close()")
+ }
+
+ @Test
+ fun operationsAfterClose() {
+ val mockSink = MockSink()
+ val bufferedSink = mockSink.buffer()
+ bufferedSink.writeByte('a'.code)
+ bufferedSink.close()
+
+ // Test a sample set of methods.
+ try {
+ bufferedSink.writeByte('a'.code)
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ try {
+ bufferedSink.write(ByteArray(10))
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ try {
+ bufferedSink.emitCompleteSegments()
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ try {
+ bufferedSink.emit()
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ try {
+ bufferedSink.flush()
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+
+ // Test a sample set of methods on the OutputStream.
+ val os = bufferedSink.outputStream()
+ try {
+ os.write('a'.code)
+ fail()
+ } catch (expected: IOException) {
+ }
+ try {
+ os.write(ByteArray(10))
+ fail()
+ } catch (expected: IOException) {
+ }
+
+ // Permitted
+ os.flush()
+ }
+
+ @Test
+ fun writeAll() {
+ val mockSink = MockSink()
+ val bufferedSink = mockSink.buffer()
+ bufferedSink.buffer.writeUtf8("abc")
+ assertEquals(3, bufferedSink.writeAll(Buffer().writeUtf8("def")))
+ assertEquals(6, bufferedSink.buffer.size)
+ assertEquals("abcdef", bufferedSink.buffer.readUtf8(6))
+ mockSink.assertLog() // No writes.
+ }
+
+ @Test
+ fun writeAllExhausted() {
+ val mockSink = MockSink()
+ val bufferedSink = mockSink.buffer()
+ assertEquals(0, bufferedSink.writeAll(Buffer()))
+ assertEquals(0, bufferedSink.buffer.size)
+ mockSink.assertLog() // No writes.
+ }
+
+ @Test
+ fun writeAllWritesOneSegmentAtATime() {
+ val write1 = Buffer().writeUtf8("a".repeat(SEGMENT_SIZE))
+ val write2 = Buffer().writeUtf8("b".repeat(SEGMENT_SIZE))
+ val write3 = Buffer().writeUtf8("c".repeat(SEGMENT_SIZE))
+ val source = Buffer().writeUtf8(
+ "" +
+ "a".repeat(SEGMENT_SIZE) +
+ "b".repeat(SEGMENT_SIZE) +
+ "c".repeat(SEGMENT_SIZE),
+ )
+ val mockSink = MockSink()
+ val bufferedSink = mockSink.buffer()
+ assertEquals((SEGMENT_SIZE * 3).toLong(), bufferedSink.writeAll(source))
+ mockSink.assertLog(
+ "write(" + write1 + ", " + write1.size + ")",
+ "write(" + write2 + ", " + write2.size + ")",
+ "write(" + write3 + ", " + write3.size + ")",
+ )
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/BufferedSinkTest.kt b/okio/src/jvmTest/kotlin/okio/BufferedSinkTest.kt
new file mode 100644
index 00000000..c9b3d187
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/BufferedSinkTest.kt
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.EOFException
+import java.math.BigInteger
+import java.nio.ByteBuffer
+import java.nio.charset.Charset
+import kotlin.text.Charsets.UTF_8
+import okio.ByteString.Companion.decodeHex
+import okio.ByteString.Companion.encodeUtf8
+import okio.TestUtil.SEGMENT_SIZE
+import okio.TestUtil.segmentSizes
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class BufferedSinkTest(
+ factory: Factory,
+) {
+ interface Factory {
+ fun create(data: Buffer): BufferedSink
+
+ companion object {
+ val BUFFER: Factory = object : Factory {
+ override fun create(data: Buffer) = data
+ override fun toString() = "Buffer"
+ }
+ val REAL_BUFFERED_SINK: Factory = object : Factory {
+ override fun create(data: Buffer) = (data as Sink).buffer()
+ override fun toString() = "RealBufferedSink"
+ }
+ }
+ }
+
+ private val data: Buffer = Buffer()
+ private val sink: BufferedSink = factory.create(data)
+
+ @Test
+ fun writeNothing() {
+ sink.writeUtf8("")
+ sink.flush()
+ assertEquals(0, data.size)
+ }
+
+ @Test
+ fun writeBytes() {
+ sink.writeByte(0xab)
+ sink.writeByte(0xcd)
+ sink.flush()
+ assertEquals("[hex=abcd]", data.toString())
+ }
+
+ @Test
+ fun writeLastByteInSegment() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 1))
+ sink.writeByte(0x20)
+ sink.writeByte(0x21)
+ sink.flush()
+ assertEquals(listOf(SEGMENT_SIZE, 1), segmentSizes(data))
+ assertEquals("a".repeat(SEGMENT_SIZE - 1), data.readUtf8((SEGMENT_SIZE - 1).toLong()))
+ assertEquals("[text= !]", data.toString())
+ }
+
+ @Test
+ fun writeShort() {
+ sink.writeShort(0xabcd)
+ sink.writeShort(0x4321)
+ sink.flush()
+ assertEquals("[hex=abcd4321]", data.toString())
+ }
+
+ @Test
+ fun writeShortLe() {
+ sink.writeShortLe(0xcdab)
+ sink.writeShortLe(0x2143)
+ sink.flush()
+ assertEquals("[hex=abcd4321]", data.toString())
+ }
+
+ @Test
+ fun writeInt() {
+ sink.writeInt(-0x543210ff)
+ sink.writeInt(-0x789abcdf)
+ sink.flush()
+ assertEquals("[hex=abcdef0187654321]", data.toString())
+ }
+
+ @Test
+ fun writeLastIntegerInSegment() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 4))
+ sink.writeInt(-0x543210ff)
+ sink.writeInt(-0x789abcdf)
+ sink.flush()
+ assertEquals(listOf(SEGMENT_SIZE, 4), segmentSizes(data))
+ assertEquals("a".repeat(SEGMENT_SIZE - 4), data.readUtf8((SEGMENT_SIZE - 4).toLong()))
+ assertEquals("[hex=abcdef0187654321]", data.toString())
+ }
+
+ @Test
+ fun writeIntegerDoesNotQuiteFitInSegment() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 3))
+ sink.writeInt(-0x543210ff)
+ sink.writeInt(-0x789abcdf)
+ sink.flush()
+ assertEquals(listOf(SEGMENT_SIZE - 3, 8), segmentSizes(data))
+ assertEquals("a".repeat(SEGMENT_SIZE - 3), data.readUtf8((SEGMENT_SIZE - 3).toLong()))
+ assertEquals("[hex=abcdef0187654321]", data.toString())
+ }
+
+ @Test
+ fun writeIntLe() {
+ sink.writeIntLe(-0x543210ff)
+ sink.writeIntLe(-0x789abcdf)
+ sink.flush()
+ assertEquals("[hex=01efcdab21436587]", data.toString())
+ }
+
+ @Test
+ fun writeLong() {
+ sink.writeLong(-0x543210fe789abcdfL)
+ sink.writeLong(-0x350145414f4ea400L)
+ sink.flush()
+ assertEquals("[hex=abcdef0187654321cafebabeb0b15c00]", data.toString())
+ }
+
+ @Test
+ fun writeLongLe() {
+ sink.writeLongLe(-0x543210fe789abcdfL)
+ sink.writeLongLe(-0x350145414f4ea400L)
+ sink.flush()
+ assertEquals("[hex=2143658701efcdab005cb1b0bebafeca]", data.toString())
+ }
+
+ @Test
+ fun writeByteString() {
+ sink.write("təˈranəˌsôr".encodeUtf8())
+ sink.flush()
+ assertEquals("74c999cb8872616ec999cb8c73c3b472".decodeHex(), data.readByteString())
+ }
+
+ @Test
+ fun writeByteStringOffset() {
+ sink.write("təˈranəˌsôr".encodeUtf8(), 5, 5)
+ sink.flush()
+ assertEquals("72616ec999".decodeHex(), data.readByteString())
+ }
+
+ @Test
+ fun writeSegmentedByteString() {
+ sink.write(Buffer().write("təˈranəˌsôr".encodeUtf8()).snapshot())
+ sink.flush()
+ assertEquals("74c999cb8872616ec999cb8c73c3b472".decodeHex(), data.readByteString())
+ }
+
+ @Test
+ fun writeSegmentedByteStringOffset() {
+ sink.write(Buffer().write("təˈranəˌsôr".encodeUtf8()).snapshot(), 5, 5)
+ sink.flush()
+ assertEquals("72616ec999".decodeHex(), data.readByteString())
+ }
+
+ @Test
+ fun writeStringUtf8() {
+ sink.writeUtf8("təˈranəˌsôr")
+ sink.flush()
+ assertEquals("74c999cb8872616ec999cb8c73c3b472".decodeHex(), data.readByteString())
+ }
+
+ @Test
+ fun writeSubstringUtf8() {
+ sink.writeUtf8("təˈranəˌsôr", 3, 7)
+ sink.flush()
+ assertEquals("72616ec999".decodeHex(), data.readByteString())
+ }
+
+ @Test
+ fun writeStringWithCharset() {
+ sink.writeString("təˈranəˌsôr", Charset.forName("utf-32be"))
+ sink.flush()
+ assertEquals(
+ (
+ "0000007400000259000002c800000072000000610000006e00000259" +
+ "000002cc00000073000000f400000072"
+ ).decodeHex(),
+ data.readByteString(),
+ )
+ }
+
+ @Test
+ fun writeSubstringWithCharset() {
+ sink.writeString("təˈranəˌsôr", 3, 7, Charset.forName("utf-32be"))
+ sink.flush()
+ assertEquals("00000072000000610000006e00000259".decodeHex(), data.readByteString())
+ }
+
+ @Test
+ fun writeUtf8SubstringWithCharset() {
+ sink.writeString("təˈranəˌsôr", 3, 7, Charset.forName("utf-8"))
+ sink.flush()
+ assertEquals("ranə".encodeUtf8(), data.readByteString())
+ }
+
+ @Test
+ fun writeAll() {
+ val source = Buffer().writeUtf8("abcdef")
+ assertEquals(6, sink.writeAll(source))
+ assertEquals(0, source.size)
+ sink.flush()
+ assertEquals("abcdef", data.readUtf8())
+ }
+
+ @Test
+ fun writeSource() {
+ val source = Buffer().writeUtf8("abcdef")
+
+ // Force resolution of the Source method overload.
+ sink.write((source as Source), 4)
+ sink.flush()
+ assertEquals("abcd", data.readUtf8())
+ assertEquals("ef", source.readUtf8())
+ }
+
+ @Test
+ fun writeSourceReadsFully() {
+ val source: Source = object : ForwardingSource(Buffer()) {
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ sink.writeUtf8("abcd")
+ return 4
+ }
+ }
+ sink.write(source, 8)
+ sink.flush()
+ assertEquals("abcdabcd", data.readUtf8())
+ }
+
+ @Test
+ fun writeSourcePropagatesEof() {
+ val source: Source = Buffer().writeUtf8("abcd")
+ try {
+ sink.write(source, 8)
+ fail()
+ } catch (expected: EOFException) {
+ }
+
+ // Ensure that whatever was available was correctly written.
+ sink.flush()
+ assertEquals("abcd", data.readUtf8())
+ }
+
+ @Test
+ fun writeSourceWithZeroIsNoOp() {
+ // This test ensures that a zero byte count never calls through to read the source. It may be
+ // tied to something like a socket which will potentially block trying to read a segment when
+ // ultimately we don't want any data.
+ val source: Source = object : ForwardingSource(Buffer()) {
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ throw AssertionError()
+ }
+ }
+ sink.write(source, 0)
+ assertEquals(0, data.size)
+ }
+
+ @Test
+ fun writeAllExhausted() {
+ val source = Buffer()
+ assertEquals(0, sink.writeAll(source))
+ assertEquals(0, source.size)
+ }
+
+ @Test
+ fun closeEmitsBufferedBytes() {
+ sink.writeByte('a'.code)
+ sink.close()
+ assertEquals('a'.code.toLong(), data.readByte().toLong())
+ }
+
+ @Test
+ fun outputStream() {
+ val out = sink.outputStream()
+ out.write('a'.code)
+ out.write("b".repeat(9998).toByteArray(UTF_8))
+ out.write('c'.code)
+ out.flush()
+ assertEquals("a" + "b".repeat(9998) + "c", data.readUtf8())
+ }
+
+ @Test
+ fun outputStreamBounds() {
+ val out = sink.outputStream()
+ try {
+ out.write(ByteArray(100), 50, 51)
+ fail()
+ } catch (expected: ArrayIndexOutOfBoundsException) {
+ }
+ }
+
+ @Test
+ fun longDecimalString() {
+ assertLongDecimalString(0)
+ assertLongDecimalString(Long.MIN_VALUE)
+ assertLongDecimalString(Long.MAX_VALUE)
+ for (i in 1..19) {
+ val value = BigInteger.valueOf(10L).pow(i).toLong()
+ assertLongDecimalString(value - 1)
+ assertLongDecimalString(value)
+ }
+ }
+
+ private fun assertLongDecimalString(value: Long) {
+ sink.writeDecimalLong(value).writeUtf8("zzz").flush()
+ val expected = java.lang.Long.toString(value) + "zzz"
+ val actual = data.readUtf8()
+ assertEquals("$value expected $expected but was $actual", actual, expected)
+ }
+
+ @Test
+ fun longHexString() {
+ assertLongHexString(0)
+ assertLongHexString(Long.MIN_VALUE)
+ assertLongHexString(Long.MAX_VALUE)
+ for (i in 0..62) {
+ assertLongHexString((1L shl i) - 1)
+ assertLongHexString(1L shl i)
+ }
+ }
+
+ @Test
+ fun writeNioBuffer() {
+ val expected = "abcdefg"
+ val nioByteBuffer = ByteBuffer.allocate(1024)
+ nioByteBuffer.put("abcdefg".toByteArray(UTF_8))
+ (nioByteBuffer as java.nio.Buffer).flip() // Cast necessary for Java 8.
+ val byteCount = sink.write(nioByteBuffer)
+ assertEquals(expected.length.toLong(), byteCount.toLong())
+ assertEquals(expected.length.toLong(), nioByteBuffer.position().toLong())
+ assertEquals(expected.length.toLong(), nioByteBuffer.limit().toLong())
+ sink.flush()
+ assertEquals(expected, data.readUtf8())
+ }
+
+ @Test
+ fun writeLargeNioBufferWritesAllData() {
+ val expected = "a".repeat(SEGMENT_SIZE * 3)
+ val nioByteBuffer = ByteBuffer.allocate(SEGMENT_SIZE * 4)
+ nioByteBuffer.put("a".repeat(SEGMENT_SIZE * 3).toByteArray(UTF_8))
+ (nioByteBuffer as java.nio.Buffer).flip() // Cast necessary for Java 8.
+ val byteCount = sink.write(nioByteBuffer)
+ assertEquals(expected.length.toLong(), byteCount.toLong())
+ assertEquals(expected.length.toLong(), nioByteBuffer.position().toLong())
+ assertEquals(expected.length.toLong(), nioByteBuffer.limit().toLong())
+ sink.flush()
+ assertEquals(expected, data.readUtf8())
+ }
+
+ private fun assertLongHexString(value: Long) {
+ sink.writeHexadecimalUnsignedLong(value).writeUtf8("zzz").flush()
+ val expected = String.format("%x", value) + "zzz"
+ val actual = data.readUtf8()
+ assertEquals("$value expected $expected but was $actual", actual, expected)
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun parameters(): List<Array<Any>> = listOf(
+ arrayOf(Factory.BUFFER),
+ arrayOf(Factory.REAL_BUFFERED_SINK),
+ )
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/BufferedSourceJavaTest.kt b/okio/src/jvmTest/kotlin/okio/BufferedSourceJavaTest.kt
new file mode 100644
index 00000000..cd7f15ea
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/BufferedSourceJavaTest.kt
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.EOFException
+import java.io.IOException
+import kotlin.text.Charsets.UTF_8
+import okio.TestUtil.SEGMENT_SIZE
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+
+/**
+ * Tests solely for the behavior of RealBufferedSource's implementation. For generic
+ * BufferedSource behavior use BufferedSourceTest.
+ */
+class BufferedSourceJavaTest {
+ @Test
+ fun inputStreamTracksSegments() {
+ val source = Buffer()
+ source.writeUtf8("a")
+ source.writeUtf8("b".repeat(SEGMENT_SIZE))
+ source.writeUtf8("c")
+ val `in` = (source as Source).buffer().inputStream()
+ assertEquals(0, `in`.available().toLong())
+ assertEquals((SEGMENT_SIZE + 2).toLong(), source.size)
+
+ // Reading one byte buffers a full segment.
+ assertEquals('a'.code.toLong(), `in`.read().toLong())
+ assertEquals((SEGMENT_SIZE - 1).toLong(), `in`.available().toLong())
+ assertEquals(2, source.size)
+
+ // Reading as much as possible reads the rest of that buffered segment.
+ val data = ByteArray(SEGMENT_SIZE * 2)
+ assertEquals((SEGMENT_SIZE - 1).toLong(), `in`.read(data, 0, data.size).toLong())
+ assertEquals("b".repeat(SEGMENT_SIZE - 1), String(data, 0, SEGMENT_SIZE - 1, UTF_8))
+ assertEquals(2, source.size)
+
+ // Continuing to read buffers the next segment.
+ assertEquals('b'.code.toLong(), `in`.read().toLong())
+ assertEquals(1, `in`.available().toLong())
+ assertEquals(0, source.size)
+
+ // Continuing to read reads from the buffer.
+ assertEquals('c'.code.toLong(), `in`.read().toLong())
+ assertEquals(0, `in`.available().toLong())
+ assertEquals(0, source.size)
+
+ // Once we've exhausted the source, we're done.
+ assertEquals(-1, `in`.read().toLong())
+ assertEquals(0, source.size)
+ }
+
+ @Test
+ fun inputStreamCloses() {
+ val source = (Buffer() as Source).buffer()
+ val inputStream = source.inputStream()
+ inputStream.close()
+ try {
+ source.require(1)
+ fail()
+ } catch (e: IllegalStateException) {
+ assertEquals("closed", e.message)
+ }
+ }
+
+ @Test
+ fun indexOfStopsReadingAtLimit() {
+ val buffer = Buffer().writeUtf8("abcdef")
+ val bufferedSource = object : ForwardingSource(buffer) {
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ return super.read(sink, Math.min(1, byteCount))
+ }
+ }.buffer()
+ assertEquals(6, buffer.size)
+ assertEquals(-1, bufferedSource.indexOf('e'.code.toByte(), 0, 4))
+ assertEquals(2, buffer.size)
+ }
+
+ @Test
+ fun requireTracksBufferFirst() {
+ val source = Buffer()
+ source.writeUtf8("bb")
+ val bufferedSource = (source as Source).buffer()
+ bufferedSource.buffer.writeUtf8("aa")
+ bufferedSource.require(2)
+ assertEquals(2, bufferedSource.buffer.size)
+ assertEquals(2, source.size)
+ }
+
+ @Test
+ fun requireIncludesBufferBytes() {
+ val source = Buffer()
+ source.writeUtf8("b")
+ val bufferedSource = (source as Source).buffer()
+ bufferedSource.buffer.writeUtf8("a")
+ bufferedSource.require(2)
+ assertEquals("ab", bufferedSource.buffer.readUtf8(2))
+ }
+
+ @Test
+ fun requireInsufficientData() {
+ val source = Buffer()
+ source.writeUtf8("a")
+ val bufferedSource = (source as Source).buffer()
+ try {
+ bufferedSource.require(2)
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun requireReadsOneSegmentAtATime() {
+ val source = Buffer()
+ source.writeUtf8("a".repeat(SEGMENT_SIZE))
+ source.writeUtf8("b".repeat(SEGMENT_SIZE))
+ val bufferedSource = (source as Source).buffer()
+ bufferedSource.require(2)
+ assertEquals(SEGMENT_SIZE.toLong(), source.size)
+ assertEquals(SEGMENT_SIZE.toLong(), bufferedSource.buffer.size)
+ }
+
+ @Test
+ fun skipReadsOneSegmentAtATime() {
+ val source = Buffer()
+ source.writeUtf8("a".repeat(SEGMENT_SIZE))
+ source.writeUtf8("b".repeat(SEGMENT_SIZE))
+ val bufferedSource = (source as Source).buffer()
+ bufferedSource.skip(2)
+ assertEquals(SEGMENT_SIZE.toLong(), source.size)
+ assertEquals((SEGMENT_SIZE - 2).toLong(), bufferedSource.buffer.size)
+ }
+
+ @Test
+ fun skipTracksBufferFirst() {
+ val source = Buffer()
+ source.writeUtf8("bb")
+ val bufferedSource = (source as Source).buffer()
+ bufferedSource.buffer.writeUtf8("aa")
+ bufferedSource.skip(2)
+ assertEquals(0, bufferedSource.buffer.size)
+ assertEquals(2, source.size)
+ }
+
+ @Test
+ fun operationsAfterClose() {
+ val source = Buffer()
+ val bufferedSource = (source as Source).buffer()
+ bufferedSource.close()
+
+ // Test a sample set of methods.
+ try {
+ bufferedSource.indexOf(1.toByte())
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ try {
+ bufferedSource.skip(1)
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ try {
+ bufferedSource.readByte()
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+ try {
+ bufferedSource.readByteString(10)
+ fail()
+ } catch (expected: IllegalStateException) {
+ }
+
+ // Test a sample set of methods on the InputStream.
+ val inputStream = bufferedSource.inputStream()
+ try {
+ inputStream.read()
+ fail()
+ } catch (expected: IOException) {
+ }
+ try {
+ inputStream.read(ByteArray(10))
+ fail()
+ } catch (expected: IOException) {
+ }
+ }
+
+ /**
+ * We don't want readAll to buffer an unbounded amount of data. Instead it
+ * should buffer a segment, write it, and repeat.
+ */
+ @Test
+ fun readAllReadsOneSegmentAtATime() {
+ val write1 = Buffer().writeUtf8("a".repeat(SEGMENT_SIZE))
+ val write2 = Buffer().writeUtf8("b".repeat(SEGMENT_SIZE))
+ val write3 = Buffer().writeUtf8("c".repeat(SEGMENT_SIZE))
+ val source = Buffer().writeUtf8(
+ "" +
+ "a".repeat(SEGMENT_SIZE) +
+ "b".repeat(SEGMENT_SIZE) +
+ "c".repeat(SEGMENT_SIZE),
+ )
+ val mockSink = MockSink()
+ val bufferedSource = (source as Source).buffer()
+ assertEquals((SEGMENT_SIZE * 3).toLong(), bufferedSource.readAll(mockSink))
+ mockSink.assertLog(
+ "write(" + write1 + ", " + write1.size + ")",
+ "write(" + write2 + ", " + write2.size + ")",
+ "write(" + write3 + ", " + write3.size + ")",
+ )
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/BufferedSourceTest.kt b/okio/src/jvmTest/kotlin/okio/BufferedSourceTest.kt
new file mode 100644
index 00000000..b30944b7
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/BufferedSourceTest.kt
@@ -0,0 +1,1503 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.EOFException
+import java.nio.ByteBuffer
+import java.nio.charset.Charset
+import java.util.Arrays
+import kotlin.text.Charsets.US_ASCII
+import kotlin.text.Charsets.UTF_8
+import okio.ByteString.Companion.decodeHex
+import okio.ByteString.Companion.encodeUtf8
+import okio.Options.Companion.of
+import okio.TestUtil.SEGMENT_SIZE
+import okio.TestUtil.assertByteArrayEquals
+import okio.TestUtil.assertByteArraysEquals
+import okio.TestUtil.randomBytes
+import okio.TestUtil.segmentSizes
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Assume
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class BufferedSourceTest(
+ private val factory: Factory,
+) {
+ interface Factory {
+ fun pipe(): Pipe
+ val isOneByteAtATime: Boolean
+
+ companion object {
+ val BUFFER: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ return Pipe(buffer, buffer)
+ }
+
+ override val isOneByteAtATime: Boolean get() = false
+
+ override fun toString() = "Buffer"
+ }
+
+ val REAL_BUFFERED_SOURCE: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ return Pipe(
+ sink = buffer,
+ source = (buffer as Source).buffer(),
+ )
+ }
+
+ override val isOneByteAtATime: Boolean get() = false
+
+ override fun toString() = "RealBufferedSource"
+ }
+
+ /**
+ * A factory deliberately written to create buffers whose internal segments are always 1 byte
+ * long. We like testing with these segments because are likely to trigger bugs!
+ */
+ val ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ return Pipe(
+ sink = buffer,
+ source = object : ForwardingSource(buffer) {
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ // Read one byte into a new buffer, then clone it so that the segment is shared.
+ // Shared segments cannot be compacted so we'll get a long chain of short segments.
+ val box = Buffer()
+ val result = super.read(box, Math.min(byteCount, 1L))
+ if (result > 0L) sink.write(box.clone(), result)
+ return result
+ }
+ }.buffer(),
+ )
+ }
+
+ override val isOneByteAtATime: Boolean get() = true
+
+ override fun toString() = "OneByteAtATimeBufferedSource"
+ }
+
+ val ONE_BYTE_AT_A_TIME_BUFFER: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ val sink = object : ForwardingSink(buffer) {
+ override fun write(source: Buffer, byteCount: Long) {
+ // Write each byte into a new buffer, then clone it so that the segments are shared.
+ // Shared segments cannot be compacted so we'll get a long chain of short segments.
+ for (i in 0 until byteCount) {
+ val box = Buffer()
+ box.write(source, 1)
+ super.write(box.clone(), 1)
+ }
+ }
+ }.buffer()
+ return Pipe(
+ sink = sink,
+ source = buffer,
+ )
+ }
+
+ override val isOneByteAtATime: Boolean get() = true
+
+ override fun toString() = "OneByteAtATimeBuffer"
+ }
+
+ val PEEK_BUFFER: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ return Pipe(
+ sink = buffer,
+ source = buffer.peek(),
+ )
+ }
+
+ override val isOneByteAtATime: Boolean get() = false
+
+ override fun toString() = "PeekBuffer"
+ }
+
+ val PEEK_BUFFERED_SOURCE: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ return Pipe(
+ sink = buffer,
+ source = (buffer as Source).buffer().peek(),
+ )
+ }
+
+ override val isOneByteAtATime: Boolean get() = false
+
+ override fun toString() = "PeekBufferedSource"
+ }
+ }
+ }
+
+ class Pipe(
+ var sink: BufferedSink,
+ var source: BufferedSource,
+ )
+
+ private val pipe = factory.pipe()
+ private val sink: BufferedSink = pipe.sink
+ private val source: BufferedSource = pipe.source
+
+ @Test
+ fun readBytes() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte()))
+ sink.emit()
+ assertEquals(0xab, (source.readByte().toInt() and 0xff).toLong())
+ assertEquals(0xcd, (source.readByte().toInt() and 0xff).toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readByteTooShortThrows() {
+ try {
+ source.readByte()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readShort() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x01.toByte()))
+ sink.emit()
+ assertEquals(0xabcd.toShort().toLong(), source.readShort().toLong())
+ assertEquals(0xef01.toShort().toLong(), source.readShort().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readShortLe() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x10.toByte()))
+ sink.emit()
+ assertEquals(0xcdab.toShort().toLong(), source.readShortLe().toLong())
+ assertEquals(0x10ef.toShort().toLong(), source.readShortLe().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readShortSplitAcrossMultipleSegments() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 1))
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte()))
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 1).toLong())
+ assertEquals(0xabcd.toShort().toLong(), source.readShort().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readShortTooShortThrows() {
+ sink.writeShort(Short.MAX_VALUE.toInt())
+ sink.emit()
+ source.readByte()
+ try {
+ source.readShort()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readShortLeTooShortThrows() {
+ sink.writeShortLe(Short.MAX_VALUE.toInt())
+ sink.emit()
+ source.readByte()
+ try {
+ source.readShortLe()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readInt() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x01.toByte(), 0x87.toByte(), 0x65.toByte(), 0x43.toByte(), 0x21.toByte()))
+ sink.emit()
+ assertEquals(-0x543210ff, source.readInt().toLong())
+ assertEquals(-0x789abcdf, source.readInt().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readIntLe() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x10.toByte(), 0x87.toByte(), 0x65.toByte(), 0x43.toByte(), 0x21.toByte()))
+ sink.emit()
+ assertEquals(0x10efcdab, source.readIntLe().toLong())
+ assertEquals(0x21436587, source.readIntLe().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readIntSplitAcrossMultipleSegments() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 3))
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x01.toByte()))
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 3).toLong())
+ assertEquals(-0x543210ff, source.readInt().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readIntTooShortThrows() {
+ sink.writeInt(Int.MAX_VALUE)
+ sink.emit()
+ source.readByte()
+ try {
+ source.readInt()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readIntLeTooShortThrows() {
+ sink.writeIntLe(Int.MAX_VALUE)
+ sink.emit()
+ source.readByte()
+ try {
+ source.readIntLe()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readLong() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x10.toByte(), 0x87.toByte(), 0x65.toByte(), 0x43.toByte(), 0x21.toByte(), 0x36.toByte(), 0x47.toByte(), 0x58.toByte(), 0x69.toByte(), 0x12.toByte(), 0x23.toByte(), 0x34.toByte(), 0x45.toByte()))
+ sink.emit()
+ assertEquals(-0x543210ef789abcdfL, source.readLong())
+ assertEquals(0x3647586912233445L, source.readLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readLongLe() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x10.toByte(), 0x87.toByte(), 0x65.toByte(), 0x43.toByte(), 0x21.toByte(), 0x36.toByte(), 0x47.toByte(), 0x58.toByte(), 0x69.toByte(), 0x12.toByte(), 0x23.toByte(), 0x34.toByte(), 0x45.toByte()))
+ sink.emit()
+ assertEquals(0x2143658710efcdabL, source.readLongLe())
+ assertEquals(0x4534231269584736L, source.readLongLe())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readLongSplitAcrossMultipleSegments() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 7))
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x01.toByte(), 0x87.toByte(), 0x65.toByte(), 0x43.toByte(), 0x21.toByte()))
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 7).toLong())
+ assertEquals(-0x543210fe789abcdfL, source.readLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readLongTooShortThrows() {
+ sink.writeLong(Long.MAX_VALUE)
+ sink.emit()
+ source.readByte()
+ try {
+ source.readLong()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readLongLeTooShortThrows() {
+ sink.writeLongLe(Long.MAX_VALUE)
+ sink.emit()
+ source.readByte()
+ try {
+ source.readLongLe()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readAll() {
+ source.buffer.writeUtf8("abc")
+ sink.writeUtf8("def")
+ sink.emit()
+ val sink = Buffer()
+ assertEquals(6, source.readAll(sink))
+ assertEquals("abcdef", sink.readUtf8())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readAllExhausted() {
+ val mockSink = MockSink()
+ assertEquals(0, source.readAll(mockSink))
+ assertTrue(source.exhausted())
+ mockSink.assertLog()
+ }
+
+ @Test
+ fun readExhaustedSource() {
+ val sink = Buffer()
+ sink.writeUtf8("a".repeat(10))
+ assertEquals(-1, source.read(sink, 10))
+ assertEquals(10, sink.size)
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readZeroBytesFromSource() {
+ val sink = Buffer()
+ sink.writeUtf8("a".repeat(10))
+
+ // Either 0 or -1 is reasonable here. For consistency with Android's
+ // ByteArrayInputStream we return 0.
+ assertEquals(-1, source.read(sink, 0))
+ assertEquals(10, sink.size)
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readFully() {
+ sink.writeUtf8("a".repeat(10000))
+ sink.emit()
+ val sink = Buffer()
+ source.readFully(sink, 9999)
+ assertEquals("a".repeat(9999), sink.readUtf8())
+ assertEquals("a", source.readUtf8())
+ }
+
+ @Test
+ fun readFullyTooShortThrows() {
+ sink.writeUtf8("Hi")
+ sink.emit()
+ val sink = Buffer()
+ try {
+ source.readFully(sink, 5)
+ fail()
+ } catch (ignored: EOFException) {
+ }
+
+ // Verify we read all that we could from the source.
+ assertEquals("Hi", sink.readUtf8())
+ }
+
+ @Test
+ fun readFullyByteArray() {
+ val data = Buffer()
+ data.writeUtf8("Hello").writeUtf8("e".repeat(SEGMENT_SIZE))
+ val expected = data.clone().readByteArray()
+ sink.write(data, data.size)
+ sink.emit()
+ val sink = ByteArray(SEGMENT_SIZE + 5)
+ source.readFully(sink)
+ assertByteArraysEquals(expected, sink)
+ }
+
+ @Test
+ fun readFullyByteArrayTooShortThrows() {
+ sink.writeUtf8("Hello")
+ sink.emit()
+ val array = ByteArray(6)
+ try {
+ source.readFully(array)
+ fail()
+ } catch (ignored: EOFException) {
+ }
+
+ // Verify we read all that we could from the source.
+ assertByteArraysEquals(byteArrayOf('H'.code.toByte(), 'e'.code.toByte(), 'l'.code.toByte(), 'l'.code.toByte(), 'o'.code.toByte(), 0), array)
+ }
+
+ @Test
+ fun readIntoByteArray() {
+ sink.writeUtf8("abcd")
+ sink.emit()
+ val sink = ByteArray(3)
+ val read = source.read(sink)
+ if (factory.isOneByteAtATime) {
+ assertEquals(1, read.toLong())
+ val expected = byteArrayOf('a'.code.toByte(), 0, 0)
+ assertByteArraysEquals(expected, sink)
+ } else {
+ assertEquals(3, read.toLong())
+ val expected = byteArrayOf('a'.code.toByte(), 'b'.code.toByte(), 'c'.code.toByte())
+ assertByteArraysEquals(expected, sink)
+ }
+ }
+
+ @Test
+ fun readIntoByteArrayNotEnough() {
+ sink.writeUtf8("abcd")
+ sink.emit()
+ val sink = ByteArray(5)
+ val read = source.read(sink)
+ if (factory.isOneByteAtATime) {
+ assertEquals(1, read.toLong())
+ val expected = byteArrayOf('a'.code.toByte(), 0, 0, 0, 0)
+ assertByteArraysEquals(expected, sink)
+ } else {
+ assertEquals(4, read.toLong())
+ val expected = byteArrayOf('a'.code.toByte(), 'b'.code.toByte(), 'c'.code.toByte(), 'd'.code.toByte(), 0)
+ assertByteArraysEquals(expected, sink)
+ }
+ }
+
+ @Test
+ fun readIntoByteArrayOffsetAndCount() {
+ sink.writeUtf8("abcd")
+ sink.emit()
+ val sink = ByteArray(7)
+ val read = source.read(sink, 2, 3)
+ if (factory.isOneByteAtATime) {
+ assertEquals(1, read.toLong())
+ val expected = byteArrayOf(0, 0, 'a'.code.toByte(), 0, 0, 0, 0)
+ assertByteArraysEquals(expected, sink)
+ } else {
+ assertEquals(3, read.toLong())
+ val expected = byteArrayOf(0, 0, 'a'.code.toByte(), 'b'.code.toByte(), 'c'.code.toByte(), 0, 0)
+ assertByteArraysEquals(expected, sink)
+ }
+ }
+
+ @Test
+ fun readByteArray() {
+ val string = "abcd" + "e".repeat(SEGMENT_SIZE)
+ sink.writeUtf8(string)
+ sink.emit()
+ assertByteArraysEquals(string.toByteArray(UTF_8), source.readByteArray())
+ }
+
+ @Test
+ fun readByteArrayPartial() {
+ sink.writeUtf8("abcd")
+ sink.emit()
+ assertEquals("[97, 98, 99]", Arrays.toString(source.readByteArray(3)))
+ assertEquals("d", source.readUtf8(1))
+ }
+
+ @Test
+ fun readByteArrayTooShortThrows() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ try {
+ source.readByteArray(4)
+ fail()
+ } catch (expected: EOFException) {
+ }
+ assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data.
+ }
+
+ @Test
+ fun readByteString() {
+ sink.writeUtf8("abcd").writeUtf8("e".repeat(SEGMENT_SIZE))
+ sink.emit()
+ assertEquals("abcd" + "e".repeat(SEGMENT_SIZE), source.readByteString().utf8())
+ }
+
+ @Test
+ fun readByteStringPartial() {
+ sink.writeUtf8("abcd").writeUtf8("e".repeat(SEGMENT_SIZE))
+ sink.emit()
+ assertEquals("abc", source.readByteString(3).utf8())
+ assertEquals("d", source.readUtf8(1))
+ }
+
+ @Test
+ fun readByteStringTooShortThrows() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ try {
+ source.readByteString(4)
+ fail()
+ } catch (expected: EOFException) {
+ }
+ assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data.
+ }
+
+ @Test
+ fun readSpecificCharsetPartial() {
+ sink.write(
+ (
+ "0000007600000259000002c80000006c000000e40000007300000259" +
+ "000002cc000000720000006100000070000000740000025900000072"
+ ).decodeHex(),
+ )
+ sink.emit()
+ assertEquals("vəˈläsə", source.readString((7 * 4).toLong(), Charset.forName("utf-32")))
+ }
+
+ @Test
+ fun readSpecificCharset() {
+ sink.write(
+ (
+ "0000007600000259000002c80000006c000000e40000007300000259" +
+ "000002cc000000720000006100000070000000740000025900000072"
+ ).decodeHex(),
+ )
+ sink.emit()
+ assertEquals("vəˈläsəˌraptər", source.readString(Charset.forName("utf-32")))
+ }
+
+ @Test
+ fun readStringTooShortThrows() {
+ sink.writeString("abc", US_ASCII)
+ sink.emit()
+ try {
+ source.readString(4, US_ASCII)
+ fail()
+ } catch (expected: EOFException) {
+ }
+ assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data.
+ }
+
+ @Test
+ fun readUtf8SpansSegments() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE * 2))
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 1).toLong())
+ assertEquals("aa", source.readUtf8(2))
+ }
+
+ @Test
+ fun readUtf8Segment() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE))
+ sink.emit()
+ assertEquals("a".repeat(SEGMENT_SIZE), source.readUtf8(SEGMENT_SIZE.toLong()))
+ }
+
+ @Test
+ fun readUtf8PartialBuffer() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE + 20))
+ sink.emit()
+ assertEquals("a".repeat(SEGMENT_SIZE + 10), source.readUtf8((SEGMENT_SIZE + 10).toLong()))
+ }
+
+ @Test
+ fun readUtf8EntireBuffer() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE * 2))
+ sink.emit()
+ assertEquals("a".repeat(SEGMENT_SIZE * 2), source.readUtf8())
+ }
+
+ @Test
+ fun readUtf8TooShortThrows() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ try {
+ source.readUtf8(4L)
+ fail()
+ } catch (expected: EOFException) {
+ }
+ assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data.
+ }
+
+ @Test
+ fun skip() {
+ sink.writeUtf8("a")
+ sink.writeUtf8("b".repeat(SEGMENT_SIZE))
+ sink.writeUtf8("c")
+ sink.emit()
+ source.skip(1)
+ assertEquals('b'.code.toLong(), (source.readByte().toInt() and 0xff).toLong())
+ source.skip((SEGMENT_SIZE - 2).toLong())
+ assertEquals('b'.code.toLong(), (source.readByte().toInt() and 0xff).toLong())
+ source.skip(1)
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun skipInsufficientData() {
+ sink.writeUtf8("a")
+ sink.emit()
+ try {
+ source.skip(2)
+ fail()
+ } catch (ignored: EOFException) {
+ }
+ }
+
+ @Test
+ fun indexOf() {
+ // The segment is empty.
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
+
+ // The segment has one value.
+ sink.writeUtf8("a") // a
+ sink.emit()
+ assertEquals(0, source.indexOf('a'.code.toByte()))
+ assertEquals(-1, source.indexOf('b'.code.toByte()))
+
+ // The segment has lots of data.
+ sink.writeUtf8("b".repeat(SEGMENT_SIZE - 2)) // ab...b
+ sink.emit()
+ assertEquals(0, source.indexOf('a'.code.toByte()))
+ assertEquals(1, source.indexOf('b'.code.toByte()))
+ assertEquals(-1, source.indexOf('c'.code.toByte()))
+
+ // The segment doesn't start at 0, it starts at 2.
+ source.skip(2) // b...b
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('b'.code.toByte()))
+ assertEquals(-1, source.indexOf('c'.code.toByte()))
+
+ // The segment is full.
+ sink.writeUtf8("c") // b...bc
+ sink.emit()
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('b'.code.toByte()))
+ assertEquals((SEGMENT_SIZE - 3).toLong(), source.indexOf('c'.code.toByte()))
+
+ // The segment doesn't start at 2, it starts at 4.
+ source.skip(2) // b...bc
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('b'.code.toByte()))
+ assertEquals((SEGMENT_SIZE - 5).toLong(), source.indexOf('c'.code.toByte()))
+
+ // Two segments.
+ sink.writeUtf8("d") // b...bcd, d is in the 2nd segment.
+ sink.emit()
+ assertEquals((SEGMENT_SIZE - 4).toLong(), source.indexOf('d'.code.toByte()))
+ assertEquals(-1, source.indexOf('e'.code.toByte()))
+ }
+
+ @Test
+ fun indexOfByteWithStartOffset() {
+ sink.writeUtf8("a").writeUtf8("b".repeat(SEGMENT_SIZE)).writeUtf8("c")
+ sink.emit()
+ assertEquals(-1, source.indexOf('a'.code.toByte(), 1))
+ assertEquals(15, source.indexOf('b'.code.toByte(), 15))
+ }
+
+ @Test
+ fun indexOfByteWithBothOffsets() {
+ if (factory.isOneByteAtATime) {
+ // When run on Travis this causes out-of-memory errors.
+ return
+ }
+ val a = 'a'.code.toByte()
+ val c = 'c'.code.toByte()
+ val size: Int = SEGMENT_SIZE * 5
+ val bytes = ByteArray(size)
+ Arrays.fill(bytes, a)
+
+ // These are tricky places where the buffer
+ // starts, ends, or segments come together.
+ val points = intArrayOf(
+ 0, 1, 2,
+ SEGMENT_SIZE - 1, SEGMENT_SIZE, SEGMENT_SIZE + 1,
+ size / 2 - 1, size / 2, size / 2 + 1,
+ size - SEGMENT_SIZE - 1, size - SEGMENT_SIZE, size - SEGMENT_SIZE + 1,
+ size - 3, size - 2, size - 1,
+ )
+
+ // In each iteration, we write c to the known point and then search for it using different
+ // windows. Some of the windows don't overlap with c's position, and therefore a match shouldn't
+ // be found.
+ for (p in points) {
+ bytes[p] = c
+ sink.write(bytes)
+ sink.emit()
+ assertEquals(p.toLong(), source.indexOf(c, 0, size.toLong()))
+ assertEquals(p.toLong(), source.indexOf(c, 0, (p + 1).toLong()))
+ assertEquals(p.toLong(), source.indexOf(c, p.toLong(), size.toLong()))
+ assertEquals(p.toLong(), source.indexOf(c, p.toLong(), (p + 1).toLong()))
+ assertEquals(p.toLong(), source.indexOf(c, (p / 2).toLong(), (p * 2 + 1).toLong()))
+ assertEquals(-1, source.indexOf(c, 0, (p / 2).toLong()))
+ assertEquals(-1, source.indexOf(c, 0, p.toLong()))
+ assertEquals(-1, source.indexOf(c, 0, 0))
+ assertEquals(-1, source.indexOf(c, p.toLong(), p.toLong()))
+
+ // Reset.
+ source.readUtf8()
+ bytes[p] = a
+ }
+ }
+
+ @Test
+ fun indexOfByteInvalidBoundsThrows() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ try {
+ source.indexOf('a'.code.toByte(), -1)
+ fail("Expected failure: fromIndex < 0")
+ } catch (expected: IllegalArgumentException) {
+ }
+ try {
+ source.indexOf('a'.code.toByte(), 10, 0)
+ fail("Expected failure: fromIndex > toIndex")
+ } catch (expected: IllegalArgumentException) {
+ }
+ }
+
+ @Test
+ fun indexOfByteString() {
+ assertEquals(-1, source.indexOf("flop".encodeUtf8()))
+ sink.writeUtf8("flip flop")
+ sink.emit()
+ assertEquals(5, source.indexOf("flop".encodeUtf8()))
+ source.readUtf8() // Clear stream.
+
+ // Make sure we backtrack and resume searching after partial match.
+ sink.writeUtf8("hi hi hi hey")
+ sink.emit()
+ assertEquals(3, source.indexOf("hi hi hey".encodeUtf8()))
+ }
+
+ @Test
+ fun indexOfByteStringAtSegmentBoundary() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 1))
+ sink.writeUtf8("bcd")
+ sink.emit()
+ assertEquals((SEGMENT_SIZE - 3).toLong(), source.indexOf("aabc".encodeUtf8(), (SEGMENT_SIZE - 4).toLong()))
+ assertEquals((SEGMENT_SIZE - 3).toLong(), source.indexOf("aabc".encodeUtf8(), (SEGMENT_SIZE - 3).toLong()))
+ assertEquals((SEGMENT_SIZE - 2).toLong(), source.indexOf("abcd".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 2).toLong(), source.indexOf("abc".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 2).toLong(), source.indexOf("abc".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 2).toLong(), source.indexOf("ab".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 2).toLong(), source.indexOf("a".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 1).toLong(), source.indexOf("bc".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 1).toLong(), source.indexOf("b".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals(SEGMENT_SIZE.toLong(), source.indexOf("c".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals(SEGMENT_SIZE.toLong(), source.indexOf("c".encodeUtf8(), SEGMENT_SIZE.toLong()))
+ assertEquals((SEGMENT_SIZE + 1).toLong(), source.indexOf("d".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE + 1).toLong(), source.indexOf("d".encodeUtf8(), (SEGMENT_SIZE + 1).toLong()))
+ }
+
+ @Test
+ fun indexOfDoesNotWrapAround() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 1))
+ sink.writeUtf8("bcd")
+ sink.emit()
+ assertEquals(-1, source.indexOf("abcda".encodeUtf8(), (SEGMENT_SIZE - 3).toLong()))
+ }
+
+ @Test
+ fun indexOfByteStringWithOffset() {
+ assertEquals(-1, source.indexOf("flop".encodeUtf8(), 1))
+ sink.writeUtf8("flop flip flop")
+ sink.emit()
+ assertEquals(10, source.indexOf("flop".encodeUtf8(), 1))
+ source.readUtf8() // Clear stream
+
+ // Make sure we backtrack and resume searching after partial match.
+ sink.writeUtf8("hi hi hi hi hey")
+ sink.emit()
+ assertEquals(6, source.indexOf("hi hi hey".encodeUtf8(), 1))
+ }
+
+ @Test
+ fun indexOfByteStringInvalidArgumentsThrows() {
+ try {
+ source.indexOf(ByteString.of())
+ fail()
+ } catch (e: IllegalArgumentException) {
+ assertEquals("bytes is empty", e.message)
+ }
+ try {
+ source.indexOf("hi".encodeUtf8(), -1)
+ fail()
+ } catch (e: IllegalArgumentException) {
+ assertEquals("fromIndex < 0: -1", e.message)
+ }
+ }
+
+ /**
+ * With [Factory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE], this code was extremely slow.
+ * https://github.com/square/okio/issues/171
+ */
+ @Test
+ fun indexOfByteStringAcrossSegmentBoundaries() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE * 2 - 3))
+ sink.writeUtf8("bcdefg")
+ sink.emit()
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("ab".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("abc".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("abcd".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("abcde".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("abcdef".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("abcdefg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 3).toLong(), source.indexOf("bcdefg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 2).toLong(), source.indexOf("cdefg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 1).toLong(), source.indexOf("defg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2).toLong(), source.indexOf("efg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 + 1).toLong(), source.indexOf("fg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 + 2).toLong(), source.indexOf("g".encodeUtf8()))
+ }
+
+ @Test
+ fun indexOfElement() {
+ sink.writeUtf8("a").writeUtf8("b".repeat(SEGMENT_SIZE)).writeUtf8("c")
+ sink.emit()
+ assertEquals(0, source.indexOfElement("DEFGaHIJK".encodeUtf8()))
+ assertEquals(1, source.indexOfElement("DEFGHIJKb".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE + 1).toLong(), source.indexOfElement("cDEFGHIJK".encodeUtf8()))
+ assertEquals(1, source.indexOfElement("DEFbGHIc".encodeUtf8()))
+ assertEquals(-1L, source.indexOfElement("DEFGHIJK".encodeUtf8()))
+ assertEquals(-1L, source.indexOfElement("".encodeUtf8()))
+ }
+
+ @Test
+ fun indexOfElementWithOffset() {
+ sink.writeUtf8("a").writeUtf8("b".repeat(SEGMENT_SIZE)).writeUtf8("c")
+ sink.emit()
+ assertEquals(-1, source.indexOfElement("DEFGaHIJK".encodeUtf8(), 1))
+ assertEquals(15, source.indexOfElement("DEFGHIJKb".encodeUtf8(), 15))
+ }
+
+ @Test
+ fun indexOfByteWithFromIndex() {
+ sink.writeUtf8("aaa")
+ sink.emit()
+ assertEquals(0, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('a'.code.toByte(), 0))
+ assertEquals(1, source.indexOf('a'.code.toByte(), 1))
+ assertEquals(2, source.indexOf('a'.code.toByte(), 2))
+ }
+
+ @Test
+ fun indexOfByteStringWithFromIndex() {
+ sink.writeUtf8("aaa")
+ sink.emit()
+ assertEquals(0, source.indexOf("a".encodeUtf8()))
+ assertEquals(0, source.indexOf("a".encodeUtf8(), 0))
+ assertEquals(1, source.indexOf("a".encodeUtf8(), 1))
+ assertEquals(2, source.indexOf("a".encodeUtf8(), 2))
+ }
+
+ @Test
+ fun indexOfElementWithFromIndex() {
+ sink.writeUtf8("aaa")
+ sink.emit()
+ assertEquals(0, source.indexOfElement("a".encodeUtf8()))
+ assertEquals(0, source.indexOfElement("a".encodeUtf8(), 0))
+ assertEquals(1, source.indexOfElement("a".encodeUtf8(), 1))
+ assertEquals(2, source.indexOfElement("a".encodeUtf8(), 2))
+ }
+
+ @Test
+ fun request() {
+ sink.writeUtf8("a").writeUtf8("b".repeat(SEGMENT_SIZE)).writeUtf8("c")
+ sink.emit()
+ assertTrue(source.request((SEGMENT_SIZE + 2).toLong()))
+ assertFalse(source.request((SEGMENT_SIZE + 3).toLong()))
+ }
+
+ @Test
+ fun require() {
+ sink.writeUtf8("a").writeUtf8("b".repeat(SEGMENT_SIZE)).writeUtf8("c")
+ sink.emit()
+ source.require((SEGMENT_SIZE + 2).toLong())
+ try {
+ source.require((SEGMENT_SIZE + 3).toLong())
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun inputStream() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ val `in` = source.inputStream()
+ val bytes = byteArrayOf('z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte())
+ var read = `in`.read(bytes)
+ if (factory.isOneByteAtATime) {
+ assertEquals(1, read.toLong())
+ assertByteArrayEquals("azz", bytes)
+ read = `in`.read(bytes)
+ assertEquals(1, read.toLong())
+ assertByteArrayEquals("bzz", bytes)
+ read = `in`.read(bytes)
+ assertEquals(1, read.toLong())
+ assertByteArrayEquals("czz", bytes)
+ } else {
+ assertEquals(3, read.toLong())
+ assertByteArrayEquals("abc", bytes)
+ }
+ assertEquals(-1, `in`.read().toLong())
+ }
+
+ @Test
+ fun inputStreamOffsetCount() {
+ sink.writeUtf8("abcde")
+ sink.emit()
+ val `in` = source.inputStream()
+ val bytes = byteArrayOf('z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte())
+ val read = `in`.read(bytes, 1, 3)
+ if (factory.isOneByteAtATime) {
+ assertEquals(1, read.toLong())
+ assertByteArrayEquals("zazzz", bytes)
+ } else {
+ assertEquals(3, read.toLong())
+ assertByteArrayEquals("zabcz", bytes)
+ }
+ }
+
+ @Test
+ fun inputStreamSkip() {
+ sink.writeUtf8("abcde")
+ sink.emit()
+ val `in` = source.inputStream()
+ assertEquals(4, `in`.skip(4))
+ assertEquals('e'.code.toLong(), `in`.read().toLong())
+ sink.writeUtf8("abcde")
+ sink.emit()
+ assertEquals(5, `in`.skip(10)) // Try to skip too much.
+ assertEquals(0, `in`.skip(1)) // Try to skip when exhausted.
+ }
+
+ @Test
+ fun inputStreamCharByChar() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ val `in` = source.inputStream()
+ assertEquals('a'.code.toLong(), `in`.read().toLong())
+ assertEquals('b'.code.toLong(), `in`.read().toLong())
+ assertEquals('c'.code.toLong(), `in`.read().toLong())
+ assertEquals(-1, `in`.read().toLong())
+ }
+
+ @Test
+ fun inputStreamBounds() {
+ sink.writeUtf8("a".repeat(100))
+ sink.emit()
+ val `in` = source.inputStream()
+ try {
+ `in`.read(ByteArray(100), 50, 51)
+ fail()
+ } catch (expected: java.lang.ArrayIndexOutOfBoundsException) {
+ }
+ }
+
+ @Test
+ fun longHexString() {
+ assertLongHexString("8000000000000000", -0x7fffffffffffffffL - 1L)
+ assertLongHexString("fffffffffffffffe", -0x2L)
+ assertLongHexString("FFFFFFFFFFFFFFFe", -0x2L)
+ assertLongHexString("ffffffffffffffff", -0x1L)
+ assertLongHexString("FFFFFFFFFFFFFFFF", -0x1L)
+ assertLongHexString("0000000000000000", 0x0)
+ assertLongHexString("0000000000000001", 0x1)
+ assertLongHexString("7999999999999999", 0x7999999999999999L)
+ assertLongHexString("FF", 0xFF)
+ assertLongHexString("0000000000000001", 0x1)
+ }
+
+ @Test
+ fun hexStringWithManyLeadingZeros() {
+ assertLongHexString("00000000000000001", 0x1)
+ assertLongHexString("0000000000000000ffffffffffffffff", -0x1L)
+ assertLongHexString("00000000000000007fffffffffffffff", 0x7fffffffffffffffL)
+ assertLongHexString("0".repeat(SEGMENT_SIZE + 1) + "1", 0x1)
+ }
+
+ private fun assertLongHexString(s: String, expected: Long) {
+ sink.writeUtf8(s)
+ sink.emit()
+ val actual = source.readHexadecimalUnsignedLong()
+ assertEquals("$s --> $expected", expected, actual)
+ }
+
+ @Test
+ fun longHexStringAcrossSegment() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 8)).writeUtf8("FFFFFFFFFFFFFFFF")
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 8).toLong())
+ assertEquals(-1, source.readHexadecimalUnsignedLong())
+ }
+
+ @Test
+ fun longHexStringTooLongThrows() {
+ try {
+ sink.writeUtf8("fffffffffffffffff")
+ sink.emit()
+ source.readHexadecimalUnsignedLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Number too large: fffffffffffffffff", e.message)
+ }
+ }
+
+ @Test
+ fun longHexStringTooShortThrows() {
+ try {
+ sink.writeUtf8(" ")
+ sink.emit()
+ source.readHexadecimalUnsignedLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Expected leading [0-9a-fA-F] character but was 0x20", e.message)
+ }
+ }
+
+ @Test
+ fun longHexEmptySourceThrows() {
+ try {
+ sink.writeUtf8("")
+ sink.emit()
+ source.readHexadecimalUnsignedLong()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun longDecimalString() {
+ assertLongDecimalString("-9223372036854775808", -9223372036854775807L - 1L)
+ assertLongDecimalString("-1", -1L)
+ assertLongDecimalString("0", 0L)
+ assertLongDecimalString("1", 1L)
+ assertLongDecimalString("9223372036854775807", 9223372036854775807L)
+ assertLongDecimalString("00000001", 1L)
+ assertLongDecimalString("-000001", -1L)
+ }
+
+ private fun assertLongDecimalString(s: String, expected: Long) {
+ sink.writeUtf8(s)
+ sink.writeUtf8("zzz")
+ sink.emit()
+ val actual = source.readDecimalLong()
+ assertEquals("$s --> $expected", expected, actual)
+ assertEquals("zzz", source.readUtf8())
+ }
+
+ @Test
+ fun longDecimalStringAcrossSegment() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 8)).writeUtf8("1234567890123456")
+ sink.writeUtf8("zzz")
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 8).toLong())
+ assertEquals(1234567890123456L, source.readDecimalLong())
+ assertEquals("zzz", source.readUtf8())
+ }
+
+ @Test
+ fun longDecimalStringTooLongThrows() {
+ try {
+ sink.writeUtf8("12345678901234567890") // Too many digits.
+ sink.emit()
+ source.readDecimalLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Number too large: 12345678901234567890", e.message)
+ }
+ }
+
+ @Test
+ fun longDecimalStringTooHighThrows() {
+ try {
+ sink.writeUtf8("9223372036854775808") // Right size but cannot fit.
+ sink.emit()
+ source.readDecimalLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Number too large: 9223372036854775808", e.message)
+ }
+ }
+
+ @Test
+ fun longDecimalStringTooLowThrows() {
+ try {
+ sink.writeUtf8("-9223372036854775809") // Right size but cannot fit.
+ sink.emit()
+ source.readDecimalLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Number too large: -9223372036854775809", e.message)
+ }
+ }
+
+ @Test
+ fun longDecimalStringTooShortThrows() {
+ try {
+ sink.writeUtf8(" ")
+ sink.emit()
+ source.readDecimalLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Expected a digit or '-' but was 0x20", e.message)
+ }
+ }
+
+ @Test
+ fun longDecimalEmptyThrows() {
+ try {
+ sink.writeUtf8("")
+ sink.emit()
+ source.readDecimalLong()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun codePoints() {
+ sink.write("7f".decodeHex())
+ sink.emit()
+ assertEquals(0x7f, source.readUtf8CodePoint().toLong())
+ sink.write("dfbf".decodeHex())
+ sink.emit()
+ assertEquals(0x07ff, source.readUtf8CodePoint().toLong())
+ sink.write("efbfbf".decodeHex())
+ sink.emit()
+ assertEquals(0xffff, source.readUtf8CodePoint().toLong())
+ sink.write("f48fbfbf".decodeHex())
+ sink.emit()
+ assertEquals(0x10ffff, source.readUtf8CodePoint().toLong())
+ }
+
+ @Test
+ fun decimalStringWithManyLeadingZeros() {
+ assertLongDecimalString("00000000000000001", 1)
+ assertLongDecimalString("00000000000000009223372036854775807", 9223372036854775807L)
+ assertLongDecimalString("-00000000000000009223372036854775808", -9223372036854775807L - 1L)
+ assertLongDecimalString("0".repeat(SEGMENT_SIZE + 1) + "1", 1)
+ }
+
+ @Test
+ fun select() {
+ val options = of(
+ "ROCK".encodeUtf8(),
+ "SCISSORS".encodeUtf8(),
+ "PAPER".encodeUtf8(),
+ )
+ sink.writeUtf8("PAPER,SCISSORS,ROCK")
+ sink.emit()
+ assertEquals(2, source.select(options).toLong())
+ assertEquals(','.code.toLong(), source.readByte().toLong())
+ assertEquals(1, source.select(options).toLong())
+ assertEquals(','.code.toLong(), source.readByte().toLong())
+ assertEquals(0, source.select(options).toLong())
+ assertTrue(source.exhausted())
+ }
+
+ /** Note that this test crashes the VM on Android. */
+ @Test
+ fun selectSpanningMultipleSegments() {
+ val commonPrefix = randomBytes(SEGMENT_SIZE + 10)
+ val a = Buffer().write(commonPrefix).writeUtf8("a").readByteString()
+ val bc = Buffer().write(commonPrefix).writeUtf8("bc").readByteString()
+ val bd = Buffer().write(commonPrefix).writeUtf8("bd").readByteString()
+ val options = of(a, bc, bd)
+ sink.write(bd)
+ sink.write(a)
+ sink.write(bc)
+ sink.emit()
+ assertEquals(2, source.select(options).toLong())
+ assertEquals(0, source.select(options).toLong())
+ assertEquals(1, source.select(options).toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun selectNotFound() {
+ val options = of(
+ "ROCK".encodeUtf8(),
+ "SCISSORS".encodeUtf8(),
+ "PAPER".encodeUtf8(),
+ )
+ sink.writeUtf8("SPOCK")
+ sink.emit()
+ assertEquals(-1, source.select(options).toLong())
+ assertEquals("SPOCK", source.readUtf8())
+ }
+
+ @Test
+ fun selectValuesHaveCommonPrefix() {
+ val options = of(
+ "abcd".encodeUtf8(),
+ "abce".encodeUtf8(),
+ "abcc".encodeUtf8(),
+ )
+ sink.writeUtf8("abcc").writeUtf8("abcd").writeUtf8("abce")
+ sink.emit()
+ assertEquals(2, source.select(options).toLong())
+ assertEquals(0, source.select(options).toLong())
+ assertEquals(1, source.select(options).toLong())
+ }
+
+ @Test
+ fun selectLongerThanSource() {
+ val options = of(
+ "abcd".encodeUtf8(),
+ "abce".encodeUtf8(),
+ "abcc".encodeUtf8(),
+ )
+ sink.writeUtf8("abc")
+ sink.emit()
+ assertEquals(-1, source.select(options).toLong())
+ assertEquals("abc", source.readUtf8())
+ }
+
+ @Test
+ fun selectReturnsFirstByteStringThatMatches() {
+ val options = of(
+ "abcd".encodeUtf8(),
+ "abc".encodeUtf8(),
+ "abcde".encodeUtf8(),
+ )
+ sink.writeUtf8("abcdef")
+ sink.emit()
+ assertEquals(0, source.select(options).toLong())
+ assertEquals("ef", source.readUtf8())
+ }
+
+ @Test
+ fun selectFromEmptySource() {
+ val options = of(
+ "abc".encodeUtf8(),
+ "def".encodeUtf8(),
+ )
+ assertEquals(-1, source.select(options).toLong())
+ }
+
+ @Test
+ fun selectNoByteStringsFromEmptySource() {
+ val options = Options.of()
+ assertEquals(-1, source.select(options).toLong())
+ }
+
+ @Test
+ fun peek() {
+ sink.writeUtf8("abcdefghi")
+ sink.emit()
+ assertEquals("abc", source.readUtf8(3))
+ val peek = source.peek()
+ assertEquals("def", peek.readUtf8(3))
+ assertEquals("ghi", peek.readUtf8(3))
+ assertFalse(peek.request(1))
+ assertEquals("def", source.readUtf8(3))
+ }
+
+ @Test
+ fun peekMultiple() {
+ sink.writeUtf8("abcdefghi")
+ sink.emit()
+ assertEquals("abc", source.readUtf8(3))
+ val peek1 = source.peek()
+ val peek2 = source.peek()
+ assertEquals("def", peek1.readUtf8(3))
+ assertEquals("def", peek2.readUtf8(3))
+ assertEquals("ghi", peek2.readUtf8(3))
+ assertFalse(peek2.request(1))
+ assertEquals("ghi", peek1.readUtf8(3))
+ assertFalse(peek1.request(1))
+ assertEquals("def", source.readUtf8(3))
+ }
+
+ @Test
+ fun peekLarge() {
+ sink.writeUtf8("abcdef")
+ sink.writeUtf8("g".repeat(2 * SEGMENT_SIZE))
+ sink.writeUtf8("hij")
+ sink.emit()
+ assertEquals("abc", source.readUtf8(3))
+ val peek = source.peek()
+ assertEquals("def", peek.readUtf8(3))
+ peek.skip((2 * SEGMENT_SIZE).toLong())
+ assertEquals("hij", peek.readUtf8(3))
+ assertFalse(peek.request(1))
+ assertEquals("def", source.readUtf8(3))
+ source.skip((2 * SEGMENT_SIZE).toLong())
+ assertEquals("hij", source.readUtf8(3))
+ }
+
+ @Test
+ fun peekInvalid() {
+ sink.writeUtf8("abcdefghi")
+ sink.emit()
+ assertEquals("abc", source.readUtf8(3))
+ val peek = source.peek()
+ assertEquals("def", peek.readUtf8(3))
+ assertEquals("ghi", peek.readUtf8(3))
+ assertFalse(peek.request(1))
+ assertEquals("def", source.readUtf8(3))
+ try {
+ peek.readUtf8()
+ fail()
+ } catch (e: IllegalStateException) {
+ assertEquals("Peek source is invalid because upstream source was used", e.message)
+ }
+ }
+
+ @Test
+ fun peekSegmentThenInvalid() {
+ sink.writeUtf8("abc")
+ sink.writeUtf8("d".repeat(2 * SEGMENT_SIZE))
+ sink.emit()
+ assertEquals("abc", source.readUtf8(3))
+
+ // Peek a little data and skip the rest of the upstream source
+ val peek = source.peek()
+ assertEquals("ddd", peek.readUtf8(3))
+ source.readAll(blackholeSink())
+
+ // Skip the rest of the buffered data
+ peek.skip(peek.buffer.size)
+ try {
+ peek.readByte()
+ fail()
+ } catch (e: IllegalStateException) {
+ assertEquals("Peek source is invalid because upstream source was used", e.message)
+ }
+ }
+
+ @Test
+ fun peekDoesntReadTooMuch() {
+ // 6 bytes in source's buffer plus 3 bytes upstream.
+ sink.writeUtf8("abcdef")
+ sink.emit()
+ source.require(6L)
+ sink.writeUtf8("ghi")
+ sink.emit()
+ val peek = source.peek()
+
+ // Read 3 bytes. This reads some of the buffered data.
+ assertTrue(peek.request(3))
+ if (source !is Buffer) {
+ assertEquals(6, source.buffer.size)
+ assertEquals(6, peek.buffer.size)
+ }
+ assertEquals("abc", peek.readUtf8(3L))
+
+ // Read 3 more bytes. This exhausts the buffered data.
+ assertTrue(peek.request(3))
+ if (source !is Buffer) {
+ assertEquals(6, source.buffer.size)
+ assertEquals(3, peek.buffer.size)
+ }
+ assertEquals("def", peek.readUtf8(3L))
+
+ // Read 3 more bytes. This draws new bytes.
+ assertTrue(peek.request(3))
+ assertEquals(9, source.buffer.size)
+ assertEquals(3, peek.buffer.size)
+ assertEquals("ghi", peek.readUtf8(3L))
+ }
+
+ @Test
+ fun rangeEquals() {
+ sink.writeUtf8("A man, a plan, a canal. Panama.")
+ sink.emit()
+ assertTrue(source.rangeEquals(7, "a plan".encodeUtf8()))
+ assertTrue(source.rangeEquals(0, "A man".encodeUtf8()))
+ assertTrue(source.rangeEquals(24, "Panama".encodeUtf8()))
+ assertFalse(source.rangeEquals(24, "Panama. Panama. Panama.".encodeUtf8()))
+ }
+
+ @Test
+ fun rangeEqualsWithOffsetAndCount() {
+ sink.writeUtf8("A man, a plan, a canal. Panama.")
+ sink.emit()
+ assertTrue(source.rangeEquals(7, "aaa plannn".encodeUtf8(), 2, 6))
+ assertTrue(source.rangeEquals(0, "AAA mannn".encodeUtf8(), 2, 5))
+ assertTrue(source.rangeEquals(24, "PPPanamaaa".encodeUtf8(), 2, 6))
+ }
+
+ @Test
+ fun rangeEqualsOnlyReadsUntilMismatch() {
+ Assume.assumeTrue(factory === Factory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE) // Other sources read in chunks anyway.
+ sink.writeUtf8("A man, a plan, a canal. Panama.")
+ sink.emit()
+ assertFalse(source.rangeEquals(0, "A man.".encodeUtf8()))
+ assertEquals("A man,", source.buffer.readUtf8())
+ }
+
+ @Test
+ fun rangeEqualsArgumentValidation() {
+ // Negative source offset.
+ assertFalse(source.rangeEquals(-1, "A".encodeUtf8()))
+ // Negative bytes offset.
+ assertFalse(source.rangeEquals(0, "A".encodeUtf8(), -1, 1))
+ // Bytes offset longer than bytes length.
+ assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 2, 1))
+ // Negative byte count.
+ assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 0, -1))
+ // Byte count longer than bytes length.
+ assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 0, 2))
+ // Bytes offset plus byte count longer than bytes length.
+ assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 1, 1))
+ }
+
+ @Test
+ fun readNioBuffer() {
+ val expected = if (factory.isOneByteAtATime) "a" else "abcdefg"
+ sink.writeUtf8("abcdefg")
+ sink.emit()
+ val nioByteBuffer = ByteBuffer.allocate(1024)
+ val byteCount = source.read(nioByteBuffer)
+ assertEquals(expected.length.toLong(), byteCount.toLong())
+ assertEquals(expected.length.toLong(), nioByteBuffer.position().toLong())
+ assertEquals(nioByteBuffer.capacity().toLong(), nioByteBuffer.limit().toLong())
+ (nioByteBuffer as java.nio.Buffer).flip() // Cast necessary for Java 8.
+ val data = ByteArray(expected.length)
+ nioByteBuffer[data]
+ assertEquals(expected, data.decodeToString())
+ }
+
+ /** Note that this test crashes the VM on Android. */
+ @Test
+ fun readLargeNioBufferOnlyReadsOneSegment() {
+ val expected = if (factory.isOneByteAtATime) "a" else "a".repeat(SEGMENT_SIZE)
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE * 4))
+ sink.emit()
+ val nioByteBuffer = ByteBuffer.allocate(SEGMENT_SIZE * 3)
+ val byteCount = source.read(nioByteBuffer)
+ assertEquals(expected.length.toLong(), byteCount.toLong())
+ assertEquals(expected.length.toLong(), nioByteBuffer.position().toLong())
+ assertEquals(nioByteBuffer.capacity().toLong(), nioByteBuffer.limit().toLong())
+ (nioByteBuffer as java.nio.Buffer).flip() // Cast necessary for Java 8.
+ val data = ByteArray(expected.length)
+ nioByteBuffer[data]
+ assertEquals(expected, data.decodeToString())
+ }
+
+ @Test
+ fun factorySegmentSizes() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ source.require(3)
+ if (factory.isOneByteAtATime) {
+ assertEquals(mutableListOf(1, 1, 1), segmentSizes(source.buffer))
+ } else {
+ assertEquals(listOf(3), segmentSizes(source.buffer))
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun parameters(): List<Array<Any>> {
+ return listOf(
+ arrayOf(Factory.BUFFER),
+ arrayOf(Factory.REAL_BUFFERED_SOURCE),
+ arrayOf(Factory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE),
+ arrayOf(Factory.ONE_BYTE_AT_A_TIME_BUFFER),
+ arrayOf(Factory.PEEK_BUFFER),
+ arrayOf(Factory.PEEK_BUFFERED_SOURCE),
+ )
+ }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/ByteStringJavaTest.kt b/okio/src/jvmTest/kotlin/okio/ByteStringJavaTest.kt
new file mode 100644
index 00000000..490eb425
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/ByteStringJavaTest.kt
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.nio.ByteBuffer
+import java.util.Random
+import kotlin.text.Charsets.US_ASCII
+import kotlin.text.Charsets.UTF_16BE
+import kotlin.text.Charsets.UTF_32BE
+import kotlin.text.Charsets.UTF_8
+import okio.ByteString.Companion.decodeHex
+import okio.ByteString.Companion.encode
+import okio.ByteString.Companion.encodeUtf8
+import okio.ByteString.Companion.readByteString
+import okio.ByteString.Companion.toByteString
+import okio.TestUtil.assertByteArraysEquals
+import okio.TestUtil.assertEquivalent
+import okio.TestUtil.makeSegments
+import okio.TestUtil.reserialize
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class ByteStringJavaTest {
+ interface Factory {
+ fun decodeHex(hex: String): ByteString
+ fun encodeUtf8(s: String): ByteString
+
+ companion object {
+ val BYTE_STRING: Factory = object : Factory {
+ override fun decodeHex(hex: String): ByteString {
+ return hex.decodeHex()
+ }
+
+ override fun encodeUtf8(s: String): ByteString {
+ return s.encodeUtf8()
+ }
+ }
+ val SEGMENTED_BYTE_STRING: Factory = object : Factory {
+ override fun decodeHex(hex: String): ByteString {
+ val buffer = Buffer()
+ buffer.write(hex.decodeHex())
+ return buffer.snapshot()
+ }
+
+ override fun encodeUtf8(s: String): ByteString {
+ val buffer = Buffer()
+ buffer.writeUtf8(s)
+ return buffer.snapshot()
+ }
+ }
+ val ONE_BYTE_PER_SEGMENT: Factory = object : Factory {
+ override fun decodeHex(hex: String): ByteString {
+ return makeSegments(hex.decodeHex())
+ }
+
+ override fun encodeUtf8(s: String): ByteString {
+ return makeSegments(s.encodeUtf8())
+ }
+ }
+ }
+ }
+
+ @Parameter(0)
+ lateinit var factory: Factory
+
+ @Parameter(1)
+ lateinit var name: String
+
+ @Test
+ fun ofByteBuffer() {
+ val bytes = "Hello, World!".toByteArray(UTF_8)
+ val byteBuffer = ByteBuffer.wrap(bytes)
+ (byteBuffer as java.nio.Buffer).position(2).limit(11) // Cast necessary for Java 8.
+ val byteString: ByteString = byteBuffer.toByteString()
+ // Verify that the bytes were copied out.
+ byteBuffer.put(4, 'a'.code.toByte())
+ assertEquals("llo, Worl", byteString.utf8())
+ }
+
+ @Test
+ fun read() {
+ val inputStream = ByteArrayInputStream("abc".toByteArray(UTF_8))
+ assertEquals("6162".decodeHex(), inputStream.readByteString(2))
+ assertEquals("63".decodeHex(), inputStream.readByteString(1))
+ assertEquals(ByteString.of(), inputStream.readByteString(0))
+ }
+
+ @Test
+ fun readAndToLowercase() {
+ val inputStream = ByteArrayInputStream("ABC".toByteArray(UTF_8))
+ assertEquals("ab".encodeUtf8(), inputStream.readByteString(2).toAsciiLowercase())
+ assertEquals("c".encodeUtf8(), inputStream.readByteString(1).toAsciiLowercase())
+ assertEquals(ByteString.EMPTY, inputStream.readByteString(0).toAsciiLowercase())
+ }
+
+ @Test
+ fun readAndToUppercase() {
+ val inputStream = ByteArrayInputStream("abc".toByteArray(UTF_8))
+ assertEquals("AB".encodeUtf8(), inputStream.readByteString(2).toAsciiUppercase())
+ assertEquals("C".encodeUtf8(), inputStream.readByteString(1).toAsciiUppercase())
+ assertEquals(ByteString.EMPTY, inputStream.readByteString(0).toAsciiUppercase())
+ }
+
+ @Test
+ fun write() {
+ val out = ByteArrayOutputStream()
+ factory.decodeHex("616263").write(out)
+ assertByteArraysEquals(byteArrayOf(0x61, 0x62, 0x63), out.toByteArray())
+ }
+
+ @Test
+ fun compareToSingleBytes() {
+ val originalByteStrings = listOf(
+ factory.decodeHex("00"),
+ factory.decodeHex("01"),
+ factory.decodeHex("7e"),
+ factory.decodeHex("7f"),
+ factory.decodeHex("80"),
+ factory.decodeHex("81"),
+ factory.decodeHex("fe"),
+ factory.decodeHex("ff"),
+ )
+ val sortedByteStrings = originalByteStrings.toMutableList()
+ sortedByteStrings.shuffle(Random(0))
+ sortedByteStrings.sort()
+ assertEquals(originalByteStrings, sortedByteStrings)
+ }
+
+ @Test
+ fun compareToMultipleBytes() {
+ val originalByteStrings = listOf(
+ factory.decodeHex(""),
+ factory.decodeHex("00"),
+ factory.decodeHex("0000"),
+ factory.decodeHex("000000"),
+ factory.decodeHex("00000000"),
+ factory.decodeHex("0000000000"),
+ factory.decodeHex("0000000001"),
+ factory.decodeHex("000001"),
+ factory.decodeHex("00007f"),
+ factory.decodeHex("0000ff"),
+ factory.decodeHex("000100"),
+ factory.decodeHex("000101"),
+ factory.decodeHex("007f00"),
+ factory.decodeHex("00ff00"),
+ factory.decodeHex("010000"),
+ factory.decodeHex("010001"),
+ factory.decodeHex("01007f"),
+ factory.decodeHex("0100ff"),
+ factory.decodeHex("010100"),
+ factory.decodeHex("01010000"),
+ factory.decodeHex("0101000000"),
+ factory.decodeHex("0101000001"),
+ factory.decodeHex("010101"),
+ factory.decodeHex("7f0000"),
+ factory.decodeHex("7f0000ffff"),
+ factory.decodeHex("ffffff"),
+ )
+ val sortedByteStrings = originalByteStrings.toMutableList()
+ sortedByteStrings.shuffle(Random(0))
+ sortedByteStrings.sort()
+ assertEquals(originalByteStrings, sortedByteStrings)
+ }
+
+ @Test
+ fun javaSerializationTestNonEmpty() {
+ val byteString = factory.encodeUtf8(bronzeHorseman)
+ assertEquivalent(byteString, reserialize(byteString))
+ }
+
+ @Test
+ fun javaSerializationTestEmpty() {
+ val byteString = factory.decodeHex("")
+ assertEquivalent(byteString, reserialize(byteString))
+ }
+
+ @Test
+ fun asByteBuffer() {
+ assertEquals(
+ 0x42,
+ ByteString.of(0x41.toByte(), 0x42.toByte(), 0x43.toByte()).asByteBuffer()[1].toLong(),
+ )
+ }
+
+ @Test
+ fun encodeDecodeStringUtf8() {
+ val byteString = bronzeHorseman.encode(UTF_8)
+ assertByteArraysEquals(byteString.toByteArray(), bronzeHorseman.toByteArray(UTF_8))
+ assertEquals(
+ byteString,
+ (
+ "d09dd0b020d0b1d0b5d180d0b5d0b3d18320d0bfd183d181" +
+ "d182d18bd0bdd0bdd18bd18520d0b2d0bed0bbd0bd"
+ ).decodeHex(),
+ )
+ assertEquals(bronzeHorseman, byteString.string(UTF_8))
+ }
+
+ @Test
+ fun encodeDecodeStringUtf16be() {
+ val byteString = bronzeHorseman.encode(UTF_16BE)
+ assertByteArraysEquals(byteString.toByteArray(), bronzeHorseman.toByteArray(UTF_16BE))
+ assertEquals(
+ byteString,
+ (
+ "041d043000200431043504400435043304430020043f0443" +
+ "04410442044b043d043d044b044500200432043e043b043d"
+ ).decodeHex(),
+ )
+ assertEquals(bronzeHorseman, byteString.string(UTF_16BE))
+ }
+
+ @Test
+ fun encodeDecodeStringUtf32be() {
+ val byteString: ByteString = bronzeHorseman.encode(UTF_32BE)
+ assertByteArraysEquals(byteString.toByteArray(), bronzeHorseman.toByteArray(UTF_32BE))
+ assertEquals(
+ byteString,
+ (
+ "0000041d0000043000000020000004310000043500000440" +
+ "000004350000043300000443000000200000043f0000044300000441000004420000044b0000043d0000043d" +
+ "0000044b0000044500000020000004320000043e0000043b0000043d"
+ ).decodeHex(),
+ )
+ assertEquals(bronzeHorseman, byteString.string(UTF_32BE))
+ }
+
+ @Test
+ fun encodeDecodeStringAsciiIsLossy() {
+ val byteString: ByteString = bronzeHorseman.encode(US_ASCII)
+ assertByteArraysEquals(byteString.toByteArray(), bronzeHorseman.toByteArray(US_ASCII))
+ assertEquals(
+ byteString,
+ "3f3f203f3f3f3f3f3f203f3f3f3f3f3f3f3f3f203f3f3f3f".decodeHex(),
+ )
+ assertEquals("?? ?????? ????????? ????", byteString.string(US_ASCII))
+ }
+
+ @Test
+ fun decodeMalformedStringReturnsReplacementCharacter() {
+ val string = "04".decodeHex().string(UTF_16BE)
+ assertEquals("\ufffd", string)
+ }
+
+ companion object {
+ private val bronzeHorseman = "На берегу пустынных волн"
+
+ @JvmStatic
+ @Parameters(name = "{1}")
+ fun parameters(): List<Array<Any>> {
+ return listOf(
+ arrayOf(Factory.BYTE_STRING, "ByteString"),
+ arrayOf(Factory.SEGMENTED_BYTE_STRING, "SegmentedByteString"),
+ arrayOf(Factory.ONE_BYTE_PER_SEGMENT, "SegmentedByteString (one-at-a-time)"),
+ )
+ }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/ByteStringKotlinTest.kt b/okio/src/jvmTest/kotlin/okio/ByteStringKotlinTest.kt
index c554251f..ba91338d 100644
--- a/okio/src/jvmTest/kotlin/okio/ByteStringKotlinTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/ByteStringKotlinTest.kt
@@ -15,14 +15,14 @@
*/
package okio
-import okio.ByteString.Companion.encode
-import okio.ByteString.Companion.encodeUtf8
-import okio.ByteString.Companion.readByteString
-import okio.ByteString.Companion.toByteString
import java.io.ByteArrayInputStream
import java.nio.ByteBuffer
import kotlin.test.Test
import kotlin.test.assertEquals
+import okio.ByteString.Companion.encode
+import okio.ByteString.Companion.encodeUtf8
+import okio.ByteString.Companion.readByteString
+import okio.ByteString.Companion.toByteString
class ByteStringKotlinTest {
@Test fun arrayToByteString() {
diff --git a/okio/src/jvmTest/kotlin/okio/CipherAlgorithm.kt b/okio/src/jvmTest/kotlin/okio/CipherAlgorithm.kt
index f9f42b03..69415241 100644
--- a/okio/src/jvmTest/kotlin/okio/CipherAlgorithm.kt
+++ b/okio/src/jvmTest/kotlin/okio/CipherAlgorithm.kt
@@ -23,7 +23,7 @@ data class CipherAlgorithm(
val transformation: String,
val padding: Boolean,
val keyLength: Int,
- val ivLength: Int? = null
+ val ivLength: Int? = null,
) {
fun createCipherFactory(random: Random): CipherFactory {
val key = random.nextBytes(keyLength)
@@ -57,7 +57,7 @@ data class CipherAlgorithm(
CipherAlgorithm("DESede/CBC/NoPadding", false, 24, 8),
CipherAlgorithm("DESede/CBC/PKCS5Padding", true, 24, 8),
CipherAlgorithm("DESede/ECB/NoPadding", false, 24),
- CipherAlgorithm("DESede/ECB/PKCS5Padding", true, 24)
+ CipherAlgorithm("DESede/ECB/PKCS5Padding", true, 24),
)
}
}
diff --git a/okio/src/jvmTest/kotlin/okio/CipherFactory.kt b/okio/src/jvmTest/kotlin/okio/CipherFactory.kt
index 7b93c652..bb4d3cf3 100644
--- a/okio/src/jvmTest/kotlin/okio/CipherFactory.kt
+++ b/okio/src/jvmTest/kotlin/okio/CipherFactory.kt
@@ -19,7 +19,7 @@ import javax.crypto.Cipher
class CipherFactory(
private val transformation: String,
- private val init: Cipher.(mode: Int) -> Unit
+ private val init: Cipher.(mode: Int) -> Unit,
) {
val blockSize
get() = newCipher().blockSize
diff --git a/okio/src/jvmTest/kotlin/okio/CipherSinkTest.kt b/okio/src/jvmTest/kotlin/okio/CipherSinkTest.kt
index f273971d..84d27d06 100644
--- a/okio/src/jvmTest/kotlin/okio/CipherSinkTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/CipherSinkTest.kt
@@ -15,10 +15,10 @@
*/
package okio
+import kotlin.random.Random
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import kotlin.random.Random
@RunWith(Parameterized::class)
class CipherSinkTest(private val cipherAlgorithm: CipherAlgorithm) {
@@ -84,7 +84,7 @@ class CipherSinkTest(private val cipherAlgorithm: CipherAlgorithm) {
val cipherSink = buffer.cipherSink(cipherFactory.encrypt)
cipherSink.buffer().use {
data.forEach {
- byte ->
+ byte ->
it.writeByte(byte.toInt())
}
}
diff --git a/okio/src/jvmTest/kotlin/okio/CipherSourceTest.kt b/okio/src/jvmTest/kotlin/okio/CipherSourceTest.kt
index f97258e2..16775350 100644
--- a/okio/src/jvmTest/kotlin/okio/CipherSourceTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/CipherSourceTest.kt
@@ -15,10 +15,10 @@
*/
package okio
+import kotlin.random.Random
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import kotlin.random.Random
@RunWith(Parameterized::class)
class CipherSourceTest(private val cipherAlgorithm: CipherAlgorithm) {
diff --git a/okio/src/jvmTest/kotlin/okio/DeflateKotlinTest.kt b/okio/src/jvmTest/kotlin/okio/DeflateKotlinTest.kt
index 7a1744ad..5174871b 100644
--- a/okio/src/jvmTest/kotlin/okio/DeflateKotlinTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/DeflateKotlinTest.kt
@@ -16,11 +16,11 @@
package okio
-import okio.ByteString.Companion.decodeHex
-import org.junit.Test
import java.util.zip.Deflater
import java.util.zip.Inflater
import kotlin.test.assertEquals
+import okio.ByteString.Companion.decodeHex
+import org.junit.Test
class DeflateKotlinTest {
@Test fun deflate() {
diff --git a/okio/src/jvmTest/kotlin/okio/DeflaterSinkTest.kt b/okio/src/jvmTest/kotlin/okio/DeflaterSinkTest.kt
new file mode 100644
index 00000000..57a8586d
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/DeflaterSinkTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.util.zip.Deflater
+import java.util.zip.Inflater
+import java.util.zip.InflaterInputStream
+import okio.TestUtil.SEGMENT_SIZE
+import okio.TestUtil.randomBytes
+import org.junit.Assert
+import org.junit.Test
+
+class DeflaterSinkTest {
+ @Test
+ fun deflateWithClose() {
+ val data = Buffer()
+ val original = "They're moving in herds. They do move in herds."
+ data.writeUtf8(original)
+ val sink = Buffer()
+ val deflaterSink = DeflaterSink(sink, Deflater())
+ deflaterSink.write(data, data.size)
+ deflaterSink.close()
+ val inflated = inflate(sink)
+ Assert.assertEquals(original, inflated.readUtf8())
+ }
+
+ @Test
+ fun deflateWithSyncFlush() {
+ val original = "Yes, yes, yes. That's why we're taking extreme precautions."
+ val data = Buffer()
+ data.writeUtf8(original)
+ val sink = Buffer()
+ val deflaterSink = DeflaterSink(sink, Deflater())
+ deflaterSink.write(data, data.size)
+ deflaterSink.flush()
+ val inflated = inflate(sink)
+ Assert.assertEquals(original, inflated.readUtf8())
+ }
+
+ @Test
+ fun deflateWellCompressed() {
+ val original = "a".repeat(1024 * 1024)
+ val data = Buffer()
+ data.writeUtf8(original)
+ val sink = Buffer()
+ val deflaterSink = DeflaterSink(sink, Deflater())
+ deflaterSink.write(data, data.size)
+ deflaterSink.close()
+ val inflated = inflate(sink)
+ Assert.assertEquals(original, inflated.readUtf8())
+ }
+
+ @Test
+ fun deflatePoorlyCompressed() {
+ val original = randomBytes(1024 * 1024)
+ val data = Buffer()
+ data.write(original)
+ val sink = Buffer()
+ val deflaterSink = DeflaterSink(sink, Deflater())
+ deflaterSink.write(data, data.size)
+ deflaterSink.close()
+ val inflated = inflate(sink)
+ Assert.assertEquals(original, inflated.readByteString())
+ }
+
+ @Test
+ fun multipleSegmentsWithoutCompression() {
+ val buffer = Buffer()
+ val deflater = Deflater()
+ deflater.setLevel(Deflater.NO_COMPRESSION)
+ val deflaterSink = DeflaterSink(buffer, deflater)
+ val byteCount = SEGMENT_SIZE * 4
+ deflaterSink.write(Buffer().writeUtf8("a".repeat(byteCount)), byteCount.toLong())
+ deflaterSink.close()
+ Assert.assertEquals("a".repeat(byteCount), inflate(buffer).readUtf8(byteCount.toLong()))
+ }
+
+ @Test
+ fun deflateIntoNonemptySink() {
+ val original = "They're moving in herds. They do move in herds."
+
+ // Exercise all possible offsets for the outgoing segment.
+ for (i in 0 until SEGMENT_SIZE) {
+ val data = Buffer().writeUtf8(original)
+ val sink = Buffer().writeUtf8("a".repeat(i))
+ val deflaterSink = DeflaterSink(sink, Deflater())
+ deflaterSink.write(data, data.size)
+ deflaterSink.close()
+ sink.skip(i.toLong())
+ val inflated = inflate(sink)
+ Assert.assertEquals(original, inflated.readUtf8())
+ }
+ }
+
+ /**
+ * This test deflates a single segment of without compression because that's
+ * the easiest way to force close() to emit a large amount of data to the
+ * underlying sink.
+ */
+ @Test
+ fun closeWithExceptionWhenWritingAndClosing() {
+ val mockSink = MockSink()
+ mockSink.scheduleThrow(0, IOException("first"))
+ mockSink.scheduleThrow(1, IOException("second"))
+ val deflater = Deflater()
+ deflater.setLevel(Deflater.NO_COMPRESSION)
+ val deflaterSink = DeflaterSink(mockSink, deflater)
+ deflaterSink.write(Buffer().writeUtf8("a".repeat(SEGMENT_SIZE)), SEGMENT_SIZE.toLong())
+ try {
+ deflaterSink.close()
+ Assert.fail()
+ } catch (expected: IOException) {
+ Assert.assertEquals("first", expected.message)
+ }
+ mockSink.assertLogContains("close()")
+ }
+
+ /**
+ * This test confirms that we swallow NullPointerException from Deflater and
+ * rethrow as an IOException.
+ */
+ @Test
+ fun rethrowNullPointerAsIOException() {
+ val deflater = Deflater()
+ // Close to cause a NullPointerException
+ deflater.end()
+
+ val data = Buffer().apply {
+ writeUtf8("They're moving in herds. They do move in herds.")
+ }
+ val deflaterSink = DeflaterSink(Buffer(), deflater)
+
+ val ioe = Assert.assertThrows("", IOException::class.java) {
+ deflaterSink.write(data, data.size)
+ }
+
+ Assert.assertTrue(ioe.cause is NullPointerException)
+ }
+
+ /**
+ * Uses streaming decompression to inflate `deflated`. The input must
+ * either be finished or have a trailing sync flush.
+ */
+ private fun inflate(deflated: Buffer): Buffer {
+ val deflatedIn = deflated.inputStream()
+ val inflater = Inflater()
+ val inflatedIn = InflaterInputStream(deflatedIn, inflater)
+ val result = Buffer()
+ val buffer = ByteArray(8192)
+ while (!inflater.needsInput() || deflated.size > 0 || deflatedIn.available() > 0) {
+ val count = inflatedIn.read(buffer, 0, buffer.size)
+ if (count != -1) {
+ result.write(buffer, 0, count)
+ }
+ }
+ return result
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/FileHandleFileSystemTest.kt b/okio/src/jvmTest/kotlin/okio/FileHandleFileSystemTest.kt
new file mode 100644
index 00000000..021ffb4c
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/FileHandleFileSystemTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import com.google.common.jimfs.Configuration
+import com.google.common.jimfs.Jimfs
+import java.nio.file.FileSystems
+import kotlinx.datetime.Clock
+import okio.FileHandleFileSystemTest.FileHandleTestingFileSystem
+import okio.FileSystem.Companion.asOkioFileSystem
+
+/**
+ * Run a regular file system test, but use [FileHandle] for more file system operations than usual.
+ * This is intended to increase test coverage for [FileHandle].
+ */
+class FileHandleFileSystemTest : AbstractFileSystemTest(
+ clock = Clock.System,
+ fileSystem = FileHandleTestingFileSystem(FileSystem.SYSTEM),
+ windowsLimitations = Path.DIRECTORY_SEPARATOR == "\\",
+ allowClobberingEmptyDirectories = Path.DIRECTORY_SEPARATOR == "\\",
+ allowAtomicMoveFromFileToDirectory = false,
+ temporaryDirectory = FileSystem.SYSTEM_TEMPORARY_DIRECTORY,
+) {
+ /**
+ * A testing-only file system that implements all reading and writing operations with
+ * [FileHandle]. This is intended to increase test coverage for [FileHandle].
+ */
+ class FileHandleTestingFileSystem(delegate: FileSystem) : ForwardingFileSystem(delegate) {
+ override fun source(file: Path): Source {
+ val fileHandle = openReadOnly(file)
+ return fileHandle.source()
+ .also { fileHandle.close() }
+ }
+
+ override fun sink(file: Path, mustCreate: Boolean): Sink {
+ val fileHandle = openReadWrite(file, mustCreate = mustCreate, mustExist = false)
+ fileHandle.resize(0L) // If the file already has data, get rid of it.
+ return fileHandle.sink()
+ .also { fileHandle.close() }
+ }
+
+ override fun appendingSink(file: Path, mustExist: Boolean): Sink {
+ val fileHandle = openReadWrite(file, mustCreate = false, mustExist = mustExist)
+ return fileHandle.appendingSink()
+ .also { fileHandle.close() }
+ }
+ }
+}
+
+class FileHandleNioJimFileSystemWrapperFileSystemTest : AbstractFileSystemTest(
+ clock = Clock.System,
+ fileSystem = FileHandleTestingFileSystem(
+ Jimfs
+ .newFileSystem(
+ when (Path.DIRECTORY_SEPARATOR == "\\") {
+ true -> Configuration.windows()
+ false -> Configuration.unix()
+ },
+ ).asOkioFileSystem(),
+ ),
+ windowsLimitations = false,
+ allowClobberingEmptyDirectories = true,
+ allowAtomicMoveFromFileToDirectory = true,
+ temporaryDirectory = FileSystem.SYSTEM_TEMPORARY_DIRECTORY,
+)
+
+class FileHandleNioDefaultFileSystemWrapperFileSystemTest : AbstractFileSystemTest(
+ clock = Clock.System,
+ fileSystem = FileHandleTestingFileSystem(
+ FileSystems.getDefault().asOkioFileSystem(),
+ ),
+ windowsLimitations = false,
+ allowClobberingEmptyDirectories = Path.DIRECTORY_SEPARATOR == "\\",
+ allowAtomicMoveFromFileToDirectory = false,
+ allowRenameWhenTargetIsOpen = Path.DIRECTORY_SEPARATOR != "\\",
+ temporaryDirectory = FileSystem.SYSTEM_TEMPORARY_DIRECTORY,
+)
diff --git a/okio/src/jvmTest/kotlin/okio/FileLeakTest.kt b/okio/src/jvmTest/kotlin/okio/FileLeakTest.kt
new file mode 100644
index 00000000..5fd18a6b
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/FileLeakTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 Square, Inc. and others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import okio.Path.Companion.toPath
+import okio.fakefilesystem.FakeFileSystem
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class FileLeakTest {
+
+ private lateinit var fakeFileSystem: FakeFileSystem
+ private val fakeZip = "/test.zip".toPath()
+ private val fakeEntry = "some.file".toPath()
+ private val fakeDirectory = "/another/".toPath()
+ private val fakeEntry2 = fakeDirectory / "another.file"
+
+ @Before
+ fun setup() {
+ fakeFileSystem = FakeFileSystem()
+ with(fakeFileSystem) {
+ write(fakeZip) {
+ writeZip {
+ putEntry(fakeEntry.name) {
+ writeUtf8("FooBar")
+ }
+ try {
+ putNextEntry(ZipEntry(fakeDirectory.name).apply { time = 0L })
+ } finally {
+ closeEntry()
+ }
+ putEntry(fakeEntry2.toString()) {
+ writeUtf8("SomethingElse")
+ }
+ }
+ }
+ }
+ }
+
+ @After
+ fun tearDown() {
+ fakeFileSystem.checkNoOpenFiles()
+ }
+
+ @Test
+ fun zipFileSystemExistsTest() {
+ val zipFileSystem = fakeFileSystem.openZip(fakeZip)
+ assertTrue(zipFileSystem.exists(fakeEntry))
+ }
+
+ @Test
+ fun zipFileSystemMetadataTest() {
+ val zipFileSystem = fakeFileSystem.openZip(fakeZip)
+ assertNotNull(zipFileSystem.metadataOrNull(fakeEntry))
+ }
+
+ @Test
+ fun zipFileSystemSourceTest() {
+ val zipFileSystem = fakeFileSystem.openZip(fakeZip)
+ zipFileSystem.source(fakeEntry).use { source ->
+ assertEquals("FooBar", source.buffer().readUtf8())
+ }
+ }
+
+ @Test
+ fun zipFileSystemListRecursiveTest() {
+ val zipFileSystem = fakeFileSystem.openZip(fakeZip)
+ zipFileSystem.listRecursively("/".toPath()).toList()
+ fakeFileSystem.delete(fakeZip)
+ }
+}
+
+/**
+ * Writes a ZIP file to a [BufferedSink].
+ */
+private inline fun <R> BufferedSink.writeZip(action: ZipOutputStream.() -> R): R {
+ return ZipOutputStream(outputStream()).use(action)
+}
+
+/**
+ * Adds a new ZIP entry named [name], populates it with [action], and closes the entry.
+ */
+private inline fun <R> ZipOutputStream.putEntry(name: String, action: BufferedSink.() -> R): R {
+ putNextEntry(ZipEntry(name).apply { time = 0L })
+ val sink = sink().buffer()
+ return try {
+ sink.action()
+ } finally {
+ sink.flush()
+ closeEntry()
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/FileSystemJavaTest.kt b/okio/src/jvmTest/kotlin/okio/FileSystemJavaTest.kt
new file mode 100644
index 00000000..699db3ef
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/FileSystemJavaTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.File
+import java.nio.file.Paths
+import okio.ByteString.Companion.encodeUtf8
+import okio.Path.Companion.toOkioPath
+import okio.Path.Companion.toPath
+import okio.fakefilesystem.FakeFileSystem
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+class FileSystemJavaTest {
+ @Test
+ fun pathApi() {
+ val path = "/home/jesse/todo.txt".toPath(false)
+ assertThat("/home/jesse".toPath(false).div("todo.txt")).isEqualTo(path)
+ assertThat("/home/jesse/todo.txt".toPath(false)).isEqualTo(path)
+ assertThat(path.isAbsolute).isTrue()
+ assertThat(path.isRelative).isFalse()
+ assertThat(path.isRoot).isFalse()
+ assertThat(path.name).isEqualTo("todo.txt")
+ assertThat(path.nameBytes).isEqualTo("todo.txt".encodeUtf8())
+ assertThat(path.parent).isEqualTo("/home/jesse".toPath(false))
+ assertThat(path.volumeLetter).isNull()
+ }
+
+ @Test
+ fun directorySeparator() {
+ assertThat(Path.DIRECTORY_SEPARATOR).isIn("/", "\\")
+ }
+
+ /** Like the same test in JvmTest, but this is using the Java APIs. */
+ @Test
+ fun javaIoFileToOkioPath() {
+ val string = "/foo/bar/baz".replace("/", Path.DIRECTORY_SEPARATOR)
+ val javaIoFile = File(string)
+ val okioPath: Path = string.toPath(false)
+ assertThat(javaIoFile.toOkioPath(false)).isEqualTo(okioPath)
+ assertThat(okioPath.toFile()).isEqualTo(javaIoFile)
+ }
+
+ /** Like the same test in JvmTest, but this is using the Java APIs. */
+ @Test
+ fun nioPathToOkioPath() {
+ val string = "/foo/bar/baz".replace("/", Path.DIRECTORY_SEPARATOR)
+ val nioPath = Paths.get(string)
+ val okioPath: Path = string.toPath(false)
+ assertThat(nioPath.toOkioPath(false)).isEqualTo(okioPath)
+ assertThat(okioPath.toNioPath() as Any).isEqualTo(nioPath)
+ }
+
+ // Just confirm these APIs exist; don't invoke them
+ @Suppress("unused")
+ fun fileSystemApi() {
+ val fileSystem = FileSystem.SYSTEM
+ val pathA: Path = "a.txt".toPath()
+ val pathB: Path = "b.txt".toPath()
+ fileSystem.canonicalize(pathA)
+ fileSystem.metadata(pathA)
+ fileSystem.metadataOrNull(pathA)
+ fileSystem.exists(pathA)
+ fileSystem.list(pathA)
+ fileSystem.listOrNull(pathA)
+ fileSystem.listRecursively(pathA, false)
+ fileSystem.listRecursively(pathA)
+ fileSystem.openReadOnly(pathA)
+ fileSystem.openReadWrite(pathA, mustCreate = false, mustExist = false)
+ fileSystem.openReadWrite(pathA)
+ fileSystem.source(pathA)
+ // Note that FileSystem.read() isn't available to Java callers.
+ fileSystem.sink(pathA, false)
+ fileSystem.sink(pathA)
+ // Note that FileSystem.write() isn't available to Java callers.
+ fileSystem.appendingSink(pathA, false)
+ fileSystem.appendingSink(pathA)
+ fileSystem.createDirectory(pathA)
+ fileSystem.createDirectories(pathA)
+ fileSystem.atomicMove(pathA, pathB)
+ fileSystem.copy(pathA, pathB)
+ fileSystem.delete(pathA)
+ fileSystem.deleteRecursively(pathA)
+ fileSystem.createSymlink(pathA, pathB)
+ }
+
+ @Test
+ fun fakeFileSystemApi() {
+ val fakeFileSystem = FakeFileSystem()
+ assertThat(fakeFileSystem.clock).isNotNull()
+ assertThat(fakeFileSystem.allPaths).isEmpty()
+ assertThat(fakeFileSystem.openPaths).isEmpty()
+ fakeFileSystem.checkNoOpenFiles()
+ }
+
+ @Test
+ fun forwardingFileSystemApi() {
+ val fakeFileSystem = FakeFileSystem()
+ val log: MutableList<String> = ArrayList()
+ val forwardingFileSystem: ForwardingFileSystem = object : ForwardingFileSystem(fakeFileSystem) {
+ override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path {
+ log.add("$functionName($parameterName=$path)")
+ return path
+ }
+ }
+ forwardingFileSystem.metadataOrNull("/".toPath(false))
+ assertThat(log).containsExactly("metadataOrNull(path=/)")
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/FixedLengthSourceTest.kt b/okio/src/jvmTest/kotlin/okio/FixedLengthSourceTest.kt
new file mode 100644
index 00000000..79184177
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/FixedLengthSourceTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.test.fail
+import okio.internal.FixedLengthSource
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+internal class FixedLengthSourceTest {
+ @Test
+ fun happyPathWithTruncate() {
+ val delegate = Buffer().writeUtf8("abcdefghijklmnop")
+ val fixedLengthSource = FixedLengthSource(delegate, 16, truncate = true)
+ val buffer = Buffer()
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(10L)
+ assertThat(buffer.readUtf8()).isEqualTo("abcdefghij")
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(6L)
+ assertThat(buffer.readUtf8()).isEqualTo("klmnop")
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(-1L)
+ assertThat(buffer.readUtf8()).isEqualTo("")
+ }
+
+ @Test
+ fun happyPathNoTruncate() {
+ val delegate = Buffer().writeUtf8("abcdefghijklmnop")
+ val fixedLengthSource = FixedLengthSource(delegate, 16, truncate = false)
+ val buffer = Buffer()
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(10L)
+ assertThat(buffer.readUtf8()).isEqualTo("abcdefghij")
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(6L)
+ assertThat(buffer.readUtf8()).isEqualTo("klmnop")
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(-1L)
+ assertThat(buffer.readUtf8()).isEqualTo("")
+ }
+
+ @Test
+ fun delegateTooLongWithTruncate() {
+ val delegate = Buffer().writeUtf8("abcdefghijklmnopqr")
+ val fixedLengthSource = FixedLengthSource(delegate, 16, truncate = true)
+ val buffer = Buffer()
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(10L)
+ assertThat(buffer.readUtf8()).isEqualTo("abcdefghij")
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(6L)
+ assertThat(buffer.readUtf8()).isEqualTo("klmnop")
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(-1L)
+ assertThat(buffer.readUtf8()).isEqualTo("")
+ }
+
+ @Test
+ fun delegateTooLongWithTruncateFencepost() {
+ val delegate = Buffer().writeUtf8("abcdefghijklmnop")
+ val fixedLengthSource = FixedLengthSource(delegate, 10, truncate = true)
+ val buffer = Buffer()
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(10L)
+ assertThat(buffer.readUtf8()).isEqualTo("abcdefghij")
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(-1L)
+ assertThat(buffer.readUtf8()).isEmpty()
+ }
+
+ @Test
+ fun delegateTooLongNoTruncate() {
+ val delegate = Buffer().writeUtf8("abcdefghijklmnopqr")
+ val fixedLengthSource = FixedLengthSource(delegate, 16, truncate = false)
+ val buffer = Buffer()
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(10L)
+ assertThat(buffer.readUtf8()).isEqualTo("abcdefghij")
+ try {
+ fixedLengthSource.read(buffer, 10L)
+ fail()
+ } catch (e: IOException) {
+ assertThat(e).hasMessage("expected 16 bytes but got 18")
+ assertThat(buffer.readUtf8()).isEqualTo("klmnop") // Doesn't produce too many bytes!
+ }
+ try {
+ fixedLengthSource.read(buffer, 10L)
+ fail()
+ } catch (e: IOException) {
+ assertThat(e).hasMessage("expected 16 bytes but got 18")
+ assertThat(buffer.readUtf8()).isEmpty() // Doesn't produce any bytes!
+ }
+ }
+
+ @Test
+ fun delegateTooLongNoTruncateFencepost() {
+ val delegate = Buffer().writeUtf8("abcdefghijklmnop")
+ val fixedLengthSource = FixedLengthSource(delegate, 10, truncate = false)
+ val buffer = Buffer()
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(10L)
+ assertThat(buffer.readUtf8()).isEqualTo("abcdefghij")
+ try {
+ fixedLengthSource.read(buffer, 10L)
+ fail()
+ } catch (e: IOException) {
+ assertThat(e).hasMessage("expected 10 bytes but got 16")
+ assertThat(buffer.readUtf8()).isEmpty() // Doesn't produce too many bytes!
+ }
+ try {
+ fixedLengthSource.read(buffer, 10L)
+ fail()
+ } catch (e: IOException) {
+ assertThat(e).hasMessage("expected 10 bytes but got 16")
+ assertThat(buffer.readUtf8()).isEmpty() // Doesn't produce any bytes!
+ }
+ }
+
+ @Test
+ fun delegateTooShortWithTruncate() {
+ val delegate = Buffer().writeUtf8("abcdefghijklmn")
+ val fixedLengthSource = FixedLengthSource(delegate, 16, truncate = true)
+ val buffer = Buffer()
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(10L)
+ assertThat(buffer.readUtf8()).isEqualTo("abcdefghij")
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(4L)
+ assertThat(buffer.readUtf8()).isEqualTo("klmn")
+ try {
+ fixedLengthSource.read(buffer, 10L)
+ fail()
+ } catch (e: IOException) {
+ assertThat(e).hasMessage("expected 16 bytes but got 14")
+ }
+ try {
+ fixedLengthSource.read(buffer, 10L)
+ fail()
+ } catch (e: IOException) {
+ assertThat(e).hasMessage("expected 16 bytes but got 14")
+ }
+ }
+
+ @Test
+ fun delegateTooShortNoTruncate() {
+ val delegate = Buffer().writeUtf8("abcdefghijklmn")
+ val fixedLengthSource = FixedLengthSource(delegate, 16, truncate = false)
+ val buffer = Buffer()
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(10L)
+ assertThat(buffer.readUtf8()).isEqualTo("abcdefghij")
+ assertThat(fixedLengthSource.read(buffer, 10L)).isEqualTo(4L)
+ assertThat(buffer.readUtf8()).isEqualTo("klmn")
+ try {
+ fixedLengthSource.read(buffer, 10L)
+ fail()
+ } catch (e: IOException) {
+ assertThat(e).hasMessage("expected 16 bytes but got 14")
+ }
+ try {
+ fixedLengthSource.read(buffer, 10L)
+ fail()
+ } catch (e: IOException) {
+ assertThat(e).hasMessage("expected 16 bytes but got 14")
+ }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/ForwardingTimeoutKotlinTest.kt b/okio/src/jvmTest/kotlin/okio/ForwardingTimeoutKotlinTest.kt
index 4db02c50..f2c19c6e 100644
--- a/okio/src/jvmTest/kotlin/okio/ForwardingTimeoutKotlinTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/ForwardingTimeoutKotlinTest.kt
@@ -15,9 +15,9 @@
*/
package okio
+import java.util.concurrent.TimeUnit
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
-import java.util.concurrent.TimeUnit
class ForwardingTimeoutKotlinTest {
@Test fun getAndSetDelegate() {
diff --git a/okio/src/jvmTest/kotlin/okio/ForwardingTimeoutTest.kt b/okio/src/jvmTest/kotlin/okio/ForwardingTimeoutTest.kt
new file mode 100644
index 00000000..637cb976
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/ForwardingTimeoutTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.util.concurrent.TimeUnit
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+class ForwardingTimeoutTest {
+ @Test
+ fun getAndSetDelegate() {
+ val timeout1 = Timeout()
+ val timeout2 = Timeout()
+ val forwardingTimeout = ForwardingTimeout(timeout1)
+ forwardingTimeout.timeout(5, TimeUnit.SECONDS)
+ assertThat(timeout1.timeoutNanos()).isNotEqualTo(0L)
+ assertThat(timeout2.timeoutNanos()).isEqualTo(0L)
+ forwardingTimeout.clearTimeout()
+ assertThat(timeout1.timeoutNanos()).isEqualTo(0L)
+ assertThat(timeout2.timeoutNanos()).isEqualTo(0L)
+ assertThat(forwardingTimeout.delegate).isEqualTo(timeout1)
+ assertThat(forwardingTimeout.setDelegate(timeout2)).isEqualTo(forwardingTimeout)
+ forwardingTimeout.timeout(5, TimeUnit.SECONDS)
+ assertThat(timeout1.timeoutNanos()).isEqualTo(0L)
+ assertThat(timeout2.timeoutNanos()).isNotEqualTo(0L)
+ forwardingTimeout.clearTimeout()
+ assertThat(timeout1.timeoutNanos()).isEqualTo(0L)
+ assertThat(timeout2.timeoutNanos()).isEqualTo(0L)
+ assertThat(forwardingTimeout.delegate).isEqualTo(timeout2)
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/GzipKotlinTest.kt b/okio/src/jvmTest/kotlin/okio/GzipKotlinTest.kt
index 6bbe9b51..dfe7182a 100644
--- a/okio/src/jvmTest/kotlin/okio/GzipKotlinTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/GzipKotlinTest.kt
@@ -16,21 +16,35 @@
package okio
+import kotlin.test.assertEquals
import okio.ByteString.Companion.decodeHex
import org.junit.Test
-import kotlin.test.assertEquals
class GzipKotlinTest {
@Test fun sink() {
val data = Buffer()
- val gzip = (data as Sink).gzip()
- gzip.buffer().writeUtf8("Hi!").close()
+ (data as Sink).gzip().buffer().use { gzip ->
+ gzip.writeUtf8("Hi!")
+ }
assertEquals("1f8b0800000000000000f3c8540400dac59e7903000000", data.readByteString().hex())
}
@Test fun source() {
val buffer = Buffer().write("1f8b0800000000000000f3c8540400dac59e7903000000".decodeHex())
- val gzip = (buffer as Source).gzip()
- assertEquals("Hi!", gzip.buffer().readUtf8())
+ (buffer as Source).gzip().buffer().use { gzip ->
+ assertEquals("Hi!", gzip.readUtf8())
+ }
+ }
+
+ @Test fun extraLongXlen() {
+ val xlen = 0xffff
+ val buffer = Buffer()
+ .write("1f8b0804000000000000".decodeHex())
+ .writeShort(xlen)
+ .write(ByteArray(xlen))
+ .write("f3c8540400dac59e7903000000".decodeHex())
+ (buffer as Source).gzip().buffer().use { gzip ->
+ assertEquals("Hi!", gzip.readUtf8())
+ }
}
}
diff --git a/okio/src/jvmTest/kotlin/okio/GzipSinkTest.kt b/okio/src/jvmTest/kotlin/okio/GzipSinkTest.kt
new file mode 100644
index 00000000..b3fe171a
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/GzipSinkTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.IOException
+import okio.TestUtil.SEGMENT_SIZE
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+
+class GzipSinkTest {
+ @Test
+ fun gzipGunzip() {
+ val data = Buffer()
+ val original = "It's a UNIX system! I know this!"
+ data.writeUtf8(original)
+ val sink = Buffer()
+ val gzipSink = GzipSink(sink)
+ gzipSink.write(data, data.size)
+ gzipSink.close()
+ val inflated = gunzip(sink)
+ assertEquals(original, inflated.readUtf8())
+ }
+
+ @Test
+ fun closeWithExceptionWhenWritingAndClosing() {
+ val mockSink = MockSink()
+ mockSink.scheduleThrow(0, IOException("first"))
+ mockSink.scheduleThrow(1, IOException("second"))
+ val gzipSink = GzipSink(mockSink)
+ gzipSink.write(Buffer().writeUtf8("a".repeat(SEGMENT_SIZE)), SEGMENT_SIZE.toLong())
+ try {
+ gzipSink.close()
+ fail()
+ } catch (expected: IOException) {
+ assertEquals("first", expected.message)
+ }
+ mockSink.assertLogContains("close()")
+ }
+
+ private fun gunzip(gzipped: Buffer): Buffer {
+ val result = Buffer()
+ val source = GzipSource(gzipped)
+ while (source.read(result, Int.MAX_VALUE.toLong()) != -1L) {
+ }
+ return result
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/GzipSourceTest.kt b/okio/src/jvmTest/kotlin/okio/GzipSourceTest.kt
new file mode 100644
index 00000000..812aab14
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/GzipSourceTest.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.IOException
+import java.util.zip.CRC32
+import kotlin.text.Charsets.UTF_8
+import okio.ByteString.Companion.decodeHex
+import okio.ByteString.Companion.of
+import okio.TestUtil.reverseBytes
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+
+class GzipSourceTest {
+ @Test
+ fun gunzip() {
+ val gzipped = Buffer()
+ gzipped.write(gzipHeader)
+ gzipped.write(deflated)
+ gzipped.write(gzipTrailer)
+ assertGzipped(gzipped)
+ }
+
+ @Test
+ fun gunzip_withHCRC() {
+ val hcrc = CRC32()
+ val gzipHeader = gzipHeaderWithFlags(0x02.toByte())
+ hcrc.update(gzipHeader.toByteArray())
+ val gzipped = Buffer()
+ gzipped.write(gzipHeader)
+ gzipped.writeShort(hcrc.value.toShort().reverseBytes().toInt()) // little endian
+ gzipped.write(deflated)
+ gzipped.write(gzipTrailer)
+ assertGzipped(gzipped)
+ }
+
+ @Test
+ fun gunzip_withExtra() {
+ val gzipped = Buffer()
+ gzipped.write(gzipHeaderWithFlags(0x04.toByte()))
+ gzipped.writeShort(7.toShort().reverseBytes().toInt()) // little endian extra length
+ gzipped.write("blubber".toByteArray(UTF_8), 0, 7)
+ gzipped.write(deflated)
+ gzipped.write(gzipTrailer)
+ assertGzipped(gzipped)
+ }
+
+ @Test
+ fun gunzip_withName() {
+ val gzipped = Buffer()
+ gzipped.write(gzipHeaderWithFlags(0x08.toByte()))
+ gzipped.write("foo.txt".toByteArray(UTF_8), 0, 7)
+ gzipped.writeByte(0) // zero-terminated
+ gzipped.write(deflated)
+ gzipped.write(gzipTrailer)
+ assertGzipped(gzipped)
+ }
+
+ @Test
+ fun gunzip_withComment() {
+ val gzipped = Buffer()
+ gzipped.write(gzipHeaderWithFlags(0x10.toByte()))
+ gzipped.write("rubbish".toByteArray(UTF_8), 0, 7)
+ gzipped.writeByte(0) // zero-terminated
+ gzipped.write(deflated)
+ gzipped.write(gzipTrailer)
+ assertGzipped(gzipped)
+ }
+
+ /**
+ * For portability, it is a good idea to export the gzipped bytes and try running gzip. Ex.
+ * `echo gzipped | base64 --decode | gzip -l -v`
+ */
+ @Test
+ fun gunzip_withAll() {
+ val gzipped = Buffer()
+ gzipped.write(gzipHeaderWithFlags(0x1c.toByte()))
+ gzipped.writeShort(7.toShort().reverseBytes().toInt()) // little endian extra length
+ gzipped.write("blubber".toByteArray(UTF_8), 0, 7)
+ gzipped.write("foo.txt".toByteArray(UTF_8), 0, 7)
+ gzipped.writeByte(0) // zero-terminated
+ gzipped.write("rubbish".toByteArray(UTF_8), 0, 7)
+ gzipped.writeByte(0) // zero-terminated
+ gzipped.write(deflated)
+ gzipped.write(gzipTrailer)
+ assertGzipped(gzipped)
+ }
+
+ private fun assertGzipped(gzipped: Buffer) {
+ val gunzipped = gunzip(gzipped)
+ assertEquals("It's a UNIX system! I know this!", gunzipped.readUtf8())
+ }
+
+ /**
+ * Note that you cannot test this with old versions of gzip, as they interpret flag bit 1 as
+ * CONTINUATION, not HCRC. For example, this is the case with the default gzip on osx.
+ */
+ @Test
+ fun gunzipWhenHeaderCRCIncorrect() {
+ val gzipped = Buffer()
+ gzipped.write(gzipHeaderWithFlags(0x02.toByte()))
+ gzipped.writeShort(0.toShort().toInt()) // wrong HCRC!
+ gzipped.write(deflated)
+ gzipped.write(gzipTrailer)
+ try {
+ gunzip(gzipped)
+ fail()
+ } catch (e: IOException) {
+ assertEquals("FHCRC: actual 0x0000261d != expected 0x00000000", e.message)
+ }
+ }
+
+ @Test
+ fun gunzipWhenCRCIncorrect() {
+ val gzipped = Buffer()
+ gzipped.write(gzipHeader)
+ gzipped.write(deflated)
+ gzipped.writeInt(0x1234567.reverseBytes()) // wrong CRC
+ gzipped.write(gzipTrailer.toByteArray(), 3, 4)
+ try {
+ gunzip(gzipped)
+ fail()
+ } catch (e: IOException) {
+ assertEquals("CRC: actual 0x37ad8f8d != expected 0x01234567", e.message)
+ }
+ }
+
+ @Test
+ fun gunzipWhenLengthIncorrect() {
+ val gzipped = Buffer()
+ gzipped.write(gzipHeader)
+ gzipped.write(deflated)
+ gzipped.write(gzipTrailer.toByteArray(), 0, 4)
+ gzipped.writeInt(0x123456.reverseBytes()) // wrong length
+ try {
+ gunzip(gzipped)
+ fail()
+ } catch (e: IOException) {
+ assertEquals("ISIZE: actual 0x00000020 != expected 0x00123456", e.message)
+ }
+ }
+
+ @Test
+ fun gunzipExhaustsSource() {
+ val gzippedSource = Buffer()
+ .write("1f8b08000000000000004b4c4a0600c241243503000000".decodeHex()) // 'abc'
+ val exhaustableSource = ExhaustableSource(gzippedSource)
+ val gunzippedSource = GzipSource(exhaustableSource).buffer()
+ assertEquals('a'.code.toLong(), gunzippedSource.readByte().toLong())
+ assertEquals('b'.code.toLong(), gunzippedSource.readByte().toLong())
+ assertEquals('c'.code.toLong(), gunzippedSource.readByte().toLong())
+ assertFalse(exhaustableSource.exhausted)
+ assertEquals(-1, gunzippedSource.read(Buffer(), 1))
+ assertTrue(exhaustableSource.exhausted)
+ }
+
+ @Test
+ fun gunzipThrowsIfSourceIsNotExhausted() {
+ val gzippedSource = Buffer()
+ .write("1f8b08000000000000004b4c4a0600c241243503000000".decodeHex()) // 'abc'
+ gzippedSource.writeByte('d'.code) // This byte shouldn't be here!
+ val gunzippedSource = GzipSource(gzippedSource).buffer()
+ assertEquals('a'.code.toLong(), gunzippedSource.readByte().toLong())
+ assertEquals('b'.code.toLong(), gunzippedSource.readByte().toLong())
+ assertEquals('c'.code.toLong(), gunzippedSource.readByte().toLong())
+ try {
+ gunzippedSource.readByte()
+ fail()
+ } catch (expected: IOException) {
+ }
+ }
+
+ private fun gzipHeaderWithFlags(flags: Byte): ByteString {
+ val result = gzipHeader.toByteArray()
+ result[3] = flags
+ return of(*result)
+ }
+
+ private val gzipHeader = "1f8b0800000000000000".decodeHex()
+
+ // Deflated "It's a UNIX system! I know this!"
+ private val deflated = "f32c512f56485408f5f38c5028ae2c2e49cd5554f054c8cecb2f5728c9c82c560400".decodeHex()
+ private val gzipTrailer = (
+ "" +
+ "8d8fad37" + // Checksum of deflated.
+ "20000000"
+ ) // 32 in little endian.
+ .decodeHex()
+
+ private fun gunzip(gzipped: Buffer): Buffer {
+ val result = Buffer()
+ val source = GzipSource(gzipped)
+ while (source.read(result, Int.MAX_VALUE.toLong()) != -1L) {
+ }
+ return result
+ }
+
+ /** This source keeps track of whether its read has returned -1. */
+ internal class ExhaustableSource(private val source: Source) : Source {
+ var exhausted = false
+
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ val result = source.read(sink, byteCount)
+ if (result == -1L) exhausted = true
+ return result
+ }
+
+ override fun timeout(): Timeout {
+ return source.timeout()
+ }
+
+ override fun close() {
+ source.close()
+ }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/InflaterSourceTest.kt b/okio/src/jvmTest/kotlin/okio/InflaterSourceTest.kt
new file mode 100644
index 00000000..53273776
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/InflaterSourceTest.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.EOFException
+import java.util.zip.DeflaterOutputStream
+import java.util.zip.Inflater
+import okio.BufferedSourceFactory.Companion.PARAMETERIZED_TEST_VALUES
+import okio.ByteString.Companion.decodeBase64
+import okio.ByteString.Companion.encodeUtf8
+import okio.TestUtil.SEGMENT_SIZE
+import okio.TestUtil.randomBytes
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Assume.assumeFalse
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class InflaterSourceTest(
+ private val bufferFactory: BufferedSourceFactory,
+) {
+ private lateinit var deflatedSink: BufferedSink
+ private lateinit var deflatedSource: BufferedSource
+
+ init {
+ resetDeflatedSourceAndSink()
+ }
+
+ private fun resetDeflatedSourceAndSink() {
+ val pipe = bufferFactory.pipe()
+ deflatedSink = pipe.sink
+ deflatedSource = pipe.source
+ }
+
+ @Test
+ fun inflate() {
+ decodeBase64("eJxzz09RyEjNKVAoLdZRKE9VL0pVyMxTKMlIVchIzEspVshPU0jNS8/MS00tKtYDAF6CD5s=")
+ val inflated = inflate(deflatedSource)
+ assertEquals("God help us, we're in the hands of engineers.", inflated.readUtf8())
+ }
+
+ @Test
+ fun inflateTruncated() {
+ decodeBase64("eJxzz09RyEjNKVAoLdZRKE9VL0pVyMxTKMlIVchIzEspVshPU0jNS8/MS00tKtYDAF6CDw==")
+ try {
+ inflate(deflatedSource)
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun inflateWellCompressed() {
+ decodeBase64(
+ "eJztwTEBAAAAwqCs61/CEL5AAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8BtFeWvE=",
+ )
+ val original = "a".repeat(1024 * 1024)
+ deflate(original.encodeUtf8())
+ val inflated = inflate(deflatedSource)
+ assertEquals(original, inflated.readUtf8())
+ }
+
+ @Test
+ fun inflatePoorlyCompressed() {
+ assumeFalse(bufferFactory.isOneByteAtATime) // 8 GiB for 1 byte per segment!
+ val original = randomBytes(1024 * 1024)
+ deflate(original)
+ val inflated = inflate(deflatedSource)
+ assertEquals(original, inflated.readByteString())
+ }
+
+ @Test
+ fun inflateIntoNonemptySink() {
+ for (i in 0 until SEGMENT_SIZE) {
+ resetDeflatedSourceAndSink()
+ val inflated = Buffer().writeUtf8("a".repeat(i))
+ deflate("God help us, we're in the hands of engineers.".encodeUtf8())
+ val source = InflaterSource(deflatedSource, Inflater())
+ while (source.read(inflated, Int.MAX_VALUE.toLong()) != -1L) {
+ }
+ inflated.skip(i.toLong())
+ assertEquals("God help us, we're in the hands of engineers.", inflated.readUtf8())
+ }
+ }
+
+ @Test
+ fun inflateSingleByte() {
+ val inflated = Buffer()
+ decodeBase64("eJxzz09RyEjNKVAoLdZRKE9VL0pVyMxTKMlIVchIzEspVshPU0jNS8/MS00tKtYDAF6CD5s=")
+ val source = InflaterSource(deflatedSource, Inflater())
+ source.read(inflated, 1)
+ source.close()
+ assertEquals("G", inflated.readUtf8())
+ assertEquals(0, inflated.size)
+ }
+
+ @Test
+ fun inflateByteCount() {
+ assumeFalse(bufferFactory.isOneByteAtATime) // This test assumes one step.
+ val inflated = Buffer()
+ decodeBase64("eJxzz09RyEjNKVAoLdZRKE9VL0pVyMxTKMlIVchIzEspVshPU0jNS8/MS00tKtYDAF6CD5s=")
+ val source = InflaterSource(deflatedSource, Inflater())
+ source.read(inflated, 11)
+ source.close()
+ assertEquals("God help us", inflated.readUtf8())
+ assertEquals(0, inflated.size)
+ }
+
+ @Test
+ fun sourceExhaustedPrematurelyOnRead() {
+ // Deflate 0 bytes of data that lacks the in-stream terminator.
+ decodeBase64("eJwAAAD//w==")
+ val inflated = Buffer()
+ val inflater = Inflater()
+ val source = InflaterSource(deflatedSource, inflater)
+ assertThat(deflatedSource.exhausted()).isFalse
+ try {
+ source.read(inflated, Long.MAX_VALUE)
+ fail()
+ } catch (expected: EOFException) {
+ assertThat(expected).hasMessage("source exhausted prematurely")
+ }
+
+ // Despite the exception, the read() call made forward progress on the underlying stream!
+ assertThat(deflatedSource.exhausted()).isTrue
+ }
+
+ /**
+ * Confirm that [InflaterSource.readOrInflate] consumes a byte on each call even if it
+ * doesn't produce a byte on every call.
+ */
+ @Test
+ fun readOrInflateMakesByteByByteProgress() {
+ // Deflate 0 bytes of data that lacks the in-stream terminator.
+ decodeBase64("eJwAAAD//w==")
+ val deflatedByteCount = 7
+ val inflated = Buffer()
+ val inflater = Inflater()
+ val source = InflaterSource(deflatedSource, inflater)
+ assertThat(deflatedSource.exhausted()).isFalse
+ if (bufferFactory.isOneByteAtATime) {
+ for (i in 0 until deflatedByteCount) {
+ assertThat(inflater.bytesRead).isEqualTo(i.toLong())
+ assertThat(source.readOrInflate(inflated, Long.MAX_VALUE)).isEqualTo(0L)
+ }
+ } else {
+ assertThat(source.readOrInflate(inflated, Long.MAX_VALUE)).isEqualTo(0L)
+ }
+ assertThat(inflater.bytesRead).isEqualTo(deflatedByteCount.toLong())
+ assertThat(deflatedSource.exhausted()).isTrue()
+ }
+
+ private fun decodeBase64(s: String) {
+ deflatedSink.write(s.decodeBase64()!!)
+ deflatedSink.flush()
+ }
+
+ /** Use DeflaterOutputStream to deflate source. */
+ private fun deflate(source: ByteString) {
+ val sink = DeflaterOutputStream(deflatedSink.outputStream()).sink()
+ sink.write(Buffer().write(source), source.size.toLong())
+ sink.close()
+ }
+
+ /** Returns a new buffer containing the inflated contents of `deflated`. */
+ private fun inflate(deflated: BufferedSource?): Buffer {
+ val result = Buffer()
+ val source = InflaterSource(deflated!!, Inflater())
+ while (source.read(result, Int.MAX_VALUE.toLong()) != -1L) {
+ }
+ return result
+ }
+
+ companion object {
+ /**
+ * Use a parameterized test to control how many bytes the InflaterSource gets with each request
+ * for more bytes.
+ */
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun parameters(): List<Array<Any>> = PARAMETERIZED_TEST_VALUES
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/JimfsOkioRoundTripTest.kt b/okio/src/jvmTest/kotlin/okio/JimfsOkioRoundTripTest.kt
new file mode 100644
index 00000000..d4e2239b
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/JimfsOkioRoundTripTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import com.google.common.jimfs.Configuration
+import com.google.common.jimfs.Jimfs
+import java.nio.file.StandardOpenOption
+import kotlin.io.path.readText
+import kotlin.io.path.writeText
+import kotlin.test.BeforeTest
+import kotlin.test.assertEquals
+import okio.FileSystem.Companion.asOkioFileSystem
+import org.junit.Test
+
+class JimfsOkioRoundTripTest {
+ private val temporaryDirectory = FileSystem.SYSTEM_TEMPORARY_DIRECTORY
+ private val jimFs = Jimfs.newFileSystem(
+ when (Path.DIRECTORY_SEPARATOR == "\\") {
+ true -> Configuration.windows()
+ false -> Configuration.unix()
+ }.toBuilder()
+ .setWorkingDirectory(temporaryDirectory.toString())
+ .build(),
+ )
+ private val jimFsRoot = jimFs.rootDirectories.first()
+ private val okioFs = jimFs.asOkioFileSystem()
+ private val base: Path = temporaryDirectory / "${this::class.simpleName}-${randomToken(16)}"
+
+ @BeforeTest
+ fun setUp() {
+ okioFs.createDirectory(base)
+ }
+
+ @Test
+ fun writeOkioReadJim() {
+ val path = base / "file-handle-write-okio-and-read-jim"
+
+ okioFs.write(path) {
+ writeUtf8("abcdefghijklmnop")
+ }
+
+ assertEquals("abcdefghijklmnop", jimFsRoot.resolve(path.toString()).readText(Charsets.UTF_8))
+ }
+
+ @Test
+ fun writeJimReadOkio() {
+ val path = base / "file-handle-write-jim-and-read-okio"
+ jimFsRoot.resolve(path.toString()).writeText("abcdefghijklmnop", Charsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.WRITE)
+
+ okioFs.openReadWrite(path).use { handle ->
+ handle.source().buffer().use { source ->
+ assertEquals("abcde", source.readUtf8(5))
+ assertEquals("fghijklmnop", source.readUtf8())
+ }
+ }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/JvmSystemFileSystemTest.kt b/okio/src/jvmTest/kotlin/okio/JvmSystemFileSystemTest.kt
new file mode 100644
index 00000000..fc4b7c09
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/JvmSystemFileSystemTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import com.google.common.jimfs.Configuration
+import com.google.common.jimfs.Jimfs
+import java.io.InterruptedIOException
+import java.nio.file.FileSystems
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.fail
+import kotlinx.datetime.Clock
+import okio.FileSystem.Companion.asOkioFileSystem
+import org.junit.Test
+
+/**
+ * This test will run using [NioSystemFileSystem] by default. If [java.nio.file.Files] is not found
+ * on the classpath, [JvmSystemFileSystem] will be use instead.
+ */
+class NioSystemFileSystemTest : AbstractFileSystemTest(
+ clock = Clock.System,
+ fileSystem = FileSystem.SYSTEM,
+ windowsLimitations = Path.DIRECTORY_SEPARATOR == "\\",
+ allowClobberingEmptyDirectories = Path.DIRECTORY_SEPARATOR == "\\",
+ allowAtomicMoveFromFileToDirectory = false,
+ temporaryDirectory = FileSystem.SYSTEM_TEMPORARY_DIRECTORY,
+)
+
+class JvmSystemFileSystemTest : AbstractFileSystemTest(
+ clock = Clock.System,
+ fileSystem = JvmSystemFileSystem(),
+ windowsLimitations = Path.DIRECTORY_SEPARATOR == "\\",
+ allowClobberingEmptyDirectories = Path.DIRECTORY_SEPARATOR == "\\",
+ allowAtomicMoveFromFileToDirectory = false,
+ temporaryDirectory = FileSystem.SYSTEM_TEMPORARY_DIRECTORY,
+) {
+
+ @Test fun checkInterruptedBeforeDeleting() {
+ Thread.currentThread().interrupt()
+ try {
+ fileSystem.delete(base)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("interrupted", expected.message)
+ assertFalse(Thread.interrupted())
+ }
+ }
+}
+
+class NioJimFileSystemWrappingFileSystemTest : AbstractFileSystemTest(
+ clock = Clock.System,
+ fileSystem = Jimfs
+ .newFileSystem(
+ when (Path.DIRECTORY_SEPARATOR == "\\") {
+ true -> Configuration.windows()
+ false -> Configuration.unix()
+ },
+ ).asOkioFileSystem(),
+ windowsLimitations = false,
+ allowClobberingEmptyDirectories = true,
+ allowAtomicMoveFromFileToDirectory = true,
+ temporaryDirectory = FileSystem.SYSTEM_TEMPORARY_DIRECTORY,
+)
+
+class NioDefaultFileSystemWrappingFileSystemTest : AbstractFileSystemTest(
+ clock = Clock.System,
+ fileSystem = FileSystems.getDefault().asOkioFileSystem(),
+ windowsLimitations = false,
+ allowClobberingEmptyDirectories = Path.DIRECTORY_SEPARATOR == "\\",
+ allowAtomicMoveFromFileToDirectory = false,
+ allowRenameWhenTargetIsOpen = Path.DIRECTORY_SEPARATOR != "\\",
+ temporaryDirectory = FileSystem.SYSTEM_TEMPORARY_DIRECTORY,
+)
diff --git a/okio/src/jvmTest/kotlin/okio/JvmTest.kt b/okio/src/jvmTest/kotlin/okio/JvmTest.kt
new file mode 100644
index 00000000..690e64a3
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/JvmTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.File
+import java.nio.file.Paths
+import kotlin.test.Test
+import okio.Path.Companion.toOkioPath
+import okio.Path.Companion.toPath
+import org.assertj.core.api.Assertions.assertThat
+
+class JvmTest {
+ @Test
+ fun baseDirectoryConsistentWithJavaIoFile() {
+ assertThat(FileSystem.SYSTEM.canonicalize(".".toPath()).toString())
+ .isEqualTo(File("").canonicalFile.toString())
+ }
+
+ @Test
+ fun javaIoFileToOkioPath() {
+ val string = "/foo/bar/baz".replace("/", Path.DIRECTORY_SEPARATOR)
+ val javaIoFile = File(string)
+ val okioPath = string.toPath()
+ assertThat(javaIoFile.toOkioPath()).isEqualTo(okioPath)
+ assertThat(okioPath.toFile()).isEqualTo(javaIoFile)
+ }
+
+ @Test
+ fun nioPathToOkioPath() {
+ val string = "/foo/bar/baz".replace("/", Path.DIRECTORY_SEPARATOR)
+ val nioPath = Paths.get(string)
+ val okioPath = string.toPath()
+ assertThat(nioPath.toOkioPath()).isEqualTo(okioPath)
+ assertThat(okioPath.toNioPath() as Any).isEqualTo(nioPath)
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/JvmTesting.kt b/okio/src/jvmTest/kotlin/okio/JvmTesting.kt
new file mode 100644
index 00000000..e6b091d7
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/JvmTesting.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import okio.Path.Companion.toOkioPath
+import okio.Path.Companion.toPath
+
+actual fun assertRelativeTo(
+ a: Path,
+ b: Path,
+ bRelativeToA: Path,
+ sameAsNio: Boolean,
+) {
+ val actual = b.relativeTo(a)
+ assertEquals(bRelativeToA, actual)
+ assertEquals(b.normalized().withUnixSlashes(), (a / actual).normalized().withUnixSlashes())
+ // Also confirm our behavior is consistent with java.nio.
+ if (sameAsNio) {
+ // On Windows, java.nio will modify slashes to backslashes for relative paths, so we force it.
+ val nioPath = a.toNioPath().relativize(b.toNioPath())
+ .toOkioPath(normalize = true).withUnixSlashes().toPath()
+ assertEquals(bRelativeToA, nioPath)
+ }
+}
+
+actual fun assertRelativeToFails(
+ a: Path,
+ b: Path,
+ sameAsNio: Boolean,
+): IllegalArgumentException {
+ // Check java.nio first.
+ if (sameAsNio) {
+ assertFailsWith<IllegalArgumentException> {
+ a.toNioPath().relativize(b.toNioPath())
+ }
+ }
+ // Return okio.
+ return assertFailsWith { b.relativeTo(a) }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/LargeStreamsTest.kt b/okio/src/jvmTest/kotlin/okio/LargeStreamsTest.kt
new file mode 100644
index 00000000..47d387fe
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/LargeStreamsTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.util.concurrent.Future
+import java.util.zip.Deflater
+import java.util.zip.GZIPInputStream
+import java.util.zip.GZIPOutputStream
+import okio.ByteString.Companion.decodeHex
+import okio.HashingSink.Companion.sha256
+import okio.TestUtil.SEGMENT_SIZE
+import okio.TestUtil.randomSource
+import okio.TestingExecutors.newExecutorService
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/** Slow running tests that run a large amount of data through a stream. */
+class LargeStreamsTest {
+ @Test
+ fun test() {
+ val pipe = Pipe((1024 * 1024).toLong())
+ val future = readAllAndCloseAsync(randomSource(FOUR_GIB_PLUS_ONE), pipe.sink)
+ val hashingSink = sha256(blackholeSink())
+ readAllAndClose(pipe.source, hashingSink)
+ assertEquals(FOUR_GIB_PLUS_ONE, future.get() as Long)
+ assertEquals(SHA256_RANDOM_FOUR_GIB_PLUS_1, hashingSink.hash)
+ }
+
+ /** Note that this test hangs on Android. */
+ @Test
+ fun gzipSource() {
+ val pipe = Pipe(1024L * 1024)
+ val gzipOut = object : GZIPOutputStream(pipe.sink.buffer().outputStream()) {
+ init {
+ // Disable compression to speed up a slow test. Improved from 141s to 33s on one machine.
+ def.setLevel(Deflater.NO_COMPRESSION)
+ }
+ }
+ val future = readAllAndCloseAsync(
+ randomSource(FOUR_GIB_PLUS_ONE),
+ gzipOut.sink(),
+ )
+ val hashingSink = sha256(blackholeSink())
+ val gzipSource = GzipSource(pipe.source)
+ readAllAndClose(gzipSource, hashingSink)
+ assertEquals(FOUR_GIB_PLUS_ONE, future.get() as Long)
+ assertEquals(SHA256_RANDOM_FOUR_GIB_PLUS_1, hashingSink.hash)
+ }
+
+ /** Note that this test hangs on Android. */
+ @Test
+ fun gzipSink() {
+ val pipe = Pipe(1024L * 1024)
+ val gzipSink = GzipSink(pipe.sink)
+
+ // Disable compression to speed up a slow test. Improved from 141s to 35s on one machine.
+ gzipSink.deflater.setLevel(Deflater.NO_COMPRESSION)
+ val future = readAllAndCloseAsync(randomSource(FOUR_GIB_PLUS_ONE), gzipSink)
+ val hashingSink = sha256(blackholeSink())
+ val gzipIn = GZIPInputStream(pipe.source.buffer().inputStream())
+ readAllAndClose(gzipIn.source(), hashingSink)
+ assertEquals(FOUR_GIB_PLUS_ONE, future.get() as Long)
+ assertEquals(SHA256_RANDOM_FOUR_GIB_PLUS_1, hashingSink.hash)
+ }
+
+ /** Reads all bytes from `source` and writes them to `sink`. */
+ private fun readAllAndClose(source: Source, sink: Sink): Long {
+ var result = 0L
+ val buffer = Buffer()
+ while (true) {
+ val count = source.read(buffer, SEGMENT_SIZE.toLong())
+ if (count == -1L) break
+ sink.write(buffer, count)
+ result += count
+ }
+ source.close()
+ sink.close()
+ return result
+ }
+
+ /** Calls [readAllAndClose] on a background thread. */
+ private fun readAllAndCloseAsync(source: Source, sink: Sink): Future<Long> {
+ val executor = newExecutorService(0)
+ return try {
+ executor.submit<Long> { readAllAndClose(source, sink) }
+ } finally {
+ executor.shutdown()
+ }
+ }
+
+ companion object {
+ /** 4 GiB plus 1 byte. This is greater than what can be expressed in an unsigned int. */
+ const val FOUR_GIB_PLUS_ONE = 0x100000001L
+
+ /** SHA-256 of `TestUtil.randomSource(FOUR_GIB_PLUS_ONE)`. */
+ val SHA256_RANDOM_FOUR_GIB_PLUS_1 =
+ "9654947a655c5efc445502fd1bf11117d894b7812b7974fde8ca4a02c5066315".decodeHex()
+ }
+}
diff --git a/okio/src/jvmTest/java/okio/MessageDigestConsistencyTest.kt b/okio/src/jvmTest/kotlin/okio/MessageDigestConsistencyTest.kt
index 962d0119..dac72988 100644
--- a/okio/src/jvmTest/java/okio/MessageDigestConsistencyTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/MessageDigestConsistencyTest.kt
@@ -15,6 +15,9 @@
*/
package okio
+import java.security.MessageDigest
+import java.util.Random
+import kotlin.test.Test
import okio.ByteString.Companion.toByteString
import okio.internal.HashFunction
import okio.internal.Md5
@@ -22,9 +25,6 @@ import okio.internal.Sha1
import okio.internal.Sha256
import okio.internal.Sha512
import org.assertj.core.api.Assertions.assertThat
-import java.security.MessageDigest
-import java.util.Random
-import kotlin.test.Test
/**
* Confirm Okio is consistent with the JDK's MessageDigest algorithms for various sizes and slices.
@@ -55,7 +55,7 @@ class MessageDigestConsistencyTest {
algorithm = algorithm,
hashFunction = newHashFunction(),
seed = seed,
- updateCount = updateCount
+ updateCount = updateCount,
)
}
}
@@ -65,7 +65,7 @@ class MessageDigestConsistencyTest {
algorithm: String,
hashFunction: HashFunction,
seed: Long,
- updateCount: Int
+ updateCount: Int,
) {
val data = Buffer()
@@ -79,13 +79,13 @@ class MessageDigestConsistencyTest {
hashFunction.update(
input = byteArray,
offset = offset,
- byteCount = byteCount
+ byteCount = byteCount,
)
data.write(
source = byteArray,
offset = offset,
- byteCount = byteCount
+ byteCount = byteCount,
)
}
diff --git a/okio/src/jvmTest/kotlin/okio/NioTest.kt b/okio/src/jvmTest/kotlin/okio/NioTest.kt
new file mode 100644
index 00000000..35e11ceb
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/NioTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.nio.ByteBuffer
+import java.nio.channels.FileChannel
+import java.nio.channels.ReadableByteChannel
+import java.nio.channels.WritableByteChannel
+import java.nio.file.StandardOpenOption
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import kotlin.text.Charsets.UTF_8
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+/** Test interop between our beloved Okio and java.nio. */
+class NioTest {
+ @JvmField @Rule
+ var temporaryFolder = TemporaryFolder()
+
+ @Test
+ fun sourceIsOpen() {
+ val source = (Buffer() as Source).buffer()
+ assertTrue(source.isOpen())
+ source.close()
+ assertFalse(source.isOpen())
+ }
+
+ @Test
+ fun sinkIsOpen() {
+ val sink = (Buffer() as Sink).buffer()
+ assertTrue(sink.isOpen)
+ sink.close()
+ assertFalse(sink.isOpen)
+ }
+
+ @Test
+ fun writableChannelNioFile() {
+ val file = temporaryFolder.newFile()
+ val fileChannel = FileChannel.open(file.toPath(), StandardOpenOption.WRITE)
+ testWritableByteChannel(fileChannel)
+ val emitted = file.source().buffer()
+ assertEquals("defghijklmnopqrstuvw", emitted.readUtf8())
+ emitted.close()
+ }
+
+ @Test
+ fun writableChannelBuffer() {
+ val buffer = Buffer()
+ testWritableByteChannel(buffer)
+ assertEquals("defghijklmnopqrstuvw", buffer.readUtf8())
+ }
+
+ @Test
+ fun writableChannelBufferedSink() {
+ val buffer = Buffer()
+ val bufferedSink = (buffer as Sink).buffer()
+ testWritableByteChannel(bufferedSink)
+ assertEquals("defghijklmnopqrstuvw", buffer.readUtf8())
+ }
+
+ @Test
+ fun readableChannelNioFile() {
+ val file = temporaryFolder.newFile()
+ val initialData = file.sink().buffer()
+ initialData.writeUtf8("abcdefghijklmnopqrstuvwxyz")
+ initialData.close()
+ val fileChannel = FileChannel.open(file.toPath(), StandardOpenOption.READ)
+ testReadableByteChannel(fileChannel)
+ }
+
+ @Test
+ fun readableChannelBuffer() {
+ val buffer = Buffer()
+ buffer.writeUtf8("abcdefghijklmnopqrstuvwxyz")
+ testReadableByteChannel(buffer)
+ }
+
+ @Test
+ fun readableChannelBufferedSource() {
+ val buffer = Buffer()
+ val bufferedSource = (buffer as Source).buffer()
+ buffer.writeUtf8("abcdefghijklmnopqrstuvwxyz")
+ testReadableByteChannel(bufferedSource)
+ }
+
+ /**
+ * Does some basic writes to `channel`. We execute this against both Okio's channels and
+ * also a standard implementation from the JDK to confirm that their behavior is consistent.
+ */
+ private fun testWritableByteChannel(channel: WritableByteChannel) {
+ assertTrue(channel.isOpen)
+ val byteBuffer = ByteBuffer.allocate(1024)
+ byteBuffer.put("abcdefghijklmnopqrstuvwxyz".toByteArray(UTF_8))
+ (byteBuffer as java.nio.Buffer).flip() // Cast necessary for Java 8.
+ (byteBuffer as java.nio.Buffer).position(3) // Cast necessary for Java 8.
+ (byteBuffer as java.nio.Buffer).limit(23) // Cast necessary for Java 8.
+ val byteCount = channel.write(byteBuffer)
+ assertEquals(20, byteCount)
+ assertEquals(23, byteBuffer.position())
+ assertEquals(23, byteBuffer.limit())
+ channel.close()
+ assertEquals(channel is Buffer, channel.isOpen) // Buffer.close() does nothing.
+ }
+
+ /**
+ * Does some basic reads from `channel`. We execute this against both Okio's channels and
+ * also a standard implementation from the JDK to confirm that their behavior is consistent.
+ */
+ private fun testReadableByteChannel(channel: ReadableByteChannel) {
+ assertTrue(channel.isOpen)
+ val byteBuffer = ByteBuffer.allocate(1024)
+ (byteBuffer as java.nio.Buffer).position(3) // Cast necessary for Java 8.
+ (byteBuffer as java.nio.Buffer).limit(23) // Cast necessary for Java 8.
+ val byteCount = channel.read(byteBuffer)
+ assertEquals(20, byteCount)
+ assertEquals(23, byteBuffer.position())
+ assertEquals(23, byteBuffer.limit())
+ channel.close()
+ assertEquals(channel is Buffer, channel.isOpen) // Buffer.close() does nothing.
+ (byteBuffer as java.nio.Buffer).flip() // Cast necessary for Java 8.
+ (byteBuffer as java.nio.Buffer).position(3) // Cast necessary for Java 8.
+ val data = ByteArray(byteBuffer.remaining())
+ byteBuffer[data]
+ assertEquals("abcdefghijklmnopqrst", String(data, UTF_8))
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/OkioKotlinTest.kt b/okio/src/jvmTest/kotlin/okio/OkioKotlinTest.kt
index 58c6bcad..86270d1e 100644
--- a/okio/src/jvmTest/kotlin/okio/OkioKotlinTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/OkioKotlinTest.kt
@@ -16,17 +16,17 @@
package okio
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TemporaryFolder
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.net.Socket
import java.nio.file.StandardOpenOption
import java.nio.file.StandardOpenOption.APPEND
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
class OkioKotlinTest {
@get:Rule val temp = TemporaryFolder()
@@ -96,7 +96,8 @@ class OkioKotlinTest {
}
@Ignore("Not sure how to test this")
- @Test fun pathSourceWithOptions() {
+ @Test
+ fun pathSourceWithOptions() {
val folder = temp.newFolder()
val file = File(folder, "new.txt")
file.toPath().source(StandardOpenOption.CREATE_NEW)
diff --git a/okio/src/jvmTest/kotlin/okio/OkioTest.kt b/okio/src/jvmTest/kotlin/okio/OkioTest.kt
new file mode 100644
index 00000000..9514ddd2
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/OkioTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.nio.file.Files
+import kotlin.text.Charsets.UTF_8
+import okio.TestUtil.SEGMENT_SIZE
+import okio.TestUtil.assertNoEmptySegments
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class OkioTest {
+ @JvmField
+ @Rule
+ var temporaryFolder = TemporaryFolder()
+
+ @Test
+ fun readWriteFile() {
+ val file = temporaryFolder.newFile()
+ val sink = file.sink().buffer()
+ sink.writeUtf8("Hello, java.io file!")
+ sink.close()
+ assertTrue(file.exists())
+ assertEquals(20, file.length())
+ val source = file.source().buffer()
+ assertEquals("Hello, java.io file!", source.readUtf8())
+ source.close()
+ }
+
+ @Test
+ fun appendFile() {
+ val file = temporaryFolder.newFile()
+ var sink = file.appendingSink().buffer()
+ sink.writeUtf8("Hello, ")
+ sink.close()
+ assertTrue(file.exists())
+ assertEquals(7, file.length())
+ sink = file.appendingSink().buffer()
+ sink.writeUtf8("java.io file!")
+ sink.close()
+ assertEquals(20, file.length())
+ val source = file.source().buffer()
+ assertEquals("Hello, java.io file!", source.readUtf8())
+ source.close()
+ }
+
+ @Test
+ fun readWritePath() {
+ val path = temporaryFolder.newFile().toPath()
+ val sink = path.sink().buffer()
+ sink.writeUtf8("Hello, java.nio file!")
+ sink.close()
+ assertTrue(Files.exists(path))
+ assertEquals(21, Files.size(path))
+ val source = path.source().buffer()
+ assertEquals("Hello, java.nio file!", source.readUtf8())
+ source.close()
+ }
+
+ @Test
+ fun sinkFromOutputStream() {
+ val data = Buffer()
+ data.writeUtf8("a")
+ data.writeUtf8("b".repeat(9998))
+ data.writeUtf8("c")
+ val out = ByteArrayOutputStream()
+ val sink = out.sink()
+ sink.write(data, 3)
+ assertEquals("abb", out.toString("UTF-8"))
+ sink.write(data, data.size)
+ assertEquals("a" + "b".repeat(9998) + "c", out.toString("UTF-8"))
+ }
+
+ @Test
+ fun sourceFromInputStream() {
+ val inputStream = ByteArrayInputStream(
+ ("a" + "b".repeat(SEGMENT_SIZE * 2) + "c").toByteArray(UTF_8),
+ )
+
+ // Source: ab...bc
+ val source = inputStream.source()
+ val sink = Buffer()
+
+ // Source: b...bc. Sink: abb.
+ assertEquals(3, source.read(sink, 3))
+ assertEquals("abb", sink.readUtf8(3))
+
+ // Source: b...bc. Sink: b...b.
+ assertEquals(SEGMENT_SIZE.toLong(), source.read(sink, 20000))
+ assertEquals("b".repeat(SEGMENT_SIZE), sink.readUtf8())
+
+ // Source: b...bc. Sink: b...bc.
+ assertEquals((SEGMENT_SIZE - 1).toLong(), source.read(sink, 20000))
+ assertEquals("b".repeat(SEGMENT_SIZE - 2) + "c", sink.readUtf8())
+
+ // Source and sink are empty.
+ assertEquals(-1, source.read(sink, 1))
+ }
+
+ @Test
+ fun sourceFromInputStreamWithSegmentSize() {
+ val inputStream = ByteArrayInputStream(ByteArray(SEGMENT_SIZE))
+ val source = inputStream.source()
+ val sink = Buffer()
+ assertEquals(SEGMENT_SIZE.toLong(), source.read(sink, SEGMENT_SIZE.toLong()))
+ assertEquals(-1, source.read(sink, SEGMENT_SIZE.toLong()))
+ assertNoEmptySegments(sink)
+ }
+
+ @Test
+ fun sourceFromInputStreamBounds() {
+ val source = ByteArrayInputStream(ByteArray(100)).source()
+ try {
+ source.read(Buffer(), -1)
+ fail()
+ } catch (expected: IllegalArgumentException) {
+ }
+ }
+
+ @Test
+ fun blackhole() {
+ val data = Buffer()
+ data.writeUtf8("blackhole")
+ val blackhole = blackholeSink()
+ blackhole.write(data, 5)
+ assertEquals("hole", data.readUtf8())
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/PipeKotlinTest.kt b/okio/src/jvmTest/kotlin/okio/PipeKotlinTest.kt
index 3a41e742..ac50f3a0 100644
--- a/okio/src/jvmTest/kotlin/okio/PipeKotlinTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/PipeKotlinTest.kt
@@ -15,6 +15,10 @@
*/
package okio
+import java.io.IOException
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertFailsWith
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -22,19 +26,16 @@ import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Rule
import org.junit.Test
-import java.io.IOException
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.Executors
-import java.util.concurrent.TimeUnit
-import kotlin.test.assertFailsWith
import org.junit.rules.Timeout as JUnitTimeout
class PipeKotlinTest {
- @JvmField @Rule val timeout = JUnitTimeout(5, TimeUnit.SECONDS)
+ @JvmField @Rule
+ val timeout = JUnitTimeout(5, TimeUnit.SECONDS)
- private val executorService = Executors.newScheduledThreadPool(1)
+ private val executorService = TestingExecutors.newScheduledExecutorService(1)
- @After @Throws(Exception::class)
+ @After
+ @Throws(Exception::class)
fun tearDown() {
executorService.shutdown()
}
@@ -108,7 +109,8 @@ class PipeKotlinTest {
pipe.fold(foldSink)
latch.countDown()
},
- 500, TimeUnit.MILLISECONDS
+ 500,
+ TimeUnit.MILLISECONDS,
)
val sink = pipe.sink.buffer()
@@ -536,7 +538,8 @@ class PipeKotlinTest {
}
assertEquals("boom", foldFailure.message)
},
- 500, TimeUnit.MILLISECONDS
+ 500,
+ TimeUnit.MILLISECONDS,
)
val writeFailure = assertFailsWith<IOException> {
@@ -656,7 +659,8 @@ class PipeKotlinTest {
{
pipe.cancel()
},
- smallerTimeoutNanos, TimeUnit.NANOSECONDS
+ smallerTimeoutNanos,
+ TimeUnit.NANOSECONDS,
)
val pipeSink = pipe.sink.buffer()
@@ -699,7 +703,8 @@ class PipeKotlinTest {
{
pipe.cancel()
},
- smallerTimeoutNanos, TimeUnit.NANOSECONDS
+ smallerTimeoutNanos,
+ TimeUnit.NANOSECONDS,
)
val pipeSource = pipe.source.buffer()
@@ -783,8 +788,9 @@ class PipeKotlinTest {
val elapsed = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis() - start)
assertEquals(
- expected.toDouble(), elapsed.toDouble(),
- TimeUnit.MILLISECONDS.toNanos(200).toDouble()
+ expected.toDouble(),
+ elapsed.toDouble(),
+ TimeUnit.MILLISECONDS.toNanos(200).toDouble(),
)
}
diff --git a/okio/src/jvmTest/kotlin/okio/PipeTest.kt b/okio/src/jvmTest/kotlin/okio/PipeTest.kt
new file mode 100644
index 00000000..74b61290
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/PipeTest.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.IOException
+import java.io.InterruptedIOException
+import java.util.Random
+import java.util.concurrent.TimeUnit
+import okio.ByteString.Companion.decodeHex
+import okio.HashingSink.Companion.sha1
+import okio.TestUtil.assumeNotWindows
+import okio.TestingExecutors.newScheduledExecutorService
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+
+class PipeTest {
+ private val executorService = newScheduledExecutorService(2)
+
+ @After
+ fun tearDown() {
+ executorService.shutdown()
+ }
+
+ @Test
+ fun test() {
+ val pipe = Pipe(6)
+ pipe.sink.write(Buffer().writeUtf8("abc"), 3L)
+ val source = pipe.source
+ val readBuffer = Buffer()
+ assertEquals(3L, source.read(readBuffer, 6L))
+ assertEquals("abc", readBuffer.readUtf8())
+ pipe.sink.close()
+ assertEquals(-1L, source.read(readBuffer, 6L))
+ source.close()
+ }
+
+ /**
+ * A producer writes the first 16 MiB of bytes generated by `new Random(0)` to a sink, and a
+ * consumer consumes them. Both compute hashes of their data to confirm that they're as expected.
+ */
+ @Test
+ fun largeDataset() {
+ val pipe = Pipe(1000L) // An awkward size to force producer/consumer exchange.
+ val totalBytes = 16L * 1024L * 1024L
+ val expectedHash = "7c3b224bea749086babe079360cf29f98d88262d".decodeHex()
+
+ // Write data to the sink.
+ val sinkHash = executorService.submit<ByteString> {
+ val hashingSink = sha1(pipe.sink)
+ val random = Random(0)
+ val data = ByteArray(8192)
+ val buffer = Buffer()
+ var i = 0L
+ while (i < totalBytes) {
+ random.nextBytes(data)
+ buffer.write(data)
+ hashingSink.write(buffer, buffer.size)
+ i += data.size.toLong()
+ }
+ hashingSink.close()
+ hashingSink.hash
+ }
+
+ // Read data from the source.
+ val sourceHash = executorService.submit<ByteString> {
+ val blackhole = Buffer()
+ val hashingSink = sha1(blackhole)
+ val buffer = Buffer()
+ while (pipe.source.read(buffer, Long.MAX_VALUE) != -1L) {
+ hashingSink.write(buffer, buffer.size)
+ blackhole.clear()
+ }
+ pipe.source.close()
+ hashingSink.hash
+ }
+ assertEquals(expectedHash, sinkHash.get())
+ assertEquals(expectedHash, sourceHash.get())
+ }
+
+ @Test
+ fun sinkTimeout() {
+ assumeNotWindows()
+ val pipe = Pipe(3)
+ pipe.sink.timeout().timeout(1000, TimeUnit.MILLISECONDS)
+ pipe.sink.write(Buffer().writeUtf8("abc"), 3L)
+ val start = now()
+ try {
+ pipe.sink.write(Buffer().writeUtf8("def"), 3L)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ val readBuffer = Buffer()
+ assertEquals(3L, pipe.source.read(readBuffer, 6L))
+ assertEquals("abc", readBuffer.readUtf8())
+ }
+
+ @Test
+ fun sourceTimeout() {
+ assumeNotWindows()
+ val pipe = Pipe(3L)
+ pipe.source.timeout().timeout(1000, TimeUnit.MILLISECONDS)
+ val start = now()
+ val readBuffer = Buffer()
+ try {
+ pipe.source.read(readBuffer, 6L)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ assertEquals(0, readBuffer.size)
+ }
+
+ /**
+ * The writer is writing 12 bytes as fast as it can to a 3 byte buffer. The reader alternates
+ * sleeping 1000 ms, then reading 3 bytes. That should make for an approximate timeline like
+ * this:
+ *
+ * ```
+ * 0: writer writes 'abc', blocks 0: reader sleeps until 1000
+ * 1000: reader reads 'abc', sleeps until 2000
+ * 1000: writer writes 'def', blocks
+ * 2000: reader reads 'def', sleeps until 3000
+ * 2000: writer writes 'ghi', blocks
+ * 3000: reader reads 'ghi', sleeps until 4000
+ * 3000: writer writes 'jkl', returns
+ * 4000: reader reads 'jkl', returns
+ * ```
+ *
+ *
+ * Because the writer is writing to a buffer, it finishes before the reader does.
+ */
+ @Test
+ fun sinkBlocksOnSlowReader() {
+ val pipe = Pipe(3L)
+ executorService.execute {
+ val buffer = Buffer()
+ Thread.sleep(1000L)
+ assertEquals(3, pipe.source.read(buffer, Long.MAX_VALUE))
+ assertEquals("abc", buffer.readUtf8())
+ Thread.sleep(1000L)
+ assertEquals(3, pipe.source.read(buffer, Long.MAX_VALUE))
+ assertEquals("def", buffer.readUtf8())
+ Thread.sleep(1000L)
+ assertEquals(3, pipe.source.read(buffer, Long.MAX_VALUE))
+ assertEquals("ghi", buffer.readUtf8())
+ Thread.sleep(1000L)
+ assertEquals(3, pipe.source.read(buffer, Long.MAX_VALUE))
+ assertEquals("jkl", buffer.readUtf8())
+ }
+ val start = now()
+ pipe.sink.write(Buffer().writeUtf8("abcdefghijkl"), 12)
+ assertElapsed(3000.0, start)
+ }
+
+ @Test
+ fun sinkWriteFailsByClosedReader() {
+ val pipe = Pipe(3L)
+ executorService.schedule(
+ {
+ pipe.source.close()
+ },
+ 1000,
+ TimeUnit.MILLISECONDS,
+ )
+ val start = now()
+ try {
+ pipe.sink.write(Buffer().writeUtf8("abcdef"), 6)
+ fail()
+ } catch (expected: IOException) {
+ assertEquals("source is closed", expected.message)
+ assertElapsed(1000.0, start)
+ }
+ }
+
+ @Test
+ fun sinkFlushDoesntWaitForReader() {
+ val pipe = Pipe(100L)
+ pipe.sink.write(Buffer().writeUtf8("abc"), 3)
+ pipe.sink.flush()
+ val bufferedSource = pipe.source.buffer()
+ assertEquals("abc", bufferedSource.readUtf8(3))
+ }
+
+ @Test
+ fun sinkFlushFailsIfReaderIsClosedBeforeAllDataIsRead() {
+ val pipe = Pipe(100L)
+ pipe.sink.write(Buffer().writeUtf8("abc"), 3)
+ pipe.source.close()
+ try {
+ pipe.sink.flush()
+ fail()
+ } catch (expected: IOException) {
+ assertEquals("source is closed", expected.message)
+ }
+ }
+
+ @Test
+ fun sinkCloseFailsIfReaderIsClosedBeforeAllDataIsRead() {
+ val pipe = Pipe(100L)
+ pipe.sink.write(Buffer().writeUtf8("abc"), 3)
+ pipe.source.close()
+ try {
+ pipe.sink.close()
+ fail()
+ } catch (expected: IOException) {
+ assertEquals("source is closed", expected.message)
+ }
+ }
+
+ @Test
+ fun sinkClose() {
+ val pipe = Pipe(100L)
+ pipe.sink.close()
+ try {
+ pipe.sink.write(Buffer().writeUtf8("abc"), 3)
+ fail()
+ } catch (expected: IllegalStateException) {
+ assertEquals("closed", expected.message)
+ }
+ try {
+ pipe.sink.flush()
+ fail()
+ } catch (expected: IllegalStateException) {
+ assertEquals("closed", expected.message)
+ }
+ }
+
+ @Test
+ fun sinkMultipleClose() {
+ val pipe = Pipe(100L)
+ pipe.sink.close()
+ pipe.sink.close()
+ }
+
+ @Test
+ fun sinkCloseDoesntWaitForSourceRead() {
+ val pipe = Pipe(100L)
+ pipe.sink.write(Buffer().writeUtf8("abc"), 3)
+ pipe.sink.close()
+ val bufferedSource = pipe.source.buffer()
+ assertEquals("abc", bufferedSource.readUtf8())
+ assertTrue(bufferedSource.exhausted())
+ }
+
+ @Test
+ fun sourceClose() {
+ val pipe = Pipe(100L)
+ pipe.source.close()
+ try {
+ pipe.source.read(Buffer(), 3)
+ fail()
+ } catch (expected: IllegalStateException) {
+ assertEquals("closed", expected.message)
+ }
+ }
+
+ @Test
+ fun sourceMultipleClose() {
+ val pipe = Pipe(100L)
+ pipe.source.close()
+ pipe.source.close()
+ }
+
+ @Test
+ fun sourceReadUnblockedByClosedSink() {
+ val pipe = Pipe(3L)
+ executorService.schedule(
+ {
+ pipe.sink.close()
+ },
+ 1000,
+ TimeUnit.MILLISECONDS,
+ )
+ val start = now()
+ val readBuffer = Buffer()
+ assertEquals(-1, pipe.source.read(readBuffer, Long.MAX_VALUE))
+ assertEquals(0, readBuffer.size)
+ assertElapsed(1000.0, start)
+ }
+
+ /**
+ * The writer has 12 bytes to write. It alternates sleeping 1000 ms, then writing 3 bytes. The
+ * reader is reading as fast as it can. That should make for an approximate timeline like this:
+ *
+ * ```
+ * 0: writer sleeps until 1000
+ * 0: reader blocks
+ * 1000: writer writes 'abc', sleeps until 2000
+ * 1000: reader reads 'abc'
+ * 2000: writer writes 'def', sleeps until 3000
+ * 2000: reader reads 'def'
+ * 3000: writer writes 'ghi', sleeps until 4000
+ * 3000: reader reads 'ghi'
+ * 4000: writer writes 'jkl', returns
+ * 4000: reader reads 'jkl', returns
+ * ```
+ */
+ @Test
+ fun sourceBlocksOnSlowWriter() {
+ val pipe = Pipe(100L)
+ executorService.execute {
+ Thread.sleep(1000L)
+ pipe.sink.write(Buffer().writeUtf8("abc"), 3)
+ Thread.sleep(1000L)
+ pipe.sink.write(Buffer().writeUtf8("def"), 3)
+ Thread.sleep(1000L)
+ pipe.sink.write(Buffer().writeUtf8("ghi"), 3)
+ Thread.sleep(1000L)
+ pipe.sink.write(Buffer().writeUtf8("jkl"), 3)
+ }
+ val start = now()
+ val readBuffer = Buffer()
+ assertEquals(3, pipe.source.read(readBuffer, Long.MAX_VALUE))
+ assertEquals("abc", readBuffer.readUtf8())
+ assertElapsed(1000.0, start)
+ assertEquals(3, pipe.source.read(readBuffer, Long.MAX_VALUE))
+ assertEquals("def", readBuffer.readUtf8())
+ assertElapsed(2000.0, start)
+ assertEquals(3, pipe.source.read(readBuffer, Long.MAX_VALUE))
+ assertEquals("ghi", readBuffer.readUtf8())
+ assertElapsed(3000.0, start)
+ assertEquals(3, pipe.source.read(readBuffer, Long.MAX_VALUE))
+ assertEquals("jkl", readBuffer.readUtf8())
+ assertElapsed(4000.0, start)
+ }
+
+ /** Returns the nanotime in milliseconds as a double for measuring timeouts. */
+ private fun now(): Double {
+ return System.nanoTime() / 1000000.0
+ }
+
+ /**
+ * Fails the test unless the time from start until now is duration, accepting differences in
+ * -50..+450 milliseconds.
+ */
+ private fun assertElapsed(duration: Double, start: Double) {
+ assertEquals(duration, now() - start - 200.0, 250.0)
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/ReadUtf8LineTest.kt b/okio/src/jvmTest/kotlin/okio/ReadUtf8LineTest.kt
new file mode 100644
index 00000000..6f64355b
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/ReadUtf8LineTest.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.EOFException
+import okio.TestUtil.SEGMENT_SIZE
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class ReadUtf8LineTest {
+ interface Factory {
+ fun create(data: Buffer): BufferedSource
+ }
+
+ @Parameter
+ lateinit var factory: Factory
+ private lateinit var data: Buffer
+ private lateinit var source: BufferedSource
+
+ @Before
+ fun setUp() {
+ data = Buffer()
+ source = factory.create(data)
+ }
+
+ @Test
+ fun readLines() {
+ data.writeUtf8("abc\ndef\n")
+ assertEquals("abc", source.readUtf8LineStrict())
+ assertEquals("def", source.readUtf8LineStrict())
+ try {
+ source.readUtf8LineStrict()
+ fail()
+ } catch (expected: EOFException) {
+ assertEquals("\\n not found: limit=0 content=…", expected.message)
+ }
+ }
+
+ @Test
+ fun readUtf8LineStrictWithLimits() {
+ val lens = intArrayOf(1, SEGMENT_SIZE - 2, SEGMENT_SIZE - 1, SEGMENT_SIZE, SEGMENT_SIZE * 10)
+ for (len in lens) {
+ data.writeUtf8("a".repeat(len)).writeUtf8("\n")
+ assertEquals(len.toLong(), source.readUtf8LineStrict(len.toLong()).length.toLong())
+ source.readUtf8()
+ data.writeUtf8("a".repeat(len)).writeUtf8("\n").writeUtf8("a".repeat(len))
+ assertEquals(len.toLong(), source.readUtf8LineStrict(len.toLong()).length.toLong())
+ source.readUtf8()
+ data.writeUtf8("a".repeat(len)).writeUtf8("\r\n")
+ assertEquals(len.toLong(), source.readUtf8LineStrict(len.toLong()).length.toLong())
+ source.readUtf8()
+ data.writeUtf8("a".repeat(len)).writeUtf8("\r\n").writeUtf8("a".repeat(len))
+ assertEquals(len.toLong(), source.readUtf8LineStrict(len.toLong()).length.toLong())
+ source.readUtf8()
+ }
+ }
+
+ @Test
+ fun readUtf8LineStrictNoBytesConsumedOnFailure() {
+ data.writeUtf8("abc\n")
+ try {
+ source.readUtf8LineStrict(2)
+ fail()
+ } catch (expected: EOFException) {
+ assertTrue(expected.message!!.startsWith("\\n not found: limit=2 content=61626"))
+ }
+ assertEquals("abc", source.readUtf8LineStrict(3))
+ }
+
+ @Test
+ fun readUtf8LineStrictEmptyString() {
+ data.writeUtf8("\r\nabc")
+ assertEquals("", source.readUtf8LineStrict(0))
+ assertEquals("abc", source.readUtf8())
+ }
+
+ @Test
+ fun readUtf8LineStrictNonPositive() {
+ data.writeUtf8("\r\n")
+ try {
+ source.readUtf8LineStrict(-1)
+ fail("Expected failure: limit must be greater than 0")
+ } catch (expected: IllegalArgumentException) {
+ }
+ }
+
+ @Test
+ fun eofExceptionProvidesLimitedContent() {
+ data.writeUtf8("aaaaaaaabbbbbbbbccccccccdddddddde")
+ try {
+ source.readUtf8LineStrict()
+ fail()
+ } catch (expected: EOFException) {
+ assertEquals(
+ "\\n not found: limit=33 content=616161616161616162626262626262626363636363636363" +
+ "6464646464646464…",
+ expected.message,
+ )
+ }
+ }
+
+ @Test
+ fun newlineAtEnd() {
+ data.writeUtf8("abc\n")
+ assertEquals("abc", source.readUtf8LineStrict(3))
+ assertTrue(source.exhausted())
+ data.writeUtf8("abc\r\n")
+ assertEquals("abc", source.readUtf8LineStrict(3))
+ assertTrue(source.exhausted())
+ data.writeUtf8("abc\r")
+ try {
+ source.readUtf8LineStrict(3)
+ fail()
+ } catch (expected: EOFException) {
+ assertEquals("\\n not found: limit=3 content=6162630d…", expected.message)
+ }
+ source.readUtf8()
+ data.writeUtf8("abc")
+ try {
+ source.readUtf8LineStrict(3)
+ fail()
+ } catch (expected: EOFException) {
+ assertEquals("\\n not found: limit=3 content=616263…", expected.message)
+ }
+ }
+
+ @Test
+ fun emptyLines() {
+ data.writeUtf8("\n\n\n")
+ assertEquals("", source.readUtf8LineStrict())
+ assertEquals("", source.readUtf8LineStrict())
+ assertEquals("", source.readUtf8LineStrict())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun crDroppedPrecedingLf() {
+ data.writeUtf8("abc\r\ndef\r\nghi\rjkl\r\n")
+ assertEquals("abc", source.readUtf8LineStrict())
+ assertEquals("def", source.readUtf8LineStrict())
+ assertEquals("ghi\rjkl", source.readUtf8LineStrict())
+ }
+
+ @Test
+ fun bufferedReaderCompatible() {
+ data.writeUtf8("abc\ndef")
+ assertEquals("abc", source.readUtf8Line())
+ assertEquals("def", source.readUtf8Line())
+ assertNull(source.readUtf8Line())
+ }
+
+ @Test
+ fun bufferedReaderCompatibleWithTrailingNewline() {
+ data.writeUtf8("abc\ndef\n")
+ assertEquals("abc", source.readUtf8Line())
+ assertEquals("def", source.readUtf8Line())
+ assertNull(source.readUtf8Line())
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun parameters(): List<Array<Any>> {
+ return listOf(
+ arrayOf(
+ object : Factory {
+ override fun create(data: Buffer) = data
+ override fun toString() = "Buffer"
+ },
+ ),
+ arrayOf(
+ object : Factory {
+ override fun create(data: Buffer) = RealBufferedSource(data)
+ override fun toString() = "RealBufferedSource"
+ },
+ ),
+ arrayOf(
+ object : Factory {
+ override fun create(data: Buffer): BufferedSource {
+ return RealBufferedSource(
+ object : ForwardingSource(data) {
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ return super.read(sink, 1L.coerceAtMost(byteCount))
+ }
+ },
+ )
+ }
+
+ override fun toString() = "Slow RealBufferedSource"
+ },
+ ),
+ )
+ }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/SegmentSharingTest.kt b/okio/src/jvmTest/kotlin/okio/SegmentSharingTest.kt
index 73e74d99..df4a9d5e 100644
--- a/okio/src/jvmTest/kotlin/okio/SegmentSharingTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/SegmentSharingTest.kt
@@ -15,14 +15,14 @@
*/
package okio
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
import okio.ByteString.Companion.encodeUtf8
import okio.TestUtil.assertEquivalent
import okio.TestUtil.bufferWithSegments
import okio.TestUtil.takeAllPoolSegments
import org.junit.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import kotlin.test.assertTrue
/** Tests behavior optimized by sharing segments between buffers and byte strings. */
class SegmentSharingTest {
@@ -40,12 +40,12 @@ class SegmentSharingTest {
@Test fun snapshotGetByte() {
val byteString = bufferWithSegments(xs, ys, zs).snapshot()
- assertEquals('x', byteString[0].toChar())
- assertEquals('x', byteString[xs.length - 1].toChar())
- assertEquals('y', byteString[xs.length].toChar())
- assertEquals('y', byteString[xs.length + ys.length - 1].toChar())
- assertEquals('z', byteString[xs.length + ys.length].toChar())
- assertEquals('z', byteString[xs.length + ys.length + zs.length - 1].toChar())
+ assertEquals('x', byteString[0].toInt().toChar())
+ assertEquals('x', byteString[xs.length - 1].toInt().toChar())
+ assertEquals('y', byteString[xs.length].toInt().toChar())
+ assertEquals('y', byteString[xs.length + ys.length - 1].toInt().toChar())
+ assertEquals('z', byteString[xs.length + ys.length].toInt().toChar())
+ assertEquals('z', byteString[xs.length + ys.length + zs.length - 1].toInt().toChar())
assertFailsWith<IndexOutOfBoundsException> {
byteString[-1]
}
diff --git a/okio/src/jvmTest/kotlin/okio/SocketTimeoutTest.kt b/okio/src/jvmTest/kotlin/okio/SocketTimeoutTest.kt
new file mode 100644
index 00000000..f38796f7
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/SocketTimeoutTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.EOFException
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import java.net.InetAddress
+import java.net.ServerSocket
+import java.net.Socket
+import java.net.SocketTimeoutException
+import java.util.concurrent.TimeUnit
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+
+class SocketTimeoutTest {
+ @Test
+ fun readWithoutTimeout() {
+ val socket = socket(ONE_MB, 0)
+ val source = socket.source().buffer()
+ source.timeout().timeout(5000, TimeUnit.MILLISECONDS)
+ source.require(ONE_MB.toLong())
+ socket.close()
+ }
+
+ @Test
+ fun readWithTimeout() {
+ val socket = socket(0, 0)
+ val source = socket.source().buffer()
+ source.timeout().timeout(250, TimeUnit.MILLISECONDS)
+ try {
+ source.require(ONE_MB.toLong())
+ fail()
+ } catch (expected: SocketTimeoutException) {
+ }
+ socket.close()
+ }
+
+ @Test
+ fun writeWithoutTimeout() {
+ val socket = socket(0, ONE_MB)
+ val sink: Sink = socket.sink().buffer()
+ sink.timeout().timeout(500, TimeUnit.MILLISECONDS)
+ val data = ByteArray(ONE_MB)
+ sink.write(Buffer().write(data), data.size.toLong())
+ sink.flush()
+ socket.close()
+ }
+
+ @Test
+ fun writeWithTimeout() {
+ val socket = socket(0, 0)
+ val sink = socket.sink()
+ sink.timeout().timeout(500, TimeUnit.MILLISECONDS)
+ val data = ByteArray(ONE_MB)
+ val start = System.nanoTime()
+ try {
+ sink.write(Buffer().write(data), data.size.toLong())
+ sink.flush()
+ fail()
+ } catch (expected: SocketTimeoutException) {
+ }
+ val elapsed = System.nanoTime() - start
+ socket.close()
+ assertTrue("elapsed: $elapsed", TimeUnit.NANOSECONDS.toMillis(elapsed) >= 500)
+ assertTrue("elapsed: $elapsed", TimeUnit.NANOSECONDS.toMillis(elapsed) <= 750)
+ }
+
+ companion object {
+ // The size of the socket buffers to use. Less than half the data transferred during tests to
+ // ensure send and receive buffers are flooded and any necessary blocking behavior takes place.
+ private const val SOCKET_BUFFER_SIZE = 256 * 1024
+ private const val ONE_MB = 1024 * 1024
+
+ /**
+ * Returns a socket that can read `readableByteCount` incoming bytes and
+ * will accept `writableByteCount` written bytes. The socket will idle
+ * for 5 seconds when the required data has been read and written.
+ */
+ fun socket(readableByteCount: Int, writableByteCount: Int): Socket {
+ val inetAddress = InetAddress.getByName("localhost")
+ val serverSocket = ServerSocket(0, 50, inetAddress)
+ serverSocket.reuseAddress = true
+ serverSocket.receiveBufferSize = SOCKET_BUFFER_SIZE
+ val peer: Thread = object : Thread("peer") {
+ override fun run() {
+ var socket: Socket? = null
+ try {
+ socket = serverSocket.accept()
+ socket.sendBufferSize = SOCKET_BUFFER_SIZE
+ writeFully(socket.getOutputStream(), readableByteCount)
+ readFully(socket.getInputStream(), writableByteCount)
+ sleep(5000) // Sleep 5 seconds so the peer can close the connection.
+ } catch (ignored: Exception) {
+ } finally {
+ try {
+ socket?.close()
+ } catch (ignored: IOException) {
+ }
+ }
+ }
+ }
+ peer.start()
+ val socket = Socket(serverSocket.inetAddress, serverSocket.localPort)
+ socket.receiveBufferSize = SOCKET_BUFFER_SIZE
+ socket.sendBufferSize = SOCKET_BUFFER_SIZE
+ return socket
+ }
+
+ private fun writeFully(out: OutputStream, byteCount: Int) {
+ out.write(ByteArray(byteCount))
+ out.flush()
+ }
+
+ private fun readFully(`in`: InputStream, byteCount: Int): ByteArray {
+ var count = 0
+ val result = ByteArray(byteCount)
+ while (count < byteCount) {
+ val read = `in`.read(result, count, result.size - count)
+ if (read == -1) throw EOFException()
+ count += read
+ }
+ return result
+ }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/TestUtil.kt b/okio/src/jvmTest/kotlin/okio/TestUtil.kt
index f9f61e62..703986e7 100644
--- a/okio/src/jvmTest/kotlin/okio/TestUtil.kt
+++ b/okio/src/jvmTest/kotlin/okio/TestUtil.kt
@@ -15,16 +15,17 @@
*/
package okio
-import okio.ByteString.Companion.encodeUtf8
-import org.junit.Assume
import java.io.IOException
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
+import java.util.Locale
import java.util.Random
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
+import okio.ByteString.Companion.encodeUtf8
+import org.junit.Assume
object TestUtil {
// Necessary to make an internal member visible to Java.
@@ -175,10 +176,10 @@ object TestUtil {
}
/** Serializes original to bytes, then deserializes those bytes and returns the result. */
+ // Assume serialization doesn't change types.
@Suppress("UNCHECKED_CAST")
@Throws(Exception::class)
@JvmStatic
- // Assume serialization doesn't change types.
fun <T : Serializable> reserialize(original: T): T {
val buffer = Buffer()
val out = ObjectOutputStream(buffer.outputStream())
@@ -298,5 +299,5 @@ object TestUtil {
return reversed.toShort()
}
- fun assumeNotWindows() = Assume.assumeFalse(System.getProperty("os.name").toLowerCase().contains("win"))
+ fun assumeNotWindows() = Assume.assumeFalse(System.getProperty("os.name").lowercase(Locale.getDefault()).contains("win"))
}
diff --git a/okio/src/jvmTest/kotlin/okio/ThrottlerTakeTest.kt b/okio/src/jvmTest/kotlin/okio/ThrottlerTakeTest.kt
index aab94b30..5349dd3d 100644
--- a/okio/src/jvmTest/kotlin/okio/ThrottlerTakeTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/ThrottlerTakeTest.kt
@@ -15,9 +15,9 @@
*/
package okio
+import java.util.concurrent.TimeUnit
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
-import java.util.concurrent.TimeUnit
class ThrottlerTakeTest {
private var nowNanos = 0L
diff --git a/okio/src/jvmTest/kotlin/okio/ThrottlerTest.kt b/okio/src/jvmTest/kotlin/okio/ThrottlerTest.kt
index 0b151794..baa487c1 100644
--- a/okio/src/jvmTest/kotlin/okio/ThrottlerTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/ThrottlerTest.kt
@@ -15,12 +15,11 @@
*/
package okio
+import kotlin.test.Ignore
import okio.TestUtil.randomSource
import org.junit.After
import org.junit.Before
import org.junit.Test
-import java.util.concurrent.Executors
-import kotlin.test.Ignore
@Ignore("These tests are flaky and fail on slower hardware, need to be improved")
class ThrottlerTest {
@@ -31,7 +30,7 @@ class ThrottlerTest {
private val throttlerSlow = Throttler()
private val threads = 4
- private val executorService = Executors.newFixedThreadPool(threads)
+ private val executorService = TestingExecutors.newExecutorService(threads)
private var stopwatch = Stopwatch()
@Before fun setup() {
diff --git a/okio/src/jvmTest/kotlin/okio/TimeoutFactory.kt b/okio/src/jvmTest/kotlin/okio/TimeoutFactory.kt
new file mode 100644
index 00000000..3a9cac7c
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/TimeoutFactory.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+enum class TimeoutFactory {
+ BASE {
+ override fun newTimeout() = Timeout()
+ },
+
+ FORWARDING {
+ override fun newTimeout() = ForwardingTimeout(BASE.newTimeout())
+ },
+
+ ASYNC {
+ override fun newTimeout() = AsyncTimeout()
+ },
+ ;
+
+ abstract fun newTimeout(): Timeout
+}
diff --git a/okio/src/jvmTest/kotlin/okio/TimeoutTest.kt b/okio/src/jvmTest/kotlin/okio/TimeoutTest.kt
new file mode 100644
index 00000000..2d5c7989
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/TimeoutTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.util.concurrent.TimeUnit
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.Timeout as JUnitTimeout
+
+class TimeoutTest {
+ @JvmField @Rule
+ val timeout = JUnitTimeout(5, TimeUnit.SECONDS)
+
+ private val executorService = TestingExecutors.newExecutorService(1)
+
+ @After
+ @Throws(Exception::class)
+ fun tearDown() {
+ executorService.shutdown()
+ }
+
+ @Test fun intersectWithReturnsAValue() {
+ val timeoutA = Timeout()
+ val timeoutB = Timeout()
+
+ val s = timeoutA.intersectWith(timeoutB) { "hello" }
+ assertEquals("hello", s)
+ }
+
+ @Test fun intersectWithPrefersSmallerTimeout() {
+ val timeoutA = Timeout()
+ timeoutA.timeout(smallerTimeoutNanos, TimeUnit.NANOSECONDS)
+
+ val timeoutB = Timeout()
+ timeoutB.timeout(biggerTimeoutNanos, TimeUnit.NANOSECONDS)
+
+ timeoutA.intersectWith(timeoutB) {
+ assertEquals(smallerTimeoutNanos, timeoutA.timeoutNanos())
+ assertEquals(biggerTimeoutNanos, timeoutB.timeoutNanos())
+ }
+ timeoutB.intersectWith(timeoutA) {
+ assertEquals(smallerTimeoutNanos, timeoutA.timeoutNanos())
+ assertEquals(smallerTimeoutNanos, timeoutB.timeoutNanos())
+ }
+ assertEquals(smallerTimeoutNanos, timeoutA.timeoutNanos())
+ assertEquals(biggerTimeoutNanos, timeoutB.timeoutNanos())
+ }
+
+ @Test fun intersectWithPrefersNonZeroTimeout() {
+ val timeoutA = Timeout()
+
+ val timeoutB = Timeout()
+ timeoutB.timeout(biggerTimeoutNanos, TimeUnit.NANOSECONDS)
+
+ timeoutA.intersectWith(timeoutB) {
+ assertEquals(biggerTimeoutNanos, timeoutA.timeoutNanos())
+ assertEquals(biggerTimeoutNanos, timeoutB.timeoutNanos())
+ }
+ timeoutB.intersectWith(timeoutA) {
+ assertEquals(0L, timeoutA.timeoutNanos())
+ assertEquals(biggerTimeoutNanos, timeoutB.timeoutNanos())
+ }
+ assertEquals(0L, timeoutA.timeoutNanos())
+ assertEquals(biggerTimeoutNanos, timeoutB.timeoutNanos())
+ }
+
+ @Test fun intersectWithPrefersSmallerDeadline() {
+ val timeoutA = Timeout()
+ timeoutA.deadlineNanoTime(smallerDeadlineNanos)
+
+ val timeoutB = Timeout()
+ timeoutB.deadlineNanoTime(biggerDeadlineNanos)
+
+ timeoutA.intersectWith(timeoutB) {
+ assertEquals(smallerDeadlineNanos, timeoutA.deadlineNanoTime())
+ assertEquals(biggerDeadlineNanos, timeoutB.deadlineNanoTime())
+ }
+ timeoutB.intersectWith(timeoutA) {
+ assertEquals(smallerDeadlineNanos, timeoutA.deadlineNanoTime())
+ assertEquals(smallerDeadlineNanos, timeoutB.deadlineNanoTime())
+ }
+ assertEquals(smallerDeadlineNanos, timeoutA.deadlineNanoTime())
+ assertEquals(biggerDeadlineNanos, timeoutB.deadlineNanoTime())
+ }
+
+ @Test fun intersectWithPrefersNonZeroDeadline() {
+ val timeoutA = Timeout()
+
+ val timeoutB = Timeout()
+ timeoutB.deadlineNanoTime(biggerDeadlineNanos)
+
+ timeoutA.intersectWith(timeoutB) {
+ assertEquals(biggerDeadlineNanos, timeoutA.deadlineNanoTime())
+ assertEquals(biggerDeadlineNanos, timeoutB.deadlineNanoTime())
+ }
+ timeoutB.intersectWith(timeoutA) {
+ assertFalse(timeoutA.hasDeadline())
+ assertEquals(biggerDeadlineNanos, timeoutB.deadlineNanoTime())
+ }
+ assertFalse(timeoutA.hasDeadline())
+ assertEquals(biggerDeadlineNanos, timeoutB.deadlineNanoTime())
+ }
+
+ companion object {
+ val smallerTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(500L)
+ val biggerTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(1500L)
+
+ val smallerDeadlineNanos = TimeUnit.MILLISECONDS.toNanos(500L)
+ val biggerDeadlineNanos = TimeUnit.MILLISECONDS.toNanos(1500L)
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/Utf8Test.kt b/okio/src/jvmTest/kotlin/okio/Utf8Test.kt
new file mode 100644
index 00000000..3657f136
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/Utf8Test.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.EOFException
+import kotlin.text.Charsets.UTF_8
+import okio.ByteString.Companion.decodeHex
+import okio.ByteString.Companion.of
+import okio.TestUtil.SEGMENT_SIZE
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+
+class Utf8Test {
+ @Test
+ fun oneByteCharacters() {
+ assertEncoded("00", 0x00) // Smallest 1-byte character.
+ assertEncoded("20", ' '.code)
+ assertEncoded("7e", '~'.code)
+ assertEncoded("7f", 0x7f) // Largest 1-byte character.
+ }
+
+ @Test
+ fun twoByteCharacters() {
+ assertEncoded("c280", 0x0080) // Smallest 2-byte character.
+ assertEncoded("c3bf", 0x00ff)
+ assertEncoded("c480", 0x0100)
+ assertEncoded("dfbf", 0x07ff) // Largest 2-byte character.
+ }
+
+ @Test
+ fun threeByteCharacters() {
+ assertEncoded("e0a080", 0x0800) // Smallest 3-byte character.
+ assertEncoded("e0bfbf", 0x0fff)
+ assertEncoded("e18080", 0x1000)
+ assertEncoded("e1bfbf", 0x1fff)
+ assertEncoded("ed8080", 0xd000)
+ assertEncoded("ed9fbf", 0xd7ff) // Largest character lower than the min surrogate.
+ assertEncoded("ee8080", 0xe000) // Smallest character greater than the max surrogate.
+ assertEncoded("eebfbf", 0xefff)
+ assertEncoded("ef8080", 0xf000)
+ assertEncoded("efbfbf", 0xffff) // Largest 3-byte character.
+ }
+
+ @Test
+ fun fourByteCharacters() {
+ assertEncoded("f0908080", 0x010000) // Smallest surrogate pair.
+ assertEncoded("f48fbfbf", 0x10ffff) // Largest code point expressible by UTF-16.
+ }
+
+ @Test
+ fun danglingHighSurrogate() {
+ assertStringEncoded("3f", "\ud800") // "?"
+ }
+
+ @Test
+ fun lowSurrogateWithoutHighSurrogate() {
+ assertStringEncoded("3f", "\udc00") // "?"
+ }
+
+ @Test
+ fun highSurrogateFollowedByNonSurrogate() {
+ assertStringEncoded("3f61", "\ud800\u0061") // "?a": Following character is too low.
+ assertStringEncoded("3fee8080", "\ud800\ue000") // "?\ue000": Following character is too high.
+ }
+
+ @Test
+ fun doubleLowSurrogate() {
+ assertStringEncoded("3f3f", "\udc00\udc00") // "??"
+ }
+
+ @Test
+ fun doubleHighSurrogate() {
+ assertStringEncoded("3f3f", "\ud800\ud800") // "??"
+ }
+
+ @Test
+ fun highSurrogateLowSurrogate() {
+ assertStringEncoded("3f3f", "\udc00\ud800") // "??"
+ }
+
+ @Test
+ fun multipleSegmentString() {
+ val a = "a".repeat(SEGMENT_SIZE + SEGMENT_SIZE + 1)
+ val encoded = Buffer().writeUtf8(a)
+ val expected = Buffer().write(a.toByteArray(UTF_8))
+ assertEquals(expected, encoded)
+ }
+
+ @Test
+ fun stringSpansSegments() {
+ val buffer = Buffer()
+ val a = "a".repeat(SEGMENT_SIZE - 1)
+ val b = "bb"
+ val c = "c".repeat(SEGMENT_SIZE - 1)
+ buffer.writeUtf8(a)
+ buffer.writeUtf8(b)
+ buffer.writeUtf8(c)
+ assertEquals(a + b + c, buffer.readUtf8())
+ }
+
+ @Test
+ fun readEmptyBufferThrowsEofException() {
+ val buffer = Buffer()
+ try {
+ buffer.readUtf8CodePoint()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readLeadingContinuationByteReturnsReplacementCharacter() {
+ val buffer = Buffer()
+ buffer.writeByte(0xbf)
+ assertEquals(REPLACEMENT_CODE_POINT.toLong(), buffer.readUtf8CodePoint().toLong())
+ assertTrue(buffer.exhausted())
+ }
+
+ @Test
+ fun readMissingContinuationBytesThrowsEofException() {
+ val buffer = Buffer()
+ buffer.writeByte(0xdf)
+ try {
+ buffer.readUtf8CodePoint()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ assertFalse(buffer.exhausted()) // Prefix byte wasn't consumed.
+ }
+
+ @Test
+ fun readTooLargeCodepointReturnsReplacementCharacter() {
+ // 5-byte and 6-byte code points are not supported.
+ val buffer = Buffer()
+ buffer.write("f888808080".decodeHex())
+ assertEquals(REPLACEMENT_CODE_POINT.toLong(), buffer.readUtf8CodePoint().toLong())
+ assertEquals(REPLACEMENT_CODE_POINT.toLong(), buffer.readUtf8CodePoint().toLong())
+ assertEquals(REPLACEMENT_CODE_POINT.toLong(), buffer.readUtf8CodePoint().toLong())
+ assertEquals(REPLACEMENT_CODE_POINT.toLong(), buffer.readUtf8CodePoint().toLong())
+ assertEquals(REPLACEMENT_CODE_POINT.toLong(), buffer.readUtf8CodePoint().toLong())
+ assertTrue(buffer.exhausted())
+ }
+
+ @Test
+ fun readNonContinuationBytesReturnsReplacementCharacter() {
+ // Use a non-continuation byte where a continuation byte is expected.
+ val buffer = Buffer()
+ buffer.write("df20".decodeHex())
+ assertEquals(REPLACEMENT_CODE_POINT.toLong(), buffer.readUtf8CodePoint().toLong())
+ assertEquals(0x20, buffer.readUtf8CodePoint().toLong()) // Non-continuation character not consumed.
+ assertTrue(buffer.exhausted())
+ }
+
+ @Test
+ fun readCodePointBeyondUnicodeMaximum() {
+ // A 4-byte encoding with data above the U+10ffff Unicode maximum.
+ val buffer = Buffer()
+ buffer.write("f4908080".decodeHex())
+ assertEquals(REPLACEMENT_CODE_POINT.toLong(), buffer.readUtf8CodePoint().toLong())
+ assertTrue(buffer.exhausted())
+ }
+
+ @Test
+ fun readSurrogateCodePoint() {
+ val buffer = Buffer()
+ buffer.write("eda080".decodeHex())
+ assertEquals(REPLACEMENT_CODE_POINT.toLong(), buffer.readUtf8CodePoint().toLong())
+ assertTrue(buffer.exhausted())
+ buffer.write("edbfbf".decodeHex())
+ assertEquals(REPLACEMENT_CODE_POINT.toLong(), buffer.readUtf8CodePoint().toLong())
+ assertTrue(buffer.exhausted())
+ }
+
+ @Test
+ fun readOverlongCodePoint() {
+ // Use 2 bytes to encode data that only needs 1 byte.
+ val buffer = Buffer()
+ buffer.write("c080".decodeHex())
+ assertEquals(REPLACEMENT_CODE_POINT.toLong(), buffer.readUtf8CodePoint().toLong())
+ assertTrue(buffer.exhausted())
+ }
+
+ @Test
+ fun writeSurrogateCodePoint() {
+ assertStringEncoded("ed9fbf", "\ud7ff") // Below lowest surrogate is okay.
+ assertStringEncoded("3f", "\ud800") // Lowest surrogate gets '?'.
+ assertStringEncoded("3f", "\udfff") // Highest surrogate gets '?'.
+ assertStringEncoded("ee8080", "\ue000") // Above highest surrogate is okay.
+ }
+
+ @Test
+ fun writeCodePointBeyondUnicodeMaximum() {
+ val buffer = Buffer()
+ try {
+ buffer.writeUtf8CodePoint(0x110000)
+ fail()
+ } catch (expected: IllegalArgumentException) {
+ assertEquals("Unexpected code point: 0x110000", expected.message)
+ }
+ }
+
+ @Test
+ fun size() {
+ assertEquals(0, "".utf8Size())
+ assertEquals(3, "abc".utf8Size())
+ assertEquals(16, "təˈranəˌsôr".utf8Size())
+ }
+
+ @Test
+ fun sizeWithBounds() {
+ assertEquals(0, "".utf8Size(0, 0))
+ assertEquals(0, "abc".utf8Size(0, 0))
+ assertEquals(1, "abc".utf8Size(1, 2))
+ assertEquals(2, "abc".utf8Size(0, 2))
+ assertEquals(3, "abc".utf8Size(0, 3))
+ assertEquals(16, "təˈranəˌsôr".utf8Size(0, 11))
+ assertEquals(5, "təˈranəˌsôr".utf8Size(3, 7))
+ }
+
+ @Test
+ fun sizeBoundsCheck() {
+ try {
+ null!!.utf8Size(0, 0)
+ fail()
+ } catch (expected: NullPointerException) {
+ }
+ try {
+ "abc".utf8Size(-1, 2)
+ fail()
+ } catch (expected: IllegalArgumentException) {
+ }
+ try {
+ "abc".utf8Size(2, 1)
+ fail()
+ } catch (expected: IllegalArgumentException) {
+ }
+ try {
+ "abc".utf8Size(1, 4)
+ fail()
+ } catch (expected: IllegalArgumentException) {
+ }
+ }
+
+ private fun assertEncoded(hex: String, vararg codePoints: Int) {
+ assertCodePointEncoded(hex, *codePoints)
+ assertCodePointDecoded(hex, *codePoints)
+ assertStringEncoded(hex, String(codePoints, 0, codePoints.size))
+ }
+
+ private fun assertCodePointEncoded(hex: String, vararg codePoints: Int) {
+ val buffer = Buffer()
+ for (codePoint in codePoints) {
+ buffer.writeUtf8CodePoint(codePoint)
+ }
+ assertEquals(buffer.readByteString(), hex.decodeHex())
+ }
+
+ private fun assertCodePointDecoded(hex: String, vararg codePoints: Int) {
+ val buffer = Buffer().write(hex.decodeHex())
+ for (codePoint in codePoints) {
+ assertEquals(codePoint.toLong(), buffer.readUtf8CodePoint().toLong())
+ }
+ assertTrue(buffer.exhausted())
+ }
+
+ private fun assertStringEncoded(hex: String, string: String) {
+ val expectedUtf8 = hex.decodeHex()
+
+ // Confirm our expectations are consistent with the platform.
+ val platformUtf8 = of(*string.toByteArray(charset("UTF-8")))
+ assertEquals(expectedUtf8, platformUtf8)
+
+ // Confirm our implementation matches those expectations.
+ val actualUtf8 = Buffer().writeUtf8(string).readByteString()
+ assertEquals(expectedUtf8, actualUtf8)
+
+ // Confirm we are consistent when writing one code point at a time.
+ val bufferUtf8 = Buffer()
+ var i = 0
+ while (i < string.length) {
+ val c = string.codePointAt(i)
+ bufferUtf8.writeUtf8CodePoint(c)
+ i += Character.charCount(c)
+ }
+ assertEquals(expectedUtf8, bufferUtf8.readByteString())
+
+ // Confirm we are consistent when measuring lengths.
+ assertEquals(expectedUtf8.size.toLong(), string.utf8Size())
+ assertEquals(expectedUtf8.size.toLong(), string.utf8Size(0, string.length))
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/WaitUntilNotifiedTest.kt b/okio/src/jvmTest/kotlin/okio/WaitUntilNotifiedTest.kt
new file mode 100644
index 00000000..44c6bbfd
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/WaitUntilNotifiedTest.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.InterruptedIOException
+import java.util.concurrent.TimeUnit
+import okio.TestUtil.assumeNotWindows
+import okio.TestingExecutors.newScheduledExecutorService
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class WaitUntilNotifiedTest(
+ factory: TimeoutFactory,
+) {
+ private val timeout = factory.newTimeout()
+ private val executorService = newScheduledExecutorService(0)
+
+ @After
+ fun tearDown() {
+ executorService.shutdown()
+ }
+
+ @Test
+ @Synchronized
+ fun notified() {
+ timeout.timeout(5000, TimeUnit.MILLISECONDS)
+ val start = now()
+ executorService.schedule(
+ {
+ synchronized(this@WaitUntilNotifiedTest) {
+ (this as Object).notify()
+ }
+ },
+ 1000,
+ TimeUnit.MILLISECONDS,
+ )
+ timeout.waitUntilNotified(this)
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ @Synchronized
+ fun timeout() {
+ assumeNotWindows()
+ timeout.timeout(1000, TimeUnit.MILLISECONDS)
+ val start = now()
+ try {
+ timeout.waitUntilNotified(this)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ @Synchronized
+ fun deadline() {
+ assumeNotWindows()
+ timeout.deadline(1000, TimeUnit.MILLISECONDS)
+ val start = now()
+ try {
+ timeout.waitUntilNotified(this)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ @Synchronized
+ fun deadlineBeforeTimeout() {
+ assumeNotWindows()
+ timeout.timeout(5000, TimeUnit.MILLISECONDS)
+ timeout.deadline(1000, TimeUnit.MILLISECONDS)
+ val start = now()
+ try {
+ timeout.waitUntilNotified(this)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ @Synchronized
+ fun timeoutBeforeDeadline() {
+ assumeNotWindows()
+ timeout.timeout(1000, TimeUnit.MILLISECONDS)
+ timeout.deadline(5000, TimeUnit.MILLISECONDS)
+ val start = now()
+ try {
+ timeout.waitUntilNotified(this)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ @Synchronized
+ fun deadlineAlreadyReached() {
+ assumeNotWindows()
+ timeout.deadlineNanoTime(System.nanoTime())
+ val start = now()
+ try {
+ timeout.waitUntilNotified(this)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(0.0, start)
+ }
+
+ @Test
+ @Synchronized
+ fun threadInterrupted() {
+ assumeNotWindows()
+ val start = now()
+ Thread.currentThread().interrupt()
+ try {
+ timeout.waitUntilNotified(this)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("interrupted", expected.message)
+ assertTrue(Thread.interrupted())
+ }
+ assertElapsed(0.0, start)
+ }
+
+ @Test
+ @Synchronized
+ fun threadInterruptedOnThrowIfReached() {
+ assumeNotWindows()
+ Thread.currentThread().interrupt()
+ try {
+ timeout.throwIfReached()
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("interrupted", expected.message)
+ assertTrue(Thread.interrupted())
+ }
+ }
+
+ @Test
+ @Synchronized
+ fun cancelBeforeWaitDoesNothing() {
+ assumeNotWindows()
+ timeout.timeout(1000, TimeUnit.MILLISECONDS)
+ timeout.cancel()
+ val start = now()
+ try {
+ timeout.waitUntilNotified(this)
+ fail()
+ } catch (expected: InterruptedIOException) {
+ assertEquals("timeout", expected.message)
+ }
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ @Synchronized
+ fun canceledTimeoutDoesNotThrowWhenNotNotifiedOnTime() {
+ timeout.timeout(1000, TimeUnit.MILLISECONDS)
+ timeout.cancelLater(500)
+
+ val start = now()
+ timeout.waitUntilNotified(this) // Returns early but doesn't throw.
+ assertElapsed(1000.0, start)
+ }
+
+ @Test
+ @Synchronized
+ fun multipleCancelsAreIdempotent() {
+ timeout.timeout(1000, TimeUnit.MILLISECONDS)
+ timeout.cancelLater(250)
+ timeout.cancelLater(500)
+ timeout.cancelLater(750)
+
+ val start = now()
+ timeout.waitUntilNotified(this) // Returns early but doesn't throw.
+ assertElapsed(1000.0, start)
+ }
+
+ /** Returns the nanotime in milliseconds as a double for measuring timeouts. */
+ private fun now(): Double {
+ return System.nanoTime() / 1000000.0
+ }
+
+ /**
+ * Fails the test unless the time from start until now is duration, accepting differences in
+ * -50..+450 milliseconds.
+ */
+ private fun assertElapsed(duration: Double, start: Double) {
+ assertEquals(duration, now() - start - 200.0, 250.0)
+ }
+
+ private fun Timeout.cancelLater(delay: Long) {
+ executorService.schedule(
+ {
+ cancel()
+ },
+ delay,
+ TimeUnit.MILLISECONDS,
+ )
+ }
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters(): List<Array<out Any?>> = TimeoutFactory.entries.map { arrayOf(it) }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/ZipBuilder.kt b/okio/src/jvmTest/kotlin/okio/ZipBuilder.kt
new file mode 100644
index 00000000..1abcc031
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/ZipBuilder.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import org.junit.Assume.assumeTrue
+
+/**
+ * Execute the `zip` command line program to create reference zip files for testing.
+ *
+ * Unfortunately the `zip` command line is limited in its ability to create exactly the zip files
+ * we want for testing. In particular, the only way it will create zip64 archives is if the input
+ * file is received from a UNIX pipe. (In such cases the file length is unknown in advance, and so
+ * the tool uses zip64 for the possibility of a very large file.) Files received from a pipe are
+ * always named `-`.
+ */
+class ZipBuilder(
+ private val directory: Path,
+) {
+ private val fileSystem = FileSystem.SYSTEM
+
+ private val entries = mutableListOf<Entry>()
+ private val options = mutableListOf<String>()
+ private var archiveComment: String = ""
+
+ @JvmOverloads
+ fun addEntry(
+ path: String,
+ content: String? = null,
+ directory: Boolean = false,
+ comment: String = "",
+ modifiedAt: String? = null,
+ accessedAt: String? = null,
+ zip64: Boolean = false,
+ ) = apply {
+ entries += Entry(path, content, directory, comment, modifiedAt, accessedAt, zip64)
+ }
+
+ fun addOption(option: String) = apply { options += option }
+
+ fun archiveComment(archiveComment: String) = apply { this.archiveComment = archiveComment }
+
+ fun build(): Path {
+ assumeTrue("ZipBuilder doesn't work on Windows", Path.DIRECTORY_SEPARATOR == "/")
+
+ val archive = directory / "${randomToken(16)}.zip"
+ val anyZip64 = entries.any { it.zip64 }
+
+ require(!anyZip64 || entries.size == 1) {
+ "ZipBuilder permits at most one zip64 entry"
+ }
+ require(!anyZip64 || archiveComment.isEmpty()) {
+ "Cannot combine archiveComment with zip64"
+ }
+
+ val promptForComments = entries.any { it.comment.isNotEmpty() }
+ if (promptForComments) {
+ options += "--entry-comments"
+ }
+ if (archiveComment.isNotEmpty()) {
+ options += "--archive-comment"
+ }
+
+ val command = mutableListOf<String>()
+ command += "zip"
+ command += options
+ command += archive.toString()
+
+ for (entry in entries) {
+ if (!entry.zip64) {
+ val absolutePath = directory / entry.path
+ fileSystem.createDirectories(absolutePath.parent!!)
+
+ if (entry.directory) {
+ fileSystem.createDirectories(absolutePath)
+ } else {
+ fileSystem.write(absolutePath) {
+ writeUtf8(entry.content!!)
+ }
+ }
+
+ if (entry.modifiedAt != null) {
+ touch("-m", absolutePath, entry.modifiedAt)
+ }
+ if (entry.accessedAt != null) {
+ touch("-a", absolutePath, entry.accessedAt)
+ }
+ }
+ command += entry.path
+ }
+
+ val process = ProcessBuilder()
+ .command(command)
+ .directory(directory.toFile())
+ .redirectOutput(ProcessBuilder.Redirect.INHERIT)
+ .redirectError(ProcessBuilder.Redirect.INHERIT)
+ .start()
+
+ process.outputStream.sink().buffer().use { sink ->
+ if (anyZip64) {
+ sink.writeUtf8(entries.single().content!!)
+ }
+ if (promptForComments) {
+ for (entry in entries) {
+ sink.writeUtf8(entry.comment)
+ sink.writeUtf8("\n")
+ sink.flush()
+ }
+ }
+ sink.writeUtf8(archiveComment)
+ }
+
+ val result = process.waitFor()
+ require(result == 0) { "process failed: $command" }
+
+ return archive
+ }
+
+ private fun touch(option: String, absolutePath: Path, date: String) {
+ val exitCode = ProcessBuilder()
+ .command("touch", option, "-t", date, absolutePath.toString())
+ .apply { environment()["TZ"] = "UTC" }
+ .start()
+ .waitFor()
+ require(exitCode == 0)
+ }
+
+ private class Entry(
+ val path: String,
+ val content: String?,
+ val directory: Boolean,
+ val comment: String,
+ val modifiedAt: String?,
+ val accessedAt: String?,
+ val zip64: Boolean,
+ ) {
+ init {
+ require(directory != (content != null)) { "must be a directory or have content" }
+ if (zip64) {
+ require(path == "-") { "zip64 file name must be '-'" }
+ require(comment == "") { "zip64 must not have comments" }
+ require(modifiedAt == null) { "zip64 must not have modifiedAt" }
+ }
+ }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/ZipFileSystemJavaTest.kt b/okio/src/jvmTest/kotlin/okio/ZipFileSystemJavaTest.kt
new file mode 100644
index 00000000..6d636145
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/ZipFileSystemJavaTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import okio.Path.Companion.toPath
+import org.assertj.core.api.Assertions
+import org.junit.Before
+import org.junit.Test
+
+class ZipFileSystemJavaTest {
+ private val fileSystem = FileSystem.SYSTEM
+ private val base = FileSystem.SYSTEM_TEMPORARY_DIRECTORY.div(randomToken(16))
+
+ @Before
+ fun setUp() {
+ fileSystem.createDirectory(base)
+ }
+
+ @Test
+ fun zipFileSystemApi() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("hello.txt", "Hello World")
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+ zipFileSystem.source("hello.txt".toPath(false)).buffer().use { source ->
+ val content = source.readUtf8()
+ Assertions.assertThat(content).isEqualTo("Hello World")
+ }
+ }
+}
diff --git a/okio/src/jvmTest/kotlin/okio/ZipFileSystemTest.kt b/okio/src/jvmTest/kotlin/okio/ZipFileSystemTest.kt
new file mode 100644
index 00000000..fd7aa46b
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/ZipFileSystemTest.kt
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.test.assertFailsWith
+import kotlinx.datetime.Instant
+import okio.ByteString.Companion.decodeHex
+import okio.ByteString.Companion.encodeUtf8
+import okio.Path.Companion.toPath
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Before
+import org.junit.Test
+
+class ZipFileSystemTest {
+ private val fileSystem = FileSystem.SYSTEM
+ private var base = FileSystem.SYSTEM_TEMPORARY_DIRECTORY / randomToken(16)
+
+ @Before
+ fun setUp() {
+ fileSystem.createDirectory(base)
+ }
+
+ @Test
+ fun emptyZip() {
+ // ZipBuilder cannot write empty zips.
+ val zipPath = base / "empty.zip"
+ fileSystem.write(zipPath) {
+ write("504b0506000000000000000000000000000000000000".decodeHex())
+ }
+
+ val zipFileSystem = fileSystem.openZip(zipPath)
+ assertThat(zipFileSystem.list("/".toPath())).isEmpty()
+ }
+
+ @Test
+ fun emptyZipWithPrependedData() {
+ // ZipBuilder cannot write empty zips.
+ val zipPath = base / "empty.zip"
+ fileSystem.write(zipPath) {
+ writeUtf8("Hello I'm junk data prepended to the ZIP!")
+ write("504b0506000000000000000000000000000000000000".decodeHex())
+ }
+
+ val zipFileSystem = fileSystem.openZip(zipPath)
+ assertThat(zipFileSystem.list("/".toPath())).isEmpty()
+ }
+
+ @Test
+ fun zipWithFiles() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("hello.txt", "Hello World")
+ .addEntry("directory/subdirectory/child.txt", "Another file!")
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ assertThat(zipFileSystem.read("hello.txt".toPath()) { readUtf8() })
+ .isEqualTo("Hello World")
+
+ assertThat(zipFileSystem.read("directory/subdirectory/child.txt".toPath()) { readUtf8() })
+ .isEqualTo("Another file!")
+
+ assertThat(zipFileSystem.list("/".toPath()))
+ .hasSameElementsAs(listOf("/hello.txt".toPath(), "/directory".toPath()))
+ assertThat(zipFileSystem.list("/directory".toPath()))
+ .containsExactly("/directory/subdirectory".toPath())
+ assertThat(zipFileSystem.list("/directory/subdirectory".toPath()))
+ .containsExactly("/directory/subdirectory/child.txt".toPath())
+ }
+
+ /**
+ * Note that the zip tool does not compress files that don't benefit from it. Examples above like
+ * 'Hello World' are stored, not deflated.
+ */
+ @Test
+ fun zipWithDeflate() {
+ val content = "Android\n".repeat(1000)
+ val zipPath = ZipBuilder(base)
+ .addEntry("a.txt", content)
+ .addOption("--compression-method")
+ .addOption("deflate")
+ .build()
+ assertThat(fileSystem.metadata(zipPath).size).isLessThan(content.length.toLong())
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ assertThat(zipFileSystem.read("a.txt".toPath()) { readUtf8() })
+ .isEqualTo(content)
+ }
+
+ @Test
+ fun zipWithStore() {
+ val content = "Android\n".repeat(1000)
+ val zipPath = ZipBuilder(base)
+ .addEntry("a.txt", content)
+ .addOption("--compression-method")
+ .addOption("store")
+ .build()
+ assertThat(fileSystem.metadata(zipPath).size).isGreaterThan(content.length.toLong())
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ assertThat(zipFileSystem.read("a.txt".toPath()) { readUtf8() })
+ .isEqualTo(content)
+ }
+
+ /**
+ * Confirm we can read zip files that have file comments, even if these comments are not exposed
+ * in the public API.
+ */
+ @Test
+ fun zipWithFileComments() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("a.txt", "Android", comment = "A is for Android")
+ .addEntry("b.txt", "Banana", comment = "B or not to Be")
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ assertThat(zipFileSystem.read("a.txt".toPath()) { readUtf8() })
+ .isEqualTo("Android")
+
+ assertThat(zipFileSystem.read("b.txt".toPath()) { readUtf8() })
+ .isEqualTo("Banana")
+ }
+
+ @Test
+ fun zipWithFileModifiedDate() {
+ val zipPath = ZipBuilder(base)
+ .addEntry(
+ path = "a.txt",
+ content = "Android",
+ modifiedAt = "200102030405.06",
+ accessedAt = "200102030405.07",
+ )
+ .addEntry(
+ path = "b.txt",
+ content = "Banana",
+ modifiedAt = "200908070605.04",
+ accessedAt = "200908070605.03",
+ )
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ zipFileSystem.metadata("a.txt".toPath())
+ .apply {
+ assertThat(isRegularFile).isTrue()
+ assertThat(isDirectory).isFalse()
+ assertThat(size).isEqualTo(7L)
+ assertThat(createdAtMillis).isNull()
+ assertThat(lastModifiedAtMillis).isEqualTo("2001-02-03T04:05:06Z".toEpochMillis())
+ assertThat(lastAccessedAtMillis).isEqualTo("2001-02-03T04:05:07Z".toEpochMillis())
+ }
+
+ zipFileSystem.metadata("b.txt".toPath())
+ .apply {
+ assertThat(isRegularFile).isTrue()
+ assertThat(isDirectory).isFalse()
+ assertThat(size).isEqualTo(6L)
+ assertThat(createdAtMillis).isNull()
+ assertThat(lastModifiedAtMillis).isEqualTo("2009-08-07T06:05:04Z".toEpochMillis())
+ assertThat(lastAccessedAtMillis).isEqualTo("2009-08-07T06:05:03Z".toEpochMillis())
+ }
+ }
+
+ /** Confirm we suffer UNIX limitations on our date format. */
+ @Test
+ fun zipWithFileOutOfBoundsModifiedDate() {
+ val zipPath = ZipBuilder(base)
+ .addEntry(
+ path = "a.txt",
+ content = "Android",
+ modifiedAt = "196912310000.00",
+ accessedAt = "196912300000.00",
+ )
+ .addEntry(
+ path = "b.txt",
+ content = "Banana",
+ modifiedAt = "203801190314.07", // Last UNIX date representable in 31 bits.
+ accessedAt = "203801190314.08", // Overflows!
+ )
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ println(Instant.fromEpochMilliseconds(-2147483648000L))
+
+ zipFileSystem.metadata("a.txt".toPath())
+ .apply {
+ assertThat(isRegularFile).isTrue()
+ assertThat(isDirectory).isFalse()
+ assertThat(size).isEqualTo(7L)
+ assertThat(createdAtMillis).isNull()
+ assertThat(lastModifiedAtMillis).isEqualTo("1969-12-31T00:00:00Z".toEpochMillis())
+ assertThat(lastAccessedAtMillis).isEqualTo("1969-12-30T00:00:00Z".toEpochMillis())
+ }
+
+ // Greater than the upper bound wraps around.
+ zipFileSystem.metadata("b.txt".toPath())
+ .apply {
+ assertThat(isRegularFile).isTrue()
+ assertThat(isDirectory).isFalse()
+ assertThat(size).isEqualTo(6L)
+ assertThat(createdAtMillis).isNull()
+ assertThat(lastModifiedAtMillis).isEqualTo("2038-01-19T03:14:07Z".toEpochMillis())
+ assertThat(lastAccessedAtMillis).isEqualTo("1901-12-13T20:45:52Z".toEpochMillis())
+ }
+ }
+
+ /**
+ * Directories are optional in the zip file. But if we want metadata on them they must be stored.
+ * Note that this test adds the directories last; otherwise adding child files to them will cause
+ * their modified at times to change.
+ */
+ @Test
+ fun zipWithDirectoryModifiedDate() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("a/a.txt", "Android")
+ .addEntry(
+ path = "a",
+ directory = true,
+ modifiedAt = "200102030405.06",
+ accessedAt = "200102030405.07",
+ )
+ .addEntry("b/b.txt", "Android")
+ .addEntry(
+ path = "b",
+ directory = true,
+ modifiedAt = "200908070605.04",
+ accessedAt = "200908070605.03",
+ )
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ zipFileSystem.metadata("a".toPath())
+ .apply {
+ assertThat(isRegularFile).isFalse()
+ assertThat(isDirectory).isTrue()
+ assertThat(size).isNull()
+ assertThat(createdAtMillis).isNull()
+ assertThat(lastModifiedAtMillis).isEqualTo("2001-02-03T04:05:06Z".toEpochMillis())
+ assertThat(lastAccessedAtMillis).isEqualTo("2001-02-03T04:05:07Z".toEpochMillis())
+ }
+ assertThat(zipFileSystem.list("a".toPath())).containsExactly("/a/a.txt".toPath())
+
+ zipFileSystem.metadata("b".toPath())
+ .apply {
+ assertThat(isRegularFile).isFalse()
+ assertThat(isDirectory).isTrue()
+ assertThat(size).isNull()
+ assertThat(createdAtMillis).isNull()
+ assertThat(lastModifiedAtMillis).isEqualTo("2009-08-07T06:05:04Z".toEpochMillis())
+ assertThat(lastAccessedAtMillis).isEqualTo("2009-08-07T06:05:03Z".toEpochMillis())
+ }
+ assertThat(zipFileSystem.list("b".toPath())).containsExactly("/b/b.txt".toPath())
+ }
+
+ @Test
+ fun zipWithModifiedDate() {
+ val zipPath = ZipBuilder(base)
+ .addEntry(
+ "a/a.txt",
+ modifiedAt = "197001010001.00",
+ accessedAt = "197001010002.00",
+ content = "Android",
+ )
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ zipFileSystem.metadata("a/a.txt".toPath())
+ .apply {
+ assertThat(createdAtMillis).isNull()
+ assertThat(lastModifiedAtMillis).isEqualTo("1970-01-01T00:01:00Z".toEpochMillis())
+ assertThat(lastAccessedAtMillis).isEqualTo("1970-01-01T00:02:00Z".toEpochMillis())
+ }
+ }
+
+ /** Build a very small zip file with just a single empty directory. */
+ @Test
+ fun zipWithEmptyDirectory() {
+ val zipPath = ZipBuilder(base)
+ .addEntry(
+ path = "a",
+ directory = true,
+ modifiedAt = "200102030405.06",
+ accessedAt = "200102030405.07",
+ )
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ zipFileSystem.metadata("a".toPath())
+ .apply {
+ assertThat(isRegularFile).isFalse()
+ assertThat(isDirectory).isTrue()
+ assertThat(size).isNull()
+ assertThat(createdAtMillis).isNull()
+ assertThat(lastModifiedAtMillis).isEqualTo("2001-02-03T04:05:06Z".toEpochMillis())
+ assertThat(lastAccessedAtMillis).isEqualTo("2001-02-03T04:05:07Z".toEpochMillis())
+ }
+ assertThat(zipFileSystem.list("a".toPath())).isEmpty()
+ }
+
+ /**
+ * The `--no-dir-entries` option causes the zip file to omit the directories from the encoded
+ * file. Our implementation synthesizes these missing directories automatically.
+ */
+ @Test
+ fun zipWithSyntheticDirectory() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("a/a.txt", "Android")
+ .addEntry("a", directory = true)
+ .addEntry("b/b.txt", "Android")
+ .addEntry("b", directory = true)
+ .addOption("--no-dir-entries")
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ zipFileSystem.metadata("a".toPath())
+ .apply {
+ assertThat(isRegularFile).isFalse()
+ assertThat(isDirectory).isTrue()
+ assertThat(size).isNull()
+ assertThat(createdAtMillis).isNull()
+ assertThat(lastModifiedAtMillis).isNull()
+ assertThat(lastAccessedAtMillis).isNull()
+ }
+ assertThat(zipFileSystem.list("a".toPath())).containsExactly("/a/a.txt".toPath())
+
+ zipFileSystem.metadata("b".toPath())
+ .apply {
+ assertThat(isRegularFile).isFalse()
+ assertThat(isDirectory).isTrue()
+ assertThat(size).isNull()
+ assertThat(createdAtMillis).isNull()
+ assertThat(lastModifiedAtMillis).isNull()
+ assertThat(lastAccessedAtMillis).isNull()
+ }
+ assertThat(zipFileSystem.list("b".toPath())).containsExactly("/b/b.txt".toPath())
+ }
+
+ /**
+ * Force a file to be encoded with zip64 metadata. We use a pipe to force the zip command to
+ * create a zip64 archive; otherwise we'd need to add a very large file to get this format.
+ */
+ @Test
+ fun zip64() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("-", "Android", zip64 = true)
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ assertThat(zipFileSystem.read("-".toPath()) { readUtf8() })
+ .isEqualTo("Android")
+ }
+
+ /**
+ * Confirm we can read zip files with a full-archive comment, even if this comment is not surfaced
+ * in our API.
+ */
+ @Test
+ fun zipWithArchiveComment() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("a.txt", "Android")
+ .archiveComment("this comment applies to the entire archive")
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ assertThat(zipFileSystem.read("a.txt".toPath()) { readUtf8() })
+ .isEqualTo("Android")
+ }
+
+ @Test
+ fun cannotReadZipWithSpanning() {
+ // Spanned archives must be at least 64 KiB.
+ val largeFile = randomToken(length = 128 * 1024)
+ val zipPath = ZipBuilder(base)
+ .addEntry("large_file.txt", largeFile)
+ .addOption("--split-size")
+ .addOption("64k")
+ .build()
+ assertFailsWith<IOException> {
+ fileSystem.openZip(zipPath)
+ }
+ }
+
+ @Test
+ fun cannotReadZipWithEncryption() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("a.txt", "Android")
+ .addOption("--password")
+ .addOption("secret")
+ .build()
+ assertFailsWith<IOException> {
+ fileSystem.openZip(zipPath)
+ }
+ }
+
+ @Test
+ fun zipTooShort() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("a.txt", "Android")
+ .build()
+
+ val prefix = fileSystem.read(zipPath) { readByteString(20) }
+ fileSystem.write(zipPath) { write(prefix) }
+
+ assertFailsWith<IOException> {
+ fileSystem.openZip(zipPath)
+ }
+ }
+
+ /**
+ * The zip format permits multiple files with the same names. For example,
+ * `kotlin-gradle-plugin-1.5.20.jar` contains two copies of
+ * `META-INF/kotlin-gradle-statistics.kotlin_module`.
+ *
+ * We used to crash on duplicates, but they are common in practice so now we prefer the last
+ * entry. This behavior is consistent with both [java.util.zip.ZipFile] and
+ * [java.nio.file.FileSystem].
+ */
+ @Test
+ fun filesOverlap() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("hello.txt", "This is the first hello.txt")
+ .addEntry("xxxxx.xxx", "This is the second hello.txt")
+ .build()
+ val original = fileSystem.read(zipPath) { readByteString() }
+ val rewritten = original.replaceAll("xxxxx.xxx".encodeUtf8(), "hello.txt".encodeUtf8())
+ fileSystem.write(zipPath) { write(rewritten) }
+
+ val zipFileSystem = fileSystem.openZip(zipPath)
+ assertThat(zipFileSystem.read("hello.txt".toPath()) { readUtf8() })
+ .isEqualTo("This is the second hello.txt")
+ assertThat(zipFileSystem.list("/".toPath()))
+ .containsExactly("/hello.txt".toPath())
+ }
+
+ @Test
+ fun canonicalizationValid() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("hello.txt", "Hello World")
+ .addEntry("directory/child.txt", "Another file!")
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ assertThat(zipFileSystem.canonicalize("/".toPath())).isEqualTo("/".toPath())
+ assertThat(zipFileSystem.canonicalize(".".toPath())).isEqualTo("/".toPath())
+ assertThat(zipFileSystem.canonicalize("not/a/path/../../..".toPath())).isEqualTo("/".toPath())
+ assertThat(zipFileSystem.canonicalize("hello.txt".toPath())).isEqualTo("/hello.txt".toPath())
+ assertThat(zipFileSystem.canonicalize("stuff/../hello.txt".toPath())).isEqualTo("/hello.txt".toPath())
+ assertThat(zipFileSystem.canonicalize("directory".toPath())).isEqualTo("/directory".toPath())
+ assertThat(zipFileSystem.canonicalize("directory/whevs/..".toPath())).isEqualTo("/directory".toPath())
+ assertThat(zipFileSystem.canonicalize("directory/child.txt".toPath())).isEqualTo("/directory/child.txt".toPath())
+ assertThat(zipFileSystem.canonicalize("directory/whevs/../child.txt".toPath())).isEqualTo("/directory/child.txt".toPath())
+ }
+
+ @Test
+ fun canonicalizationInvalidThrows() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("hello.txt", "Hello World")
+ .addEntry("directory/child.txt", "Another file!")
+ .build()
+ val zipFileSystem = fileSystem.openZip(zipPath)
+
+ assertFailsWith<FileNotFoundException> {
+ zipFileSystem.canonicalize("not/a/path".toPath())
+ }
+ }
+}
+
+private fun ByteString.replaceAll(a: ByteString, b: ByteString): ByteString {
+ val buffer = Buffer()
+ buffer.write(this)
+ buffer.replace(a, b)
+ return buffer.readByteString()
+}
+
+private fun Buffer.replace(a: ByteString, b: ByteString) {
+ val result = Buffer()
+ while (!exhausted()) {
+ val index = indexOf(a)
+ if (index == -1L) {
+ result.writeAll(this)
+ } else {
+ result.write(this, index)
+ result.write(b)
+ skip(a.size.toLong())
+ }
+ }
+ writeAll(result)
+}
+
+/** Decodes this ISO8601 time string. */
+fun String.toEpochMillis() = Instant.parse(this).toEpochMilliseconds()
diff --git a/okio/src/jvmTest/java/okio/internal/HmacTest.kt b/okio/src/jvmTest/kotlin/okio/internal/HmacTest.kt
index 0c5a7f43..01666d17 100644
--- a/okio/src/jvmTest/java/okio/internal/HmacTest.kt
+++ b/okio/src/jvmTest/kotlin/okio/internal/HmacTest.kt
@@ -15,14 +15,14 @@
*/
package okio.internal
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+import kotlin.random.Random
import okio.ByteString
-import org.junit.Assert.assertArrayEquals
+import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import javax.crypto.Mac
-import javax.crypto.spec.SecretKeySpec
-import kotlin.random.Random
/**
* Check the [Hmac] implementation against the reference [Mac] JVM implementation.
@@ -44,7 +44,7 @@ class HmacTest(val parameters: Parameters) {
Parameters(
algorithm,
keySize,
- dataSize
+ dataSize,
)
}
}
@@ -72,7 +72,7 @@ class HmacTest(val parameters: Parameters) {
mac.update(bytes)
val hmacValue = mac.digest()
- assertArrayEquals(expected, hmacValue)
+ Assert.assertArrayEquals(expected, hmacValue)
}
@Test
@@ -82,13 +82,13 @@ class HmacTest(val parameters: Parameters) {
}
val hmacValue = mac.digest()
- assertArrayEquals(expected, hmacValue)
+ Assert.assertArrayEquals(expected, hmacValue)
}
data class Parameters(
val algorithm: Algorithm,
val keySize: Int,
- val dataSize: Int
+ val dataSize: Int,
) {
val algorithmName
get() = algorithm.algorithmName
@@ -98,7 +98,7 @@ class HmacTest(val parameters: Parameters) {
enum class Algorithm(
val algorithmName: String,
- internal val HmacFactory: (key: ByteString) -> Hmac
+ internal val HmacFactory: (key: ByteString) -> Hmac,
) {
SHA_1("HmacSha1", Hmac.Companion::sha1),
SHA_256("HmacSha256", Hmac.Companion::sha256),
diff --git a/okio/src/jvmTest/kotlin/okio/internal/ResourceFileSystemTest.kt b/okio/src/jvmTest/kotlin/okio/internal/ResourceFileSystemTest.kt
new file mode 100644
index 00000000..13c63158
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/internal/ResourceFileSystemTest.kt
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.internal
+
+import java.net.URL
+import java.net.URLClassLoader
+import java.util.Enumeration
+import kotlin.reflect.KClass
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.fail
+import okio.BufferedSource
+import okio.ByteString
+import okio.FileNotFoundException
+import okio.FileSystem
+import okio.ForwardingFileSystem
+import okio.IOException
+import okio.Path
+import okio.Path.Companion.toPath
+import okio.ZipBuilder
+import okio.randomToken
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+class ResourceFileSystemTest {
+ private val fileSystem = FileSystem.RESOURCES as ResourceFileSystem
+ private var base = FileSystem.SYSTEM_TEMPORARY_DIRECTORY / randomToken(16)
+
+ @Test
+ fun testResourceA() {
+ val path = "okio/resourcefilesystem/a.txt".toPath()
+
+ val metadata = fileSystem.metadataOrNull(path)!!
+
+ assertThat(metadata.size).isEqualTo(1L)
+ assertThat(metadata.isRegularFile).isTrue()
+ assertThat(metadata.isDirectory).isFalse()
+
+ val content = fileSystem.read(path) { readUtf8() }
+ assertThat(fileSystem.metadata(path).isRegularFile).isTrue()
+
+ assertThat(content).isEqualTo("a")
+ }
+
+ @Test
+ fun testResourceB() {
+ val path = "okio/resourcefilesystem/b/b.txt".toPath()
+
+ val metadata = fileSystem.metadataOrNull(path)!!
+
+ assertThat(metadata.size).isEqualTo(3L)
+ assertThat(metadata.isRegularFile).isTrue()
+ assertThat(metadata.isDirectory).isFalse()
+
+ val content = fileSystem.read(path) { readUtf8() }
+ assertThat(fileSystem.metadata(path).isRegularFile).isTrue()
+
+ assertThat(content).isEqualTo("b/b")
+ }
+
+ @Test
+ fun testSingleArchive() {
+ val zipPath = ZipBuilder(base)
+ .addEntry("hello.txt", "Hello World")
+ .addEntry("directory/subdirectory/child.txt", "Another file!")
+ .addEntry("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n")
+ .build()
+ val resourceFileSystem = ResourceFileSystem(
+ classLoader = URLClassLoader(arrayOf(zipPath.toFile().toURI().toURL()), null),
+ indexEagerly = false,
+ )
+
+ assertThat(resourceFileSystem.read("hello.txt".toPath()) { readUtf8() })
+ .isEqualTo("Hello World")
+ assertThat(
+ resourceFileSystem.metadata("hello.txt".toPath()).isRegularFile,
+ ).isTrue()
+
+ assertThat(resourceFileSystem.read("directory/subdirectory/child.txt".toPath()) { readUtf8() })
+ .isEqualTo("Another file!")
+ assertThat(
+ resourceFileSystem.metadata("directory/subdirectory/child.txt".toPath()).isRegularFile,
+ ).isTrue()
+
+ assertThat(resourceFileSystem.list("/".toPath()))
+ .hasSameElementsAs(listOf("/META-INF".toPath(), "/hello.txt".toPath(), "/directory".toPath()))
+ assertThat(resourceFileSystem.list("/directory".toPath()))
+ .containsExactly("/directory/subdirectory".toPath())
+ assertThat(resourceFileSystem.list("/directory/subdirectory".toPath()))
+ .containsExactly("/directory/subdirectory/child.txt".toPath())
+
+ val metadata = resourceFileSystem.metadata(".".toPath())
+ assertThat(metadata.isDirectory).isTrue()
+ }
+
+ @Test
+ fun testDirectoryAndJarOverlap() {
+ val filesAPath = base / "filesA"
+ FileSystem.SYSTEM.createDirectories(filesAPath / "colors")
+ FileSystem.SYSTEM.write(filesAPath / "colors" / "red.txt") { writeUtf8("Apples are red") }
+ FileSystem.SYSTEM.write(filesAPath / "colors" / "green.txt") { writeUtf8("Grass is green") }
+ val zipBPath = ZipBuilder(base)
+ .addEntry("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n")
+ .addEntry("colors/blue.txt", "The sky is blue")
+ .addEntry("colors/green.txt", "Limes are green")
+ .build()
+
+ val resourceFileSystem = ResourceFileSystem(
+ classLoader = URLClassLoader(
+ arrayOf(
+ filesAPath.toFile().toURI().toURL(),
+ zipBPath.toFile().toURI().toURL(),
+ ),
+ null,
+ ),
+ indexEagerly = false,
+ )
+
+ assertThat(resourceFileSystem.read("/colors/red.txt".toPath()) { readUtf8() })
+ .isEqualTo("Apples are red")
+ assertThat(resourceFileSystem.read("/colors/green.txt".toPath()) { readUtf8() })
+ .isEqualTo("Grass is green")
+ assertThat(resourceFileSystem.read("/colors/blue.txt".toPath()) { readUtf8() })
+ .isEqualTo("The sky is blue")
+
+ assertThat(resourceFileSystem.metadata("/colors/red.txt".toPath()).isRegularFile).isTrue()
+ assertThat(resourceFileSystem.metadata("/colors/green.txt".toPath()).isRegularFile).isTrue()
+ assertThat(resourceFileSystem.metadata("/colors/blue.txt".toPath()).isRegularFile).isTrue()
+
+ assertThat(resourceFileSystem.list("/".toPath()))
+ .hasSameElementsAs(listOf("/META-INF".toPath(), "/colors".toPath()))
+ assertThat(resourceFileSystem.list("/colors".toPath())).hasSameElementsAs(
+ listOf(
+ "/colors/red.txt".toPath(),
+ "/colors/green.txt".toPath(),
+ "/colors/blue.txt".toPath(),
+ ),
+ )
+
+ assertThat(resourceFileSystem.metadata("/".toPath()).isDirectory).isTrue()
+ assertThat(resourceFileSystem.metadata("/colors".toPath()).isDirectory).isTrue()
+ }
+
+ @Test
+ fun testDirectoryAndDirectoryOverlap() {
+ val filesAPath = base / "filesA"
+ FileSystem.SYSTEM.createDirectories(filesAPath / "colors")
+ FileSystem.SYSTEM.write(filesAPath / "colors" / "red.txt") { writeUtf8("Apples are red") }
+ FileSystem.SYSTEM.write(filesAPath / "colors" / "green.txt") { writeUtf8("Grass is green") }
+ val filesBPath = base / "filesB"
+ FileSystem.SYSTEM.createDirectories(filesBPath / "colors")
+ FileSystem.SYSTEM.write(filesBPath / "colors" / "blue.txt") { writeUtf8("The sky is blue") }
+ FileSystem.SYSTEM.write(filesBPath / "colors" / "green.txt") { writeUtf8("Limes are green") }
+
+ val resourceFileSystem = ResourceFileSystem(
+ classLoader = URLClassLoader(
+ arrayOf(
+ filesAPath.toFile().toURI().toURL(),
+ filesBPath.toFile().toURI().toURL(),
+ ),
+ null,
+ ),
+ indexEagerly = false,
+ )
+
+ assertThat(resourceFileSystem.read("/colors/red.txt".toPath()) { readUtf8() })
+ .isEqualTo("Apples are red")
+ assertThat(resourceFileSystem.read("/colors/green.txt".toPath()) { readUtf8() })
+ .isEqualTo("Grass is green")
+ assertThat(resourceFileSystem.read("/colors/blue.txt".toPath()) { readUtf8() })
+ .isEqualTo("The sky is blue")
+
+ assertThat(resourceFileSystem.metadata("/colors/red.txt".toPath()).isRegularFile).isTrue()
+ assertThat(resourceFileSystem.metadata("/colors/green.txt".toPath()).isRegularFile).isTrue()
+ assertThat(resourceFileSystem.metadata("/colors/blue.txt".toPath()).isRegularFile).isTrue()
+
+ assertThat(resourceFileSystem.list("/".toPath()))
+ .hasSameElementsAs(listOf("/colors".toPath()))
+ assertThat(resourceFileSystem.list("/colors".toPath())).hasSameElementsAs(
+ listOf(
+ "/colors/red.txt".toPath(),
+ "/colors/green.txt".toPath(),
+ "/colors/blue.txt".toPath(),
+ ),
+ )
+
+ assertThat(resourceFileSystem.metadata("/".toPath()).isDirectory).isTrue()
+ assertThat(resourceFileSystem.metadata("/colors".toPath()).isDirectory).isTrue()
+ }
+
+ @Test
+ fun testJarAndJarOverlap() {
+ val zipAPath = ZipBuilder(base)
+ .addEntry("colors/red.txt", "Apples are red")
+ .addEntry("colors/green.txt", "Grass is green")
+ .addEntry("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n")
+ .build()
+ val zipBPath = ZipBuilder(base)
+ .addEntry("colors/blue.txt", "The sky is blue")
+ .addEntry("colors/green.txt", "Limes are green")
+ .addEntry("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n")
+ .build()
+ val resourceFileSystem = ResourceFileSystem(
+ classLoader = URLClassLoader(
+ arrayOf(
+ zipAPath.toFile().toURI().toURL(),
+ zipBPath.toFile().toURI().toURL(),
+ ),
+ null,
+ ),
+ indexEagerly = false,
+ )
+
+ assertThat(resourceFileSystem.read("/colors/red.txt".toPath()) { readUtf8() })
+ .isEqualTo("Apples are red")
+ assertThat(resourceFileSystem.read("/colors/green.txt".toPath()) { readUtf8() })
+ .isEqualTo("Grass is green")
+ assertThat(resourceFileSystem.read("/colors/blue.txt".toPath()) { readUtf8() })
+ .isEqualTo("The sky is blue")
+
+ assertThat(resourceFileSystem.metadata("/colors/red.txt".toPath()).isRegularFile).isTrue()
+ assertThat(resourceFileSystem.metadata("/colors/green.txt".toPath()).isRegularFile).isTrue()
+ assertThat(resourceFileSystem.metadata("/colors/blue.txt".toPath()).isRegularFile).isTrue()
+
+ assertThat(resourceFileSystem.list("/".toPath()))
+ .hasSameElementsAs(listOf("/META-INF".toPath(), "/colors".toPath()))
+ assertThat(resourceFileSystem.list("/colors".toPath())).hasSameElementsAs(
+ listOf(
+ "/colors/red.txt".toPath(),
+ "/colors/green.txt".toPath(),
+ "/colors/blue.txt".toPath(),
+ ),
+ )
+
+ assertThat(resourceFileSystem.metadata("/".toPath()).isDirectory).isTrue()
+ assertThat(resourceFileSystem.metadata("/colors".toPath()).isDirectory).isTrue()
+ }
+
+ @Test
+ fun testResourceMissing() {
+ val path = "okio/resourcefilesystem/b/c.txt".toPath()
+
+ assertThat(fileSystem.metadataOrNull(path)).isNull()
+
+ try {
+ fileSystem.read(path) { readUtf8() }
+ fail()
+ } catch (ioe: IOException) {
+ assertThat(ioe.message).isEqualTo("file not found: okio/resourcefilesystem/b/c.txt")
+ }
+ }
+
+ @Test
+ fun testProjectIsListable() {
+ val path = "okio/resourcefilesystem/b/".toPath()
+
+ val metadata = fileSystem.metadataOrNull(path)!!
+
+ assertThat(metadata.isDirectory).isTrue()
+ assertThat(metadata.createdAtMillis).isGreaterThan(1L)
+
+ assertThat(fileSystem.list(path).map { it.name }).containsExactly("b.txt")
+ }
+
+ @Test
+ fun testResourceFromJar() {
+ val path = "LICENSE-junit.txt".toPath()
+
+ val metadata = fileSystem.metadataOrNull(path)!!
+
+ assertThat(metadata.size).isGreaterThan(10000L)
+ assertThat(metadata.isRegularFile).isTrue()
+ assertThat(metadata.isDirectory).isFalse()
+
+ val content = fileSystem.read(path) { readUtf8Line() }
+
+ assertThat(content).isEqualTo("JUnit")
+ }
+
+ @Test
+ fun testClassFilesOmittedFromJar() {
+ assertThat(fileSystem.list("/org/junit/rules".toPath())).isEmpty()
+ assertThat(fileSystem.metadataOrNull("/org/junit/Test.class".toPath())).isNull()
+ }
+
+ @Test
+ fun testClassFilesOmittedFromDirectory() {
+ val filesPath = base / "files"
+ val packagePath = filesPath / "com" / "example" / "project"
+ FileSystem.SYSTEM.createDirectories(packagePath)
+ FileSystem.SYSTEM.write(packagePath / "Hello.class") { writeUtf8("cafebabe") }
+
+ val resourceFileSystem = ResourceFileSystem(
+ classLoader = URLClassLoader(
+ arrayOf(filesPath.toFile().toURI().toURL()),
+ null,
+ ),
+ indexEagerly = false,
+ )
+
+ assertThat(resourceFileSystem.list("/com/example/project".toPath())).isEmpty()
+ assertThat(resourceFileSystem.metadataOrNull("/com/example/project/Hello.class".toPath()))
+ .isNull()
+ assertFailsWith<FileNotFoundException> {
+ resourceFileSystem.source("/com/example/project/Hello.class".toPath())
+ }
+ }
+
+ @Test
+ fun testDirectoryFromJar() {
+ val path = "org/junit/".toPath()
+
+ val metadata = fileSystem.metadataOrNull(path)
+ assertThat(metadata?.isDirectory).isTrue()
+
+ val files = fileSystem.list(path).map { it.name }
+ assertThat(files).contains("matchers", "rules")
+ assertThat(files.filter { it.endsWith(".class") }).isEmpty()
+ }
+
+ @Test
+ fun packagePath() {
+ val path = ByteString::class.java.`package`.toPath()
+
+ assertThat((path / "a.txt").toString())
+ .isEqualTo("okio${Path.DIRECTORY_SEPARATOR}a.txt")
+ }
+
+ @Test
+ fun classResource() {
+ val path = ByteString::class.packagePath!!
+
+ assertThat((path / "a.txt").toString())
+ .isEqualTo("okio${Path.DIRECTORY_SEPARATOR}a.txt")
+ }
+
+ /**
+ * Confirm that class loaders aren't accessed until the file system is used. This should save
+ * resources so that zip files aren't decoded if they're unused.
+ */
+ @Test
+ fun testIndexLazily() {
+ val classLoader = object : ClassLoader() {
+ override fun findResources(name: String?): Enumeration<URL> {
+ throw Exception("finding a resource")
+ }
+ }
+
+ val resourceFileSystem = ResourceFileSystem(
+ classLoader = classLoader,
+ indexEagerly = false,
+ )
+
+ assertThat(
+ assertFailsWith<Exception> {
+ resourceFileSystem.list("/".toPath())
+ },
+ ).hasMessage("finding a resource")
+ }
+
+ @Test
+ fun testIndexEagerly() {
+ val classLoader = object : ClassLoader() {
+ override fun findResources(name: String?): Enumeration<URL> {
+ throw Exception("finding a resource")
+ }
+ }
+
+ assertThat(
+ assertFailsWith<Exception> {
+ ResourceFileSystem(
+ classLoader = classLoader,
+ indexEagerly = true,
+ )
+ },
+ ).hasMessage("finding a resource")
+ }
+
+ /** Confirm we can read individual files without triggering indexing. */
+ @Test
+ fun testSourceDoesntTriggerIndexing() {
+ val processedPaths = mutableSetOf<Path>()
+ val recordingFileSystem = object : ForwardingFileSystem(FileSystem.SYSTEM) {
+ override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path {
+ processedPaths += path
+ return super.onPathParameter(path, functionName, parameterName)
+ }
+ }
+
+ val zipAPath = ZipBuilder(base)
+ .addEntry("colors/red.txt", "Apples are red")
+ .addEntry("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n")
+ .build()
+ val zipBPath = ZipBuilder(base)
+ .addEntry("colors/blue.txt", "The sky is blue")
+ .addEntry("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n")
+ .build()
+ val resourceFileSystem = ResourceFileSystem(
+ classLoader = URLClassLoader(
+ arrayOf(
+ zipAPath.toFile().toURI().toURL(),
+ zipBPath.toFile().toURI().toURL(),
+ ),
+ null,
+ ),
+ indexEagerly = false,
+ systemFileSystem = recordingFileSystem,
+ )
+
+ // Reading paths with source() or read() doesn't index zips.
+ assertThat(
+ resourceFileSystem.read("/colors/red.txt".toPath()) { readUtf8() },
+ ).isEqualTo("Apples are red")
+ assertThat(
+ resourceFileSystem.read("/colors/blue.txt".toPath()) { readUtf8() },
+ ).isEqualTo("The sky is blue")
+ assertThat(processedPaths).isEmpty()
+
+ // Calling list() does though.
+ assertThat(resourceFileSystem.list("/colors".toPath())).containsExactlyInAnyOrder(
+ "/colors/red.txt".toPath(),
+ "/colors/blue.txt".toPath(),
+ )
+ assertThat(processedPaths).containsExactlyInAnyOrder(
+ zipAPath,
+ zipBPath,
+ )
+ }
+
+ /**
+ * Our resource file system uses [URLClassLoader] internally, which means we need to go back and
+ * forth between [File], [URL], and [URI] models for component paths. This is a big hazard for
+ * escaping special characters and it's likely that some file paths won't survive the round trip!
+ */
+ @Test
+ fun fileNameWithSpaceInPath() {
+ val zipPath = ZipBuilder(base / "space in directory name")
+ .addEntry("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n")
+ .addEntry("hello.txt", "Hello World")
+ .build()
+ val resourceFileSystem = ResourceFileSystem(
+ classLoader = URLClassLoader(arrayOf(zipPath.toFile().toURI().toURL()), null),
+ indexEagerly = false,
+ )
+
+ assertThat(resourceFileSystem.read("hello.txt".toPath()) { readUtf8() })
+ .isEqualTo("Hello World")
+ assertThat(resourceFileSystem.metadata("hello.txt".toPath())).isNotNull()
+ }
+
+ @Test
+ fun missingResourceSilentlyIgnored() {
+ val zipAPath = ZipBuilder(base)
+ .addEntry("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n")
+ .addEntry("hello.txt", "Hello World")
+ .build()
+ val resourceFileSystem = ResourceFileSystem(
+ classLoader = URLClassLoader(
+ arrayOf(
+ zipAPath.toFile().toURI().toURL(),
+ (base / "missing.zip").toFile().toURI().toURL(),
+ ),
+ null,
+ ),
+ indexEagerly = false,
+ )
+
+ assertThat(resourceFileSystem.read("hello.txt".toPath()) { readUtf8() })
+ .isEqualTo("Hello World")
+ }
+
+ @Test
+ fun listSpecialCharacterNamedFiles() {
+ val path = "okio/resourcefilesystem/non-ascii".toPath()
+
+ assertThat(fileSystem.listRecursively(path).toList()).containsExactly(
+ "/okio/resourcefilesystem/non-ascii/ギリシア神話".toPath(),
+ "/okio/resourcefilesystem/non-ascii/ギリシア神話/Ἰλιάς".toPath(),
+ )
+
+ val content = fileSystem.read(
+ "/okio/resourcefilesystem/non-ascii/ギリシア神話/Ἰλιάς".toPath(),
+ BufferedSource::readUtf8,
+ )
+ assertEquals("Chante, ô déesse, le courroux du Péléide Achille,\n", content)
+ }
+
+ private fun Package.toPath(): Path = name.replace(".", "/").toPath()
+
+ private val KClass<*>.packagePath: Path?
+ get() = qualifiedName?.replace(".", "/")?.toPath()?.parent
+}
diff --git a/okio/src/jvmTest/resources/okio/resourcefilesystem/a.txt b/okio/src/jvmTest/resources/okio/resourcefilesystem/a.txt
new file mode 100644
index 00000000..2e65efe2
--- /dev/null
+++ b/okio/src/jvmTest/resources/okio/resourcefilesystem/a.txt
@@ -0,0 +1 @@
+a \ No newline at end of file
diff --git a/okio/src/jvmTest/resources/okio/resourcefilesystem/b/b.txt b/okio/src/jvmTest/resources/okio/resourcefilesystem/b/b.txt
new file mode 100644
index 00000000..09038752
--- /dev/null
+++ b/okio/src/jvmTest/resources/okio/resourcefilesystem/b/b.txt
@@ -0,0 +1 @@
+b/b \ No newline at end of file
diff --git a/okio/src/jvmTest/resources/okio/resourcefilesystem/non-ascii/ギリシア神話/Ἰλιάς b/okio/src/jvmTest/resources/okio/resourcefilesystem/non-ascii/ギリシア神話/Ἰλιάς
new file mode 100644
index 00000000..3c4ecbae
--- /dev/null
+++ b/okio/src/jvmTest/resources/okio/resourcefilesystem/non-ascii/ギリシア神話/Ἰλιάς
@@ -0,0 +1 @@
+Chante, ô déesse, le courroux du Péléide Achille,
diff --git a/okio/src/linuxMain/kotlin/okio/LinuxPosixVariant.kt b/okio/src/linuxMain/kotlin/okio/LinuxPosixVariant.kt
new file mode 100644
index 00000000..168d0ddc
--- /dev/null
+++ b/okio/src/linuxMain/kotlin/okio/LinuxPosixVariant.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.cinterop.alloc
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.ptr
+import platform.posix.ENOENT
+import platform.posix.S_IFDIR
+import platform.posix.S_IFMT
+import platform.posix.S_IFREG
+import platform.posix.errno
+import platform.posix.lstat
+import platform.posix.stat
+
+internal actual fun PosixFileSystem.variantMetadataOrNull(path: Path): FileMetadata? {
+ return memScoped {
+ val stat = alloc<stat>()
+ if (lstat(path.toString(), stat.ptr) != 0) {
+ if (errno == ENOENT) return null
+ throw errnoToIOException(errno)
+ }
+ return@memScoped FileMetadata(
+ isRegularFile = stat.st_mode.toInt() and S_IFMT == S_IFREG,
+ isDirectory = stat.st_mode.toInt() and S_IFMT == S_IFDIR,
+ symlinkTarget = symlinkTarget(stat, path),
+ size = stat.st_size,
+ createdAtMillis = stat.st_ctim.epochMillis,
+ lastModifiedAtMillis = stat.st_mtim.epochMillis,
+ lastAccessedAtMillis = stat.st_atim.epochMillis,
+ )
+ }
+}
diff --git a/okio/src/mingwX64Main/kotlin/okio/Windows.kt b/okio/src/mingwX64Main/kotlin/okio/Windows.kt
new file mode 100644
index 00000000..18370b9d
--- /dev/null
+++ b/okio/src/mingwX64Main/kotlin/okio/Windows.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.cinterop.ByteVarOf
+import kotlinx.cinterop.allocArray
+import kotlinx.cinterop.memScoped
+import platform.windows.DWORD
+import platform.windows.ERROR_FILE_NOT_FOUND
+import platform.windows.ERROR_PATH_NOT_FOUND
+import platform.windows.FORMAT_MESSAGE_FROM_SYSTEM
+import platform.windows.FORMAT_MESSAGE_IGNORE_INSERTS
+import platform.windows.FormatMessageA
+import platform.windows.GetLastError
+import platform.windows.LANG_NEUTRAL
+import platform.windows.SUBLANG_DEFAULT
+
+internal fun lastErrorToIOException(): IOException {
+ val lastError = GetLastError()
+ return when (lastError.toInt()) {
+ ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND -> FileNotFoundException(lastErrorString(lastError))
+ else -> IOException(lastErrorString(lastError))
+ }
+}
+
+internal fun lastErrorString(lastError: DWORD): String {
+ memScoped {
+ val messageMaxSize = 2048
+ val message = allocArray<ByteVarOf<Byte>>(messageMaxSize)
+ FormatMessageA(
+ dwFlags = (FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS).toUInt(),
+ lpSource = null,
+ dwMessageId = lastError,
+ dwLanguageId = (SUBLANG_DEFAULT * 1024 + LANG_NEUTRAL).toUInt(), // MAKELANGID macro.
+ lpBuffer = message,
+ nSize = messageMaxSize.toUInt(),
+ Arguments = null,
+ )
+ return Buffer().writeNullTerminated(message).readUtf8().trim()
+ }
+}
diff --git a/okio/src/mingwX64Main/kotlin/okio/WindowsFileHandle.kt b/okio/src/mingwX64Main/kotlin/okio/WindowsFileHandle.kt
new file mode 100644
index 00000000..79cfb7f9
--- /dev/null
+++ b/okio/src/mingwX64Main/kotlin/okio/WindowsFileHandle.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.cinterop.CValuesRef
+import kotlinx.cinterop.IntVar
+import kotlinx.cinterop.addressOf
+import kotlinx.cinterop.alloc
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.ptr
+import kotlinx.cinterop.usePinned
+import kotlinx.cinterop.value
+import platform.windows.CloseHandle
+import platform.windows.ERROR_HANDLE_EOF
+import platform.windows.FILE_BEGIN
+import platform.windows.FlushFileBuffers
+import platform.windows.GetFileSizeEx
+import platform.windows.GetLastError
+import platform.windows.HANDLE
+import platform.windows.LARGE_INTEGER
+import platform.windows.ReadFile
+import platform.windows.SetEndOfFile
+import platform.windows.SetFilePointer
+import platform.windows.WriteFile
+import platform.windows._OVERLAPPED
+
+internal class WindowsFileHandle(
+ readWrite: Boolean,
+ private val file: HANDLE?,
+) : FileHandle(readWrite) {
+ override fun protectedSize(): Long {
+ memScoped {
+ val result = alloc<LARGE_INTEGER>()
+ if (GetFileSizeEx(file, result.ptr) == 0) {
+ throw lastErrorToIOException()
+ }
+ return result.toLong()
+ }
+ }
+
+ override fun protectedRead(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ): Int {
+ val bytesRead = if (array.isNotEmpty()) {
+ array.usePinned { pinned ->
+ variantPread(pinned.addressOf(arrayOffset), byteCount, fileOffset)
+ }
+ } else {
+ 0
+ }
+ if (bytesRead == 0) return -1
+ return bytesRead
+ }
+
+ fun variantPread(
+ target: CValuesRef<*>,
+ byteCount: Int,
+ offset: Long,
+ ): Int {
+ memScoped {
+ val overlapped = alloc<_OVERLAPPED>()
+ overlapped.Offset = offset.toUInt()
+ overlapped.OffsetHigh = (offset ushr 32).toUInt()
+ val readFileResult = ReadFile(
+ hFile = file,
+ lpBuffer = target.getPointer(this),
+ nNumberOfBytesToRead = byteCount.toUInt(),
+ lpNumberOfBytesRead = null,
+ lpOverlapped = overlapped.ptr,
+ )
+ if (readFileResult == 0 && GetLastError().toInt() != ERROR_HANDLE_EOF) {
+ throw lastErrorToIOException()
+ }
+ return overlapped.InternalHigh.toInt()
+ }
+ }
+
+ override fun protectedWrite(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ) {
+ val bytesWritten = if (array.isNotEmpty()) {
+ array.usePinned { pinned ->
+ variantPwrite(pinned.addressOf(arrayOffset), byteCount, fileOffset)
+ }
+ } else {
+ 0
+ }
+ if (bytesWritten != byteCount) throw IOException("bytesWritten=$bytesWritten")
+ }
+
+ fun variantPwrite(
+ source: CValuesRef<*>,
+ byteCount: Int,
+ offset: Long,
+ ): Int {
+ memScoped {
+ val overlapped = alloc<_OVERLAPPED>()
+ overlapped.Offset = offset.toUInt()
+ overlapped.OffsetHigh = (offset ushr 32).toUInt()
+ val writeFileResult = WriteFile(
+ hFile = file,
+ lpBuffer = source.getPointer(this),
+ nNumberOfBytesToWrite = byteCount.toUInt(),
+ lpNumberOfBytesWritten = null,
+ lpOverlapped = overlapped.ptr,
+ )
+ if (writeFileResult == 0) {
+ throw lastErrorToIOException()
+ }
+ return overlapped.InternalHigh.toInt()
+ }
+ }
+
+ override fun protectedFlush() {
+ if (FlushFileBuffers(file) == 0) {
+ throw lastErrorToIOException()
+ }
+ }
+
+ override fun protectedResize(size: Long) {
+ memScoped {
+ val distanceToMoveHigh = alloc<IntVar>()
+ distanceToMoveHigh.value = (size ushr 32).toInt()
+ val movePointerResult = SetFilePointer(
+ hFile = file,
+ lDistanceToMove = size.toInt(),
+ lpDistanceToMoveHigh = distanceToMoveHigh.ptr,
+ dwMoveMethod = FILE_BEGIN.toUInt(),
+ )
+ if (movePointerResult == 0U) {
+ throw lastErrorToIOException()
+ }
+ if (SetEndOfFile(file) == 0) {
+ throw lastErrorToIOException()
+ }
+ }
+ }
+
+ override fun protectedClose() {
+ if (CloseHandle(file) == 0) {
+ throw lastErrorToIOException()
+ }
+ }
+
+ private fun LARGE_INTEGER.toLong(): Long {
+ return (HighPart.toLong() shl 32) + (LowPart.toLong() and 0xffffffffL)
+ }
+}
diff --git a/okio/src/mingwX64Main/kotlin/okio/WindowsPosixVariant.kt b/okio/src/mingwX64Main/kotlin/okio/WindowsPosixVariant.kt
new file mode 100644
index 00000000..21dd41a0
--- /dev/null
+++ b/okio/src/mingwX64Main/kotlin/okio/WindowsPosixVariant.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.alloc
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.ptr
+import kotlinx.cinterop.toKString
+import okio.Path.Companion.toPath
+import platform.posix.EACCES
+import platform.posix.ENOENT
+import platform.posix.FILE
+import platform.posix.PATH_MAX
+import platform.posix.S_IFDIR
+import platform.posix.S_IFMT
+import platform.posix.S_IFREG
+import platform.posix._fullpath
+import platform.posix._stat64
+import platform.posix.errno
+import platform.posix.fopen
+import platform.posix.free
+import platform.posix.getenv
+import platform.posix.mkdir
+import platform.posix.remove
+import platform.posix.rmdir
+import platform.windows.CREATE_NEW
+import platform.windows.CreateFileA
+import platform.windows.FILE_ATTRIBUTE_NORMAL
+import platform.windows.FILE_SHARE_WRITE
+import platform.windows.GENERIC_READ
+import platform.windows.GENERIC_WRITE
+import platform.windows.INVALID_HANDLE_VALUE
+import platform.windows.MOVEFILE_REPLACE_EXISTING
+import platform.windows.MoveFileExA
+import platform.windows.OPEN_ALWAYS
+import platform.windows.OPEN_EXISTING
+
+internal actual val PLATFORM_TEMPORARY_DIRECTORY: Path
+ get() {
+ // Windows' built-in APIs check the TEMP, TMP, and USERPROFILE environment variables in order.
+ // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppatha?redirectedfrom=MSDN
+ val temp = getenv("TEMP")
+ if (temp != null) return temp.toKString().toPath()
+
+ val tmp = getenv("TMP")
+ if (tmp != null) return tmp.toKString().toPath()
+
+ val userProfile = getenv("USERPROFILE")
+ if (userProfile != null) return userProfile.toKString().toPath()
+
+ return "\\Windows\\TEMP".toPath()
+ }
+
+internal actual val PLATFORM_DIRECTORY_SEPARATOR = "\\"
+
+internal actual fun PosixFileSystem.variantDelete(path: Path, mustExist: Boolean) {
+ val pathString = path.toString()
+
+ if (remove(pathString) == 0) return
+
+ // If remove failed with EACCES, it might be a directory. Try that.
+ if (errno == EACCES) {
+ if (rmdir(pathString) == 0) return
+ }
+ if (errno == ENOENT) {
+ if (mustExist) {
+ throw FileNotFoundException("no such file: $path")
+ } else {
+ return
+ }
+ }
+
+ throw errnoToIOException(EACCES)
+}
+
+internal actual fun PosixFileSystem.variantMkdir(dir: Path): Int {
+ return mkdir(dir.toString())
+}
+
+internal actual fun PosixFileSystem.variantCanonicalize(path: Path): Path {
+ // Note that _fullpath() returns normally if the file doesn't exist.
+ val fullpath = _fullpath(null, path.toString(), PATH_MAX.toULong())
+ ?: throw errnoToIOException(errno)
+ try {
+ val pathString = Buffer().writeNullTerminated(fullpath).readUtf8()
+ if (platform.posix.access(pathString, 0) != 0 && errno == ENOENT) {
+ throw FileNotFoundException("no such file")
+ }
+ return pathString.toPath()
+ } finally {
+ free(fullpath)
+ }
+}
+
+internal actual fun PosixFileSystem.variantMetadataOrNull(path: Path): FileMetadata? {
+ return memScoped {
+ val stat = alloc<_stat64>()
+ if (_stat64(path.toString(), stat.ptr) != 0) {
+ if (errno == ENOENT) return null
+ throw errnoToIOException(errno)
+ }
+ return@memScoped FileMetadata(
+ isRegularFile = stat.st_mode.toInt() and S_IFMT == S_IFREG,
+ isDirectory = stat.st_mode.toInt() and S_IFMT == S_IFDIR,
+ symlinkTarget = null,
+ size = stat.st_size,
+ createdAtMillis = stat.st_ctime * 1000L,
+ lastModifiedAtMillis = stat.st_mtime * 1000L,
+ lastAccessedAtMillis = stat.st_atime * 1000L,
+ )
+ }
+}
+
+internal actual fun PosixFileSystem.variantMove(source: Path, target: Path) {
+ if (MoveFileExA(source.toString(), target.toString(), MOVEFILE_REPLACE_EXISTING.toUInt()) == 0) {
+ throw lastErrorToIOException()
+ }
+}
+
+internal actual fun PosixFileSystem.variantSource(file: Path): Source {
+ val openFile: CPointer<FILE> = fopen(file.toString(), "rb")
+ ?: throw errnoToIOException(errno)
+ return FileSource(openFile)
+}
+
+internal actual fun PosixFileSystem.variantSink(file: Path, mustCreate: Boolean): Sink {
+ // We're non-atomically checking file existence because Windows errors if we use the `x` flag along with `w`.
+ if (mustCreate && exists(file)) throw IOException("$file already exists.")
+ val openFile: CPointer<FILE> = fopen(file.toString(), "wb")
+ ?: throw errnoToIOException(errno)
+ return FileSink(openFile)
+}
+
+internal actual fun PosixFileSystem.variantAppendingSink(file: Path, mustExist: Boolean): Sink {
+ // There is a `r+` flag which we could have used to force existence of [file] but this flag
+ // doesn't allow opening for appending, and we don't currently have a way to move the cursor to
+ // the end of the file. We are then forcing existence non-atomically.
+ if (mustExist && !exists(file)) throw IOException("$file doesn't exist.")
+ val openFile: CPointer<FILE> = fopen(file.toString(), "ab")
+ ?: throw errnoToIOException(errno)
+ return FileSink(openFile)
+}
+
+internal actual fun PosixFileSystem.variantOpenReadOnly(file: Path): FileHandle {
+ val openFile = CreateFileA(
+ lpFileName = file.toString(),
+ dwDesiredAccess = GENERIC_READ,
+ dwShareMode = FILE_SHARE_WRITE.toUInt(),
+ lpSecurityAttributes = null,
+ dwCreationDisposition = OPEN_EXISTING.toUInt(),
+ dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL.toUInt(),
+ hTemplateFile = null,
+ )
+ if (openFile == INVALID_HANDLE_VALUE) {
+ throw lastErrorToIOException()
+ }
+ return WindowsFileHandle(false, openFile)
+}
+
+internal actual fun PosixFileSystem.variantOpenReadWrite(
+ file: Path,
+ mustCreate: Boolean,
+ mustExist: Boolean,
+): FileHandle {
+ require(!mustCreate || !mustExist) {
+ "Cannot require mustCreate and mustExist at the same time."
+ }
+
+ val creationDisposition = when {
+ mustCreate -> CREATE_NEW.toUInt()
+ mustExist -> OPEN_EXISTING.toUInt()
+ else -> OPEN_ALWAYS.toUInt()
+ }
+
+ val openFile = CreateFileA(
+ lpFileName = file.toString(),
+ dwDesiredAccess = GENERIC_READ or GENERIC_WRITE.toUInt(),
+ dwShareMode = FILE_SHARE_WRITE.toUInt(),
+ lpSecurityAttributes = null,
+ dwCreationDisposition = creationDisposition,
+ dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL.toUInt(),
+ hTemplateFile = null,
+ )
+ if (openFile == INVALID_HANDLE_VALUE) {
+ throw lastErrorToIOException()
+ }
+ return WindowsFileHandle(true, openFile)
+}
+
+internal actual fun PosixFileSystem.variantCreateSymlink(source: Path, target: Path) {
+ throw IOException("Not supported")
+}
diff --git a/okio/src/nativeMain/kotlin/okio/Cinterop.kt b/okio/src/nativeMain/kotlin/okio/Cinterop.kt
new file mode 100644
index 00000000..a9f3e74f
--- /dev/null
+++ b/okio/src/nativeMain/kotlin/okio/Cinterop.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.cinterop.ByteVarOf
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.get
+import kotlinx.cinterop.set
+import platform.posix.ENOENT
+import platform.posix.strerror
+
+internal fun Buffer.writeNullTerminated(bytes: CPointer<ByteVarOf<Byte>>): Buffer = apply {
+ var pos = 0
+ while (true) {
+ val byte = bytes[pos++].toInt()
+ if (byte == 0) {
+ break
+ } else {
+ writeByte(byte)
+ }
+ }
+}
+
+internal fun Buffer.write(
+ source: CPointer<ByteVarOf<Byte>>,
+ offset: Int = 0,
+ byteCount: Int,
+): Buffer = apply {
+ for (i in offset until offset + byteCount) {
+ writeByte(source[i].toInt())
+ }
+}
+
+internal fun Buffer.read(
+ sink: CPointer<ByteVarOf<Byte>>,
+ offset: Int = 0,
+ byteCount: Int,
+): Buffer = apply {
+ for (i in offset until offset + byteCount) {
+ sink[i] = readByte()
+ }
+}
+
+internal fun errnoToIOException(errno: Int): IOException {
+ val message = strerror(errno)
+ val messageString = if (message != null) {
+ Buffer().writeNullTerminated(message).readUtf8()
+ } else {
+ "errno: $errno"
+ }
+ return when (errno) {
+ ENOENT -> FileNotFoundException(messageString)
+ else -> IOException(messageString)
+ }
+}
diff --git a/okio/src/nativeMain/kotlin/okio/FileSink.kt b/okio/src/nativeMain/kotlin/okio/FileSink.kt
new file mode 100644
index 00000000..4b514267
--- /dev/null
+++ b/okio/src/nativeMain/kotlin/okio/FileSink.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.addressOf
+import kotlinx.cinterop.usePinned
+import okio.Buffer.UnsafeCursor
+import platform.posix.FILE
+import platform.posix.errno
+import platform.posix.fclose
+import platform.posix.fflush
+
+/** Writes bytes to a file as a sink. */
+internal class FileSink(
+ private val file: CPointer<FILE>,
+) : Sink {
+ private val unsafeCursor = UnsafeCursor()
+ private var closed = false
+
+ override fun write(
+ source: Buffer,
+ byteCount: Long,
+ ) {
+ require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
+ require(source.size >= byteCount) { "source.size=${source.size} < byteCount=$byteCount" }
+ check(!closed) { "closed" }
+
+ var byteCount = byteCount
+ while (byteCount > 0) {
+ // Get the first segment, which we will read a contiguous range of bytes from.
+ val cursor = source.readUnsafe(unsafeCursor)
+ val segmentReadableByteCount = cursor.next()
+ val attemptCount = minOf(byteCount, segmentReadableByteCount.toLong()).toInt()
+
+ // Copy bytes from that segment into the file.
+ val bytesWritten = cursor.data!!.usePinned { pinned ->
+ variantFwrite(pinned.addressOf(cursor.start), attemptCount.toUInt(), file).toLong()
+ }
+
+ // Consume the bytes from the segment.
+ cursor.close()
+ source.skip(bytesWritten)
+ byteCount -= bytesWritten
+
+ // If the write was shorter than expected, some I/O failed.
+ if (bytesWritten < attemptCount) {
+ throw errnoToIOException(errno)
+ }
+ }
+ }
+
+ override fun flush() {
+ if (fflush(file) != 0) {
+ throw errnoToIOException(errno)
+ }
+ }
+
+ override fun timeout(): Timeout = Timeout.NONE
+
+ override fun close() {
+ if (closed) return
+ closed = true
+ if (fclose(file) != 0) {
+ throw errnoToIOException(errno)
+ }
+ }
+}
diff --git a/okio/src/nativeMain/kotlin/okio/FileSource.kt b/okio/src/nativeMain/kotlin/okio/FileSource.kt
new file mode 100644
index 00000000..6e9965f4
--- /dev/null
+++ b/okio/src/nativeMain/kotlin/okio/FileSource.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.addressOf
+import kotlinx.cinterop.usePinned
+import okio.Buffer.UnsafeCursor
+import platform.posix.FILE
+import platform.posix.errno
+import platform.posix.fclose
+import platform.posix.feof
+import platform.posix.ferror
+
+/** Reads the bytes of a file as a source. */
+internal class FileSource(
+ private val file: CPointer<FILE>,
+) : Source {
+ private val unsafeCursor = UnsafeCursor()
+ private var closed = false
+
+ override fun read(
+ sink: Buffer,
+ byteCount: Long,
+ ): Long {
+ require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
+ check(!closed) { "closed" }
+ val sinkInitialSize = sink.size
+
+ // Request a writable segment in `sink`. We request at least 1024 bytes, unless the request is
+ // for smaller than that, in which case we request only that many bytes.
+ val cursor = sink.readAndWriteUnsafe(unsafeCursor)
+ val addedCapacityCount = cursor.expandBuffer(minByteCount = minOf(byteCount, 1024L).toInt())
+
+ // Now that we have a writable segment, figure out how many bytes to read. This is the smaller
+ // of the user's requested byte count, and the segment's writable capacity.
+ val attemptCount = minOf(byteCount, addedCapacityCount)
+
+ // Copy bytes from the file to the segment.
+ val bytesRead = cursor.data!!.usePinned { pinned ->
+ variantFread(pinned.addressOf(cursor.start), attemptCount.toUInt(), file).toLong()
+ }
+
+ // Remove new capacity that was added but not used.
+ cursor.resizeBuffer(sinkInitialSize + bytesRead)
+ cursor.close()
+
+ return when {
+ bytesRead == attemptCount -> bytesRead
+ feof(file) != 0 -> if (bytesRead == 0L) -1L else bytesRead
+ ferror(file) != 0 -> throw errnoToIOException(errno)
+ else -> bytesRead
+ }
+ }
+
+ override fun timeout(): Timeout = Timeout.NONE
+
+ override fun close() {
+ if (closed) return
+ closed = true
+ fclose(file)
+ }
+}
diff --git a/okio/src/nativeMain/kotlin/okio/FileSystem.kt b/okio/src/nativeMain/kotlin/okio/FileSystem.kt
new file mode 100644
index 00000000..eb1e1653
--- /dev/null
+++ b/okio/src/nativeMain/kotlin/okio/FileSystem.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import okio.internal.commonCopy
+import okio.internal.commonCreateDirectories
+import okio.internal.commonDeleteRecursively
+import okio.internal.commonExists
+import okio.internal.commonListRecursively
+import okio.internal.commonMetadata
+
+actual abstract class FileSystem {
+ @Throws(IOException::class)
+ actual abstract fun canonicalize(path: Path): Path
+
+ @Throws(IOException::class)
+ actual fun metadata(path: Path): FileMetadata = commonMetadata(path)
+
+ @Throws(IOException::class)
+ actual abstract fun metadataOrNull(path: Path): FileMetadata?
+
+ @Throws(IOException::class)
+ actual fun exists(path: Path): Boolean = commonExists(path)
+
+ @Throws(IOException::class)
+ actual abstract fun list(dir: Path): List<Path>
+
+ actual abstract fun listOrNull(dir: Path): List<Path>?
+
+ actual open fun listRecursively(dir: Path, followSymlinks: Boolean): Sequence<Path> =
+ commonListRecursively(dir, followSymlinks)
+
+ @Throws(IOException::class)
+ actual abstract fun openReadOnly(file: Path): FileHandle
+
+ @Throws(IOException::class)
+ actual abstract fun openReadWrite(
+ file: Path,
+ mustCreate: Boolean,
+ mustExist: Boolean,
+ ): FileHandle
+
+ @Throws(IOException::class)
+ actual abstract fun source(file: Path): Source
+
+ @Throws(IOException::class)
+ actual inline fun <T> read(file: Path, readerAction: BufferedSource.() -> T): T {
+ return source(file).buffer().use {
+ it.readerAction()
+ }
+ }
+
+ @Throws(IOException::class)
+ actual abstract fun sink(file: Path, mustCreate: Boolean): Sink
+
+ @Throws(IOException::class)
+ actual inline fun <T> write(
+ file: Path,
+ mustCreate: Boolean,
+ writerAction: BufferedSink.() -> T,
+ ): T {
+ return sink(file, mustCreate).buffer().use {
+ it.writerAction()
+ }
+ }
+
+ @Throws(IOException::class)
+ actual abstract fun appendingSink(file: Path, mustExist: Boolean): Sink
+
+ @Throws(IOException::class)
+ actual abstract fun createDirectory(dir: Path, mustCreate: Boolean)
+
+ @Throws(IOException::class)
+ actual fun createDirectories(dir: Path, mustCreate: Boolean): Unit = commonCreateDirectories(dir, mustCreate)
+
+ @Throws(IOException::class)
+ actual abstract fun atomicMove(source: Path, target: Path)
+
+ @Throws(IOException::class)
+ actual open fun copy(source: Path, target: Path): Unit = commonCopy(source, target)
+
+ @Throws(IOException::class)
+ actual abstract fun delete(path: Path, mustExist: Boolean)
+
+ @Throws(IOException::class)
+ actual open fun deleteRecursively(fileOrDirectory: Path, mustExist: Boolean): Unit =
+ commonDeleteRecursively(fileOrDirectory, mustExist)
+
+ @Throws(IOException::class)
+ actual abstract fun createSymlink(source: Path, target: Path)
+
+ actual companion object {
+ /**
+ * The current process's host file system. Use this instance directly, or dependency inject a
+ * [FileSystem] to make code testable.
+ */
+ val SYSTEM: FileSystem = PosixFileSystem
+
+ actual val SYSTEM_TEMPORARY_DIRECTORY: Path = PLATFORM_TEMPORARY_DIRECTORY
+ }
+}
diff --git a/okio/src/nativeMain/kotlin/okio/PosixFileSystem.kt b/okio/src/nativeMain/kotlin/okio/PosixFileSystem.kt
new file mode 100644
index 00000000..8958d99b
--- /dev/null
+++ b/okio/src/nativeMain/kotlin/okio/PosixFileSystem.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.get
+import okio.Path.Companion.toPath
+import okio.internal.toPath
+import platform.posix.EEXIST
+import platform.posix.closedir
+import platform.posix.dirent
+import platform.posix.errno
+import platform.posix.opendir
+import platform.posix.readdir
+import platform.posix.set_posix_errno
+
+internal object PosixFileSystem : FileSystem() {
+ private val SELF_DIRECTORY_ENTRY = ".".toPath()
+ private val PARENT_DIRECTORY_ENTRY = "..".toPath()
+
+ override fun canonicalize(path: Path) = variantCanonicalize(path)
+
+ override fun metadataOrNull(path: Path) = variantMetadataOrNull(path)
+
+ override fun list(dir: Path): List<Path> = list(dir, throwOnFailure = true)!!
+
+ override fun listOrNull(dir: Path): List<Path>? = list(dir, throwOnFailure = false)
+
+ private fun list(dir: Path, throwOnFailure: Boolean): List<Path>? {
+ val opendir = opendir(dir.toString())
+ ?: if (throwOnFailure) throw errnoToIOException(errno) else return null
+
+ try {
+ val result = mutableListOf<Path>()
+ val buffer = Buffer()
+
+ set_posix_errno(0) // If readdir() returns null it's either the end or an error.
+ while (true) {
+ val dirent: CPointer<dirent> = readdir(opendir) ?: break
+ val childPath = buffer.writeNullTerminated(
+ bytes = dirent[0].d_name,
+ ).toPath(normalize = true)
+
+ if (childPath == SELF_DIRECTORY_ENTRY || childPath == PARENT_DIRECTORY_ENTRY) {
+ continue // exclude '.' and '..' from the results.
+ }
+
+ result += dir / childPath
+ }
+
+ if (errno != 0) {
+ if (throwOnFailure) {
+ throw errnoToIOException(errno)
+ } else {
+ return null
+ }
+ }
+
+ result.sort()
+ return result
+ } finally {
+ closedir(opendir) // Ignore errno from closedir.
+ }
+ }
+
+ override fun openReadOnly(file: Path) = variantOpenReadOnly(file)
+
+ override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
+ return variantOpenReadWrite(file, mustCreate = mustCreate, mustExist = mustExist)
+ }
+
+ override fun source(file: Path) = variantSource(file)
+
+ override fun sink(file: Path, mustCreate: Boolean) = variantSink(file, mustCreate)
+
+ override fun appendingSink(file: Path, mustExist: Boolean) = variantAppendingSink(file, mustExist)
+
+ override fun createDirectory(dir: Path, mustCreate: Boolean) {
+ val result = variantMkdir(dir)
+ if (result != 0) {
+ if (errno == EEXIST) {
+ if (mustCreate) {
+ errnoToIOException(errno)
+ } else {
+ return
+ }
+ }
+ throw errnoToIOException(errno)
+ }
+ }
+
+ override fun atomicMove(
+ source: Path,
+ target: Path,
+ ) {
+ variantMove(source, target)
+ }
+
+ override fun delete(path: Path, mustExist: Boolean) {
+ variantDelete(path, mustExist)
+ }
+
+ override fun createSymlink(source: Path, target: Path) = variantCreateSymlink(source, target)
+
+ override fun toString() = "PosixSystemFileSystem"
+}
diff --git a/okio/src/nativeMain/kotlin/okio/PosixVariant.kt b/okio/src/nativeMain/kotlin/okio/PosixVariant.kt
new file mode 100644
index 00000000..d2c4ee3e
--- /dev/null
+++ b/okio/src/nativeMain/kotlin/okio/PosixVariant.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+internal expect val PLATFORM_TEMPORARY_DIRECTORY: Path
+
+internal expect fun PosixFileSystem.variantDelete(path: Path, mustExist: Boolean)
+
+internal expect fun PosixFileSystem.variantMkdir(dir: Path): Int
+
+internal expect fun PosixFileSystem.variantCanonicalize(path: Path): Path
+
+internal expect fun PosixFileSystem.variantMetadataOrNull(path: Path): FileMetadata?
+
+internal expect fun PosixFileSystem.variantMove(source: Path, target: Path)
+
+internal expect fun PosixFileSystem.variantSource(file: Path): Source
+
+internal expect fun PosixFileSystem.variantSink(file: Path, mustCreate: Boolean): Sink
+
+internal expect fun PosixFileSystem.variantAppendingSink(file: Path, mustExist: Boolean): Sink
+
+internal expect fun PosixFileSystem.variantOpenReadOnly(file: Path): FileHandle
+
+internal expect fun PosixFileSystem.variantOpenReadWrite(
+ file: Path,
+ mustCreate: Boolean,
+ mustExist: Boolean,
+): FileHandle
+
+internal expect fun PosixFileSystem.variantCreateSymlink(source: Path, target: Path)
diff --git a/okio/src/nativeMain/kotlin/okio/SizetVariant.kt b/okio/src/nativeMain/kotlin/okio/SizetVariant.kt
new file mode 100644
index 00000000..46f5bca4
--- /dev/null
+++ b/okio/src/nativeMain/kotlin/okio/SizetVariant.kt
@@ -0,0 +1,39 @@
+/*
+* Copyright (C) 2020 Square, Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package okio
+
+import kotlinx.cinterop.ByteVar
+import kotlinx.cinterop.ByteVarOf
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.UnsafeNumber
+import kotlinx.cinterop.convert
+import platform.posix.FILE
+import platform.posix.fread
+import platform.posix.fwrite
+
+@OptIn(UnsafeNumber::class)
+internal fun variantFread(
+ target: CPointer<ByteVarOf<Byte>>,
+ byteCount: UInt,
+ file: CPointer<FILE>,
+): UInt = fread(target, 1u, byteCount.convert(), file).convert()
+
+@OptIn(UnsafeNumber::class)
+internal fun variantFwrite(
+ source: CPointer<ByteVar>,
+ byteCount: UInt,
+ file: CPointer<FILE>,
+): UInt = fwrite(source, 1u, byteCount.convert(), file).convert()
diff --git a/okio/src/nativeTest/kotlin/okio/NativeSystemFileSystemTest.kt b/okio/src/nativeTest/kotlin/okio/NativeSystemFileSystemTest.kt
new file mode 100644
index 00000000..e4db5eff
--- /dev/null
+++ b/okio/src/nativeTest/kotlin/okio/NativeSystemFileSystemTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.datetime.Clock
+
+class NativeSystemFileSystemTest : AbstractFileSystemTest(
+ clock = Clock.System,
+ fileSystem = FileSystem.SYSTEM,
+ windowsLimitations = Path.DIRECTORY_SEPARATOR == "\\",
+ allowClobberingEmptyDirectories = Path.DIRECTORY_SEPARATOR == "\\",
+ allowAtomicMoveFromFileToDirectory = false,
+ temporaryDirectory = FileSystem.SYSTEM_TEMPORARY_DIRECTORY,
+)
diff --git a/okio/src/nonJvmMain/kotlin/okio/ByteString.kt b/okio/src/nonAppleMain/kotlin/okio/ByteString.kt
index f0f038b6..e1b8cc51 100644
--- a/okio/src/nonJvmMain/kotlin/okio/ByteString.kt
+++ b/okio/src/nonAppleMain/kotlin/okio/ByteString.kt
@@ -25,6 +25,7 @@ import okio.internal.Sha512
import okio.internal.commonBase64
import okio.internal.commonBase64Url
import okio.internal.commonCompareTo
+import okio.internal.commonCopyInto
import okio.internal.commonDecodeBase64
import okio.internal.commonDecodeHex
import okio.internal.commonEncodeUtf8
@@ -51,13 +52,14 @@ import okio.internal.commonWrite
actual open class ByteString
internal actual constructor(
- internal actual val data: ByteArray
+ internal actual val data: ByteArray,
) : Comparable<ByteString> {
@Suppress("SetterBackingFieldAssignment")
internal actual var hashCode: Int = 0 // 0 if unknown.
set(value) {
// Do nothing to avoid IllegalImmutabilityException.
}
+
@Suppress("SetterBackingFieldAssignment")
internal actual var utf8: String? = null
set(value) {
@@ -125,16 +127,23 @@ internal actual constructor(
offset: Int,
other: ByteString,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
actual open fun rangeEquals(
offset: Int,
other: ByteArray,
otherOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
+ actual open fun copyInto(
+ offset: Int,
+ target: ByteArray,
+ targetOffset: Int,
+ byteCount: Int,
+ ) = commonCopyInto(offset, target, targetOffset, byteCount)
+
actual fun startsWith(prefix: ByteString) = commonStartsWith(prefix)
actual fun startsWith(prefix: ByteArray) = commonStartsWith(prefix)
diff --git a/okio/src/nonAppleMain/kotlin/okio/SegmentedByteString.kt b/okio/src/nonAppleMain/kotlin/okio/SegmentedByteString.kt
new file mode 100644
index 00000000..485d834e
--- /dev/null
+++ b/okio/src/nonAppleMain/kotlin/okio/SegmentedByteString.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import okio.internal.HashFunction
+import okio.internal.commonCopyInto
+import okio.internal.commonEquals
+import okio.internal.commonGetSize
+import okio.internal.commonHashCode
+import okio.internal.commonInternalGet
+import okio.internal.commonRangeEquals
+import okio.internal.commonSubstring
+import okio.internal.commonToByteArray
+import okio.internal.commonWrite
+import okio.internal.forEachSegment
+
+internal actual class SegmentedByteString internal actual constructor(
+ internal actual val segments: Array<ByteArray>,
+ internal actual val directory: IntArray,
+) : ByteString(EMPTY.data) {
+
+ override fun base64() = toByteString().base64()
+
+ override fun hex() = toByteString().hex()
+
+ override fun toAsciiLowercase() = toByteString().toAsciiLowercase()
+
+ override fun toAsciiUppercase() = toByteString().toAsciiUppercase()
+
+ override fun base64Url() = toByteString().base64Url()
+
+ override fun substring(beginIndex: Int, endIndex: Int): ByteString =
+ commonSubstring(beginIndex, endIndex)
+
+ override fun internalGet(pos: Int): Byte = commonInternalGet(pos)
+
+ override fun getSize() = commonGetSize()
+
+ override fun toByteArray(): ByteArray = commonToByteArray()
+
+ override fun write(buffer: Buffer, offset: Int, byteCount: Int): Unit =
+ commonWrite(buffer, offset, byteCount)
+
+ override fun rangeEquals(
+ offset: Int,
+ other: ByteString,
+ otherOffset: Int,
+ byteCount: Int,
+ ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
+
+ override fun rangeEquals(
+ offset: Int,
+ other: ByteArray,
+ otherOffset: Int,
+ byteCount: Int,
+ ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount)
+
+ override fun copyInto(
+ offset: Int,
+ target: ByteArray,
+ targetOffset: Int,
+ byteCount: Int,
+ ) = commonCopyInto(offset, target, targetOffset, byteCount)
+
+ override fun indexOf(other: ByteArray, fromIndex: Int) = toByteString().indexOf(other, fromIndex)
+
+ override fun lastIndexOf(other: ByteArray, fromIndex: Int) = toByteString().lastIndexOf(
+ other,
+ fromIndex,
+ )
+
+ override fun digest(hashFunction: HashFunction): ByteString {
+ forEachSegment { data, offset, byteCount ->
+ hashFunction.update(data, offset, byteCount)
+ }
+ val digestBytes = hashFunction.digest()
+ return ByteString(digestBytes)
+ }
+
+ /** Returns a copy as a non-segmented byte string. */
+ private fun toByteString() = ByteString(toByteArray())
+
+ override fun internalArray() = toByteArray()
+
+ override fun equals(other: Any?): Boolean = commonEquals(other)
+
+ override fun hashCode(): Int = commonHashCode()
+
+ override fun toString() = toByteString().toString()
+}
diff --git a/okio/src/nonJvmMain/kotlin/okio/Buffer.kt b/okio/src/nonJvmMain/kotlin/okio/Buffer.kt
index ec28f63f..8dfb5622 100644
--- a/okio/src/nonJvmMain/kotlin/okio/Buffer.kt
+++ b/okio/src/nonJvmMain/kotlin/okio/Buffer.kt
@@ -93,12 +93,12 @@ actual class Buffer : BufferedSource, BufferedSink {
actual fun copyTo(
out: Buffer,
offset: Long,
- byteCount: Long
+ byteCount: Long,
): Buffer = commonCopyTo(out, offset, byteCount)
actual fun copyTo(
out: Buffer,
- offset: Long
+ offset: Long,
): Buffer = copyTo(out, offset, size - offset)
actual operator fun get(pos: Long): Byte = commonGet(pos)
@@ -232,7 +232,7 @@ actual class Buffer : BufferedSource, BufferedSink {
offset: Long,
bytes: ByteString,
bytesOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, bytes, bytesOffset, byteCount)
override fun flush() = Unit
@@ -297,7 +297,7 @@ actual class Buffer : BufferedSource, BufferedSink {
actual fun readAndWriteUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor =
commonReadAndWriteUnsafe(unsafeCursor)
- actual class UnsafeCursor {
+ actual class UnsafeCursor : Closeable {
actual var buffer: Buffer? = null
actual var readWrite: Boolean = false
@@ -315,7 +315,7 @@ actual class Buffer : BufferedSource, BufferedSink {
actual fun expandBuffer(minByteCount: Int): Long = commonExpandBuffer(minByteCount)
- actual fun close() {
+ actual override fun close() {
commonClose()
}
}
diff --git a/okio/src/nonJvmMain/kotlin/okio/BufferedSink.kt b/okio/src/nonJvmMain/kotlin/okio/BufferedSink.kt
index 65d717c6..cdb767a2 100644
--- a/okio/src/nonJvmMain/kotlin/okio/BufferedSink.kt
+++ b/okio/src/nonJvmMain/kotlin/okio/BufferedSink.kt
@@ -15,7 +15,7 @@
*/
package okio
-actual interface BufferedSink : Sink {
+actual sealed interface BufferedSink : Sink {
actual val buffer: Buffer
actual fun write(byteString: ByteString): BufferedSink
diff --git a/okio/src/nonJvmMain/kotlin/okio/BufferedSource.kt b/okio/src/nonJvmMain/kotlin/okio/BufferedSource.kt
index 98b7718a..369a3e63 100644
--- a/okio/src/nonJvmMain/kotlin/okio/BufferedSource.kt
+++ b/okio/src/nonJvmMain/kotlin/okio/BufferedSource.kt
@@ -15,7 +15,7 @@
*/
package okio
-actual interface BufferedSource : Source {
+actual sealed interface BufferedSource : Source {
actual val buffer: Buffer
actual fun exhausted(): Boolean
diff --git a/okio/src/nonJvmMain/kotlin/okio/ForwardingSource.kt b/okio/src/nonJvmMain/kotlin/okio/ForwardingSource.kt
new file mode 100644
index 00000000..1b75eba5
--- /dev/null
+++ b/okio/src/nonJvmMain/kotlin/okio/ForwardingSource.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+actual abstract class ForwardingSource actual constructor(
+ actual val delegate: Source,
+) : Source {
+ // TODO 'Source by delegate' once https://youtrack.jetbrains.com/issue/KT-23935 is fixed.
+
+ @Throws(IOException::class)
+ actual override fun read(sink: Buffer, byteCount: Long): Long = delegate.read(sink, byteCount)
+
+ actual override fun timeout() = delegate.timeout()
+
+ @Throws(IOException::class)
+ actual override fun close() = delegate.close()
+
+ actual override fun toString() = "${this::class.simpleName}($delegate)"
+}
diff --git a/okio/src/nonJvmMain/kotlin/okio/HashingSink.kt b/okio/src/nonJvmMain/kotlin/okio/HashingSink.kt
index cbd14a26..fd86acf9 100644
--- a/okio/src/nonJvmMain/kotlin/okio/HashingSink.kt
+++ b/okio/src/nonJvmMain/kotlin/okio/HashingSink.kt
@@ -24,7 +24,7 @@ import okio.internal.Sha512
actual class HashingSink internal constructor(
private val sink: Sink,
- private val hashFunction: HashFunction
+ private val hashFunction: HashFunction,
) : Sink {
override fun write(source: Buffer, byteCount: Long) {
diff --git a/okio/src/nonJvmMain/kotlin/okio/HashingSource.kt b/okio/src/nonJvmMain/kotlin/okio/HashingSource.kt
index 62bfd608..40e4a628 100644
--- a/okio/src/nonJvmMain/kotlin/okio/HashingSource.kt
+++ b/okio/src/nonJvmMain/kotlin/okio/HashingSource.kt
@@ -24,7 +24,7 @@ import okio.internal.Sha512
actual class HashingSource internal constructor(
private val source: Source,
- private val hashFunction: HashFunction
+ private val hashFunction: HashFunction,
) : Source {
override fun read(sink: Buffer, byteCount: Long): Long {
diff --git a/okio/src/nonJvmMain/kotlin/okio/-Platform.kt b/okio/src/nonJvmMain/kotlin/okio/NonJvmPlatform.kt
index 90fcd36d..6e9ac870 100644
--- a/okio/src/nonJvmMain/kotlin/okio/-Platform.kt
+++ b/okio/src/nonJvmMain/kotlin/okio/NonJvmPlatform.kt
@@ -19,24 +19,39 @@ package okio
import okio.internal.commonAsUtf8ToByteArray
import okio.internal.commonToUtf8String
+internal expect val PLATFORM_DIRECTORY_SEPARATOR: String
+
internal actual fun ByteArray.toUtf8String(): String = commonToUtf8String()
internal actual fun String.asUtf8ToByteArray(): ByteArray = commonAsUtf8ToByteArray()
actual open class ArrayIndexOutOfBoundsException actual constructor(
- message: String?
+ message: String?,
) : IndexOutOfBoundsException(message)
-internal actual inline fun <R> synchronized(lock: Any, block: () -> R): R = block()
+actual class Lock {
+ companion object {
+ val instance = Lock()
+ }
+}
+
+internal actual fun newLock(): Lock = Lock.instance
+
+actual inline fun <T> Lock.withLock(action: () -> T): T = action()
actual open class IOException actual constructor(
message: String?,
- cause: Throwable?
+ cause: Throwable?,
) : Exception(message, cause) {
actual constructor(message: String?) : this(message, null)
+ actual constructor() : this(null, null)
}
-actual open class EOFException actual constructor(message: String?) : IOException(message)
+actual class ProtocolException actual constructor(message: String) : IOException(message)
+
+actual open class EOFException actual constructor(message: String?) : IOException(message) {
+ actual constructor() : this(null)
+}
actual open class FileNotFoundException actual constructor(message: String?) : IOException(message)
diff --git a/okio/src/nonJvmMain/kotlin/okio/Path.kt b/okio/src/nonJvmMain/kotlin/okio/Path.kt
new file mode 100644
index 00000000..16328afa
--- /dev/null
+++ b/okio/src/nonJvmMain/kotlin/okio/Path.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import okio.internal.commonCompareTo
+import okio.internal.commonEquals
+import okio.internal.commonHashCode
+import okio.internal.commonIsAbsolute
+import okio.internal.commonIsRelative
+import okio.internal.commonIsRoot
+import okio.internal.commonName
+import okio.internal.commonNameBytes
+import okio.internal.commonNormalized
+import okio.internal.commonParent
+import okio.internal.commonRelativeTo
+import okio.internal.commonResolve
+import okio.internal.commonRoot
+import okio.internal.commonSegments
+import okio.internal.commonSegmentsBytes
+import okio.internal.commonToPath
+import okio.internal.commonToString
+import okio.internal.commonVolumeLetter
+
+actual class Path internal actual constructor(
+ internal actual val bytes: ByteString,
+) : Comparable<Path> {
+ actual val root: Path?
+ get() = commonRoot()
+
+ actual val segments: List<String>
+ get() = commonSegments()
+
+ actual val segmentsBytes: List<ByteString>
+ get() = commonSegmentsBytes()
+
+ actual val isAbsolute: Boolean
+ get() = commonIsAbsolute()
+
+ actual val isRelative: Boolean
+ get() = commonIsRelative()
+
+ actual val volumeLetter: Char?
+ get() = commonVolumeLetter()
+
+ actual val nameBytes: ByteString
+ get() = commonNameBytes()
+
+ actual val name: String
+ get() = commonName()
+
+ actual val parent: Path?
+ get() = commonParent()
+
+ actual val isRoot: Boolean
+ get() = commonIsRoot()
+
+ actual operator fun div(child: String): Path = commonResolve(child, normalize = false)
+
+ actual operator fun div(child: ByteString): Path = commonResolve(child, normalize = false)
+
+ actual operator fun div(child: Path): Path = commonResolve(child, normalize = false)
+
+ actual fun resolve(child: String, normalize: Boolean): Path =
+ commonResolve(child, normalize = normalize)
+
+ actual fun resolve(child: ByteString, normalize: Boolean): Path =
+ commonResolve(child, normalize = normalize)
+
+ actual fun resolve(child: Path, normalize: Boolean): Path =
+ commonResolve(child = child, normalize = normalize)
+
+ actual fun relativeTo(other: Path): Path = commonRelativeTo(other)
+
+ actual fun normalized(): Path = commonNormalized()
+
+ actual override fun compareTo(other: Path): Int = commonCompareTo(other)
+
+ actual override fun equals(other: Any?): Boolean = commonEquals(other)
+
+ actual override fun hashCode() = commonHashCode()
+
+ actual override fun toString() = commonToString()
+
+ actual companion object {
+ actual val DIRECTORY_SEPARATOR: String = PLATFORM_DIRECTORY_SEPARATOR
+
+ actual fun String.toPath(normalize: Boolean): Path = commonToPath(normalize)
+ }
+}
diff --git a/okio/src/nonJvmMain/kotlin/okio/RealBufferedSink.kt b/okio/src/nonJvmMain/kotlin/okio/RealBufferedSink.kt
index ed03094e..8b09c7f4 100644
--- a/okio/src/nonJvmMain/kotlin/okio/RealBufferedSink.kt
+++ b/okio/src/nonJvmMain/kotlin/okio/RealBufferedSink.kt
@@ -37,7 +37,7 @@ import okio.internal.commonWriteUtf8
import okio.internal.commonWriteUtf8CodePoint
internal actual class RealBufferedSink actual constructor(
- actual val sink: Sink
+ actual val sink: Sink,
) : BufferedSink {
actual var closed: Boolean = false
override val buffer = Buffer()
diff --git a/okio/src/nonJvmMain/kotlin/okio/RealBufferedSource.kt b/okio/src/nonJvmMain/kotlin/okio/RealBufferedSource.kt
index d6f4b942..93ad10f0 100644
--- a/okio/src/nonJvmMain/kotlin/okio/RealBufferedSource.kt
+++ b/okio/src/nonJvmMain/kotlin/okio/RealBufferedSource.kt
@@ -47,7 +47,7 @@ import okio.internal.commonTimeout
import okio.internal.commonToString
internal actual class RealBufferedSource actual constructor(
- actual val source: Source
+ actual val source: Source,
) : BufferedSource {
actual var closed: Boolean = false
override val buffer: Buffer = Buffer()
@@ -96,15 +96,17 @@ internal actual class RealBufferedSource actual constructor(
commonIndexOfElement(targetBytes, fromIndex)
override fun rangeEquals(offset: Long, bytes: ByteString) = rangeEquals(
- offset, bytes, 0,
- bytes.size
+ offset,
+ bytes,
+ 0,
+ bytes.size,
)
override fun rangeEquals(
offset: Long,
bytes: ByteString,
bytesOffset: Int,
- byteCount: Int
+ byteCount: Int,
): Boolean = commonRangeEquals(offset, bytes, bytesOffset, byteCount)
override fun peek(): BufferedSource = commonPeek()
diff --git a/okio/src/nonJvmTest/kotlin/okio/NonJvmTesting.kt b/okio/src/nonJvmTest/kotlin/okio/NonJvmTesting.kt
new file mode 100644
index 00000000..a5e9a780
--- /dev/null
+++ b/okio/src/nonJvmTest/kotlin/okio/NonJvmTesting.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+actual fun assertRelativeTo(
+ a: Path,
+ b: Path,
+ bRelativeToA: Path,
+ sameAsNio: Boolean,
+) {
+ val actual = b.relativeTo(a)
+ assertEquals(bRelativeToA, actual)
+ assertEquals(b.normalized().withUnixSlashes(), (a / actual).normalized().withUnixSlashes())
+}
+
+actual fun assertRelativeToFails(
+ a: Path,
+ b: Path,
+ sameAsNio: Boolean,
+): IllegalArgumentException {
+ return assertFailsWith { b.relativeTo(a) }
+}
diff --git a/okio/src/nonWasmTest/kotlin/okio/FakeFileSystemTest.kt b/okio/src/nonWasmTest/kotlin/okio/FakeFileSystemTest.kt
new file mode 100644
index 00000000..07dfad8d
--- /dev/null
+++ b/okio/src/nonWasmTest/kotlin/okio/FakeFileSystemTest.kt
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.reflect.KClass
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.time.Duration.Companion.minutes
+import okio.Path.Companion.toPath
+import okio.fakefilesystem.FakeFileSystem
+
+class FakeWindowsFileSystemTest : FakeFileSystemTest(
+ FakeFileSystem(clock = FakeClock()).also { it.emulateWindows() },
+ temporaryDirectory = "C:\\".toPath(),
+)
+
+class FakeUnixFileSystemTest : FakeFileSystemTest(
+ FakeFileSystem(clock = FakeClock()).also { it.emulateUnix() },
+ temporaryDirectory = "/".toPath(),
+)
+
+class StrictFakeFileSystemTest : FakeFileSystemTest(
+ FakeFileSystem(clock = FakeClock()),
+ temporaryDirectory = "/".toPath(),
+)
+
+abstract class FakeFileSystemTest internal constructor(
+ private val fakeFileSystem: FakeFileSystem,
+ temporaryDirectory: Path,
+) : AbstractFileSystemTest(
+ clock = fakeFileSystem.clock,
+ fileSystem = fakeFileSystem,
+ windowsLimitations = !fakeFileSystem.allowMovingOpenFiles,
+ allowClobberingEmptyDirectories = fakeFileSystem.allowClobberingEmptyDirectories,
+ allowAtomicMoveFromFileToDirectory = false,
+ temporaryDirectory = temporaryDirectory,
+) {
+ private val fakeClock: FakeClock = fakeFileSystem.clock as FakeClock
+
+ @Test
+ fun openPathsIncludesOpenSink() {
+ val openPath = base / "open-file"
+ val sink = fileSystem.sink(openPath)
+ assertEquals(openPath, fakeFileSystem.openPaths.single())
+ sink.close()
+ assertTrue(fakeFileSystem.openPaths.isEmpty())
+ }
+
+ @Test
+ fun openPathsIncludesOpenSource() {
+ val openPath = base / "open-file"
+ openPath.writeUtf8("hello, world!")
+ assertTrue(fakeFileSystem.openPaths.isEmpty())
+ val source = fileSystem.source(openPath)
+ assertEquals(openPath, fakeFileSystem.openPaths.single())
+ source.close()
+ assertTrue(fakeFileSystem.openPaths.isEmpty())
+ }
+
+ @Test
+ fun openPathsIsOpenOrder() {
+ if (!fakeFileSystem.allowWritesWhileWriting) return
+
+ val fileA = base / "a"
+ val fileB = base / "b"
+ val fileC = base / "c"
+ val fileD = base / "d"
+
+ assertEquals(fakeFileSystem.openPaths, listOf())
+ val sinkD = fileSystem.sink(fileD)
+ assertEquals(fakeFileSystem.openPaths, listOf(fileD))
+ val sinkB = fileSystem.sink(fileB)
+ assertEquals(fakeFileSystem.openPaths, listOf(fileD, fileB))
+ val sinkC = fileSystem.sink(fileC)
+ assertEquals(fakeFileSystem.openPaths, listOf(fileD, fileB, fileC))
+ val sinkA = fileSystem.sink(fileA)
+ assertEquals(fakeFileSystem.openPaths, listOf(fileD, fileB, fileC, fileA))
+ val sinkB2 = fileSystem.sink(fileB)
+ assertEquals(fakeFileSystem.openPaths, listOf(fileD, fileB, fileC, fileA, fileB))
+ sinkD.close()
+ assertEquals(fakeFileSystem.openPaths, listOf(fileB, fileC, fileA, fileB))
+ sinkB2.close()
+ assertEquals(fakeFileSystem.openPaths, listOf(fileB, fileC, fileA))
+ sinkB.close()
+ assertEquals(fakeFileSystem.openPaths, listOf(fileC, fileA))
+ sinkC.close()
+ assertEquals(fakeFileSystem.openPaths, listOf(fileA))
+ sinkA.close()
+ assertEquals(fakeFileSystem.openPaths, listOf())
+ }
+
+ @Test
+ fun allPathsIncludesFile() {
+ val file = base / "all-files-includes-file"
+ file.writeUtf8("hello, world!")
+ assertEquals(setOf(base, file), fakeFileSystem.allPaths)
+ }
+
+ @Test
+ fun allPathsIsSorted() {
+ val fileA = base / "a"
+ val fileB = base / "b"
+ val fileC = base / "c"
+ val fileD = base / "d"
+
+ // Create files in a different order than the sorted order, so a file system that returns files
+ // in creation-order or reverse-creation order won't pass by accident.
+ fileD.writeUtf8("fileD")
+ fileB.writeUtf8("fileB")
+ fileC.writeUtf8("fileC")
+ fileA.writeUtf8("fileA")
+
+ assertEquals(listOf(base, fileA, fileB, fileC, fileD), fakeFileSystem.allPaths.toList())
+ }
+
+ @Test
+ fun allPathsIncludesDirectory() {
+ val dir = base / "all-files-includes-directory"
+ fileSystem.createDirectory(dir)
+ assertEquals(setOf(base, dir), fakeFileSystem.allPaths)
+ }
+
+ @Test
+ fun allPathsDoesNotIncludeDeletedFile() {
+ val file = base / "all-files-does-not-include-deleted-file"
+ file.writeUtf8("hello, world!")
+ fileSystem.delete(file)
+ assertEquals(setOf(base), fakeFileSystem.allPaths)
+ }
+
+ @Test
+ fun allPathsDoesNotIncludeDeletedOpenFile() {
+ if (windowsLimitations) return // Can't delete open files with Windows' limitations.
+
+ val file = base / "all-files-does-not-include-deleted-open-file"
+ val sink = fileSystem.sink(file)
+ assertEquals(setOf(base, file), fakeFileSystem.allPaths)
+ fileSystem.delete(file)
+ assertEquals(setOf(base), fakeFileSystem.allPaths)
+ sink.close()
+ }
+
+ @Test
+ fun fileLastAccessedTime() {
+ val path = base / "file-last-accessed-time"
+
+ fakeClock.sleep(1.minutes)
+ path.writeUtf8("hello, world!")
+ val createdAt = clock.now()
+
+ fakeClock.sleep(1.minutes)
+ path.writeUtf8("hello again!")
+ val modifiedAt = clock.now()
+
+ fakeClock.sleep(1.minutes)
+ path.readUtf8()
+ val accessedAt = clock.now()
+
+ val metadata = fileSystem.metadata(path)
+ assertEquals(createdAt, metadata.createdAt)
+ assertEquals(modifiedAt, metadata.lastModifiedAt)
+ assertEquals(accessedAt, metadata.lastAccessedAt)
+ }
+
+ @Test
+ fun directoryLastAccessedTime() {
+ val path = base / "directory-last-accessed-time"
+
+ fakeClock.sleep(1.minutes)
+ fileSystem.createDirectory(path)
+ val createdAt = clock.now()
+
+ fakeClock.sleep(1.minutes)
+ (path / "child").writeUtf8("hello world!")
+ val modifiedAt = clock.now()
+
+ fakeClock.sleep(1.minutes)
+ fileSystem.list(path)
+ val accessedAt = clock.now()
+
+ val metadata = fileSystem.metadata(path)
+ assertEquals(createdAt, metadata.createdAt)
+ assertEquals(modifiedAt, metadata.lastModifiedAt)
+ assertEquals(accessedAt, metadata.lastAccessedAt)
+ }
+
+ @Test
+ fun checkNoOpenFilesThrowsOnOpenSource() {
+ val path = base / "check-no-open-files-open-source"
+ path.writeUtf8("hello, world!")
+ val exception = fileSystem.source(path).use { source ->
+ assertFailsWith<IllegalStateException> {
+ fakeFileSystem.checkNoOpenFiles()
+ }
+ }
+
+ assertEquals(
+ """
+ |expected 0 open files, but found:
+ | $path
+ """.trimMargin(),
+ exception.message,
+ )
+ assertEquals("file opened for READ here", exception.cause?.message)
+
+ // Now that the source is closed this is safe.
+ fakeFileSystem.checkNoOpenFiles()
+ }
+
+ @Test
+ fun checkNoOpenFilesThrowsOnOpenSink() {
+ val path = base / "check-no-open-files-open-sink"
+ val exception = fileSystem.sink(path).use { source ->
+ assertFailsWith<IllegalStateException> {
+ fakeFileSystem.checkNoOpenFiles()
+ }
+ }
+
+ assertEquals(
+ """
+ |expected 0 open files, but found:
+ | $path
+ """.trimMargin(),
+ exception.message,
+ )
+ assertEquals("file opened for WRITE here", exception.cause?.message)
+
+ // Now that the source is closed this is safe.
+ fakeFileSystem.checkNoOpenFiles()
+ }
+
+ @Test
+ fun createDirectoriesForVolumeLetterRoot() {
+ val path = "X:\\".toPath()
+ fileSystem.createDirectories(path)
+ assertTrue(fileSystem.metadata(path).isDirectory)
+ }
+
+ @Test
+ fun createDirectoriesForChildOfVolumeLetterRoot() {
+ val path = "X:\\path".toPath()
+ fileSystem.createDirectories(path)
+ assertTrue(fileSystem.metadata(path).isDirectory)
+ }
+
+ @Test
+ fun createDirectoriesForUnixRoot() {
+ val path = "/".toPath()
+ fileSystem.createDirectories(path)
+ assertTrue(fileSystem.metadata(path).isDirectory)
+ }
+
+ @Test
+ fun createDirectoriesForChildOfUnixRoot() {
+ val path = "/path".toPath()
+ fileSystem.createDirectories(path)
+ assertTrue(fileSystem.metadata(path).isDirectory)
+ }
+
+ @Test
+ fun createDirectoriesForUncRoot() {
+ val path = "\\\\server".toPath()
+ fileSystem.createDirectories(path)
+ assertTrue(fileSystem.metadata(path).isDirectory)
+ }
+
+ @Test
+ fun createDirectoriesForChildOfUncRoot() {
+ val path = "\\\\server\\project".toPath()
+ fileSystem.createDirectories(path)
+ assertTrue(fileSystem.metadata(path).isDirectory)
+ }
+
+ @Test
+ fun workingDirectoryMustBeAbsolute() {
+ val exception = assertFailsWith<IllegalArgumentException> {
+ fakeFileSystem.workingDirectory = "some/relative/path".toPath()
+ }
+ assertEquals("expected an absolute path but was some/relative/path", exception.message)
+ }
+
+ @Test
+ fun metadataForRootsGeneratedOnDemand() {
+ assertTrue(fileSystem.metadata("X:\\".toPath()).isDirectory)
+ assertTrue(fileSystem.metadata("/".toPath()).isDirectory)
+ assertTrue(fileSystem.metadata("\\\\server".toPath()).isDirectory)
+ }
+
+ @Test
+ fun startWriteWhileWritingNotAllowedWhenStrict() {
+ val path = base / "write-write"
+ path.writeUtf8("hello world!")
+ fileSystem.sink(path).use {
+ try {
+ fileSystem.sink(path).use {
+ }
+ assertTrue(fakeFileSystem.allowWritesWhileWriting)
+ } catch (_: IOException) {
+ assertFalse(fakeFileSystem.allowWritesWhileWriting)
+ }
+ }
+ }
+
+ @Test
+ fun startReadWhileWritingNotAllowedWhenStrict() {
+ val path = base / "write-read"
+ path.writeUtf8("hello world!")
+ fileSystem.sink(path).use {
+ try {
+ fileSystem.source(path).use {
+ }
+ assertTrue(fakeFileSystem.allowReadsWhileWriting)
+ } catch (_: IOException) {
+ assertFalse(fakeFileSystem.allowReadsWhileWriting)
+ }
+ }
+ }
+
+ @Test
+ fun startWriteWhileReadingNotAllowedWhenStrict() {
+ val path = base / "read-write"
+ path.writeUtf8("hello world!")
+ fileSystem.source(path).use {
+ try {
+ fileSystem.sink(path).use {
+ }
+ assertTrue(fakeFileSystem.allowReadsWhileWriting)
+ } catch (_: IOException) {
+ assertFalse(fakeFileSystem.allowReadsWhileWriting)
+ }
+ }
+ }
+
+ @Test
+ fun startReadWhileReadingAllowedWhenStrict() {
+ val path = base / "read-read"
+ path.writeUtf8("hello world!")
+ fileSystem.source(path).use {
+ fileSystem.source(path).use {
+ }
+ }
+ }
+
+ @Test
+ fun symlinkCanBeUsedAfterSettingAllowSymlinksToFalse() {
+ if (!supportsSymlink()) return
+
+ val target = base / "symlink-target"
+ val source = base / "symlink-source"
+ fileSystem.createSymlink(source, target)
+ fakeFileSystem.allowSymlinks = false
+ target.writeUtf8("I am the target file")
+ assertEquals("I am the target file", source.readUtf8())
+ }
+
+ @Test
+ fun symlinkCannotBeCreatedAfterSettingAllowSymlinksToFalse() {
+ fakeFileSystem.allowSymlinks = false
+ val target = base / "symlink-target"
+ val source = base / "symlink-source"
+ assertFailsWith<IOException> {
+ fileSystem.createSymlink(source, target)
+ }
+ }
+
+ @Test
+ fun fileExtras() {
+ val path = base / "a.txt"
+ path.writeUtf8("hello")
+ fakeFileSystem.setExtra(path, ContentTypeExtra::class, ContentTypeExtra("text/plain"))
+ val metadata = fileSystem.metadata(path)
+ assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class))
+ }
+
+ @Test
+ fun directoryExtras() {
+ val path = base / "a.txt"
+ fileSystem.createDirectory(path)
+ fakeFileSystem.setExtra(path, ContentTypeExtra::class, ContentTypeExtra("text/plain"))
+ val metadata = fileSystem.metadata(path)
+ assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class))
+ }
+
+ @Test
+ fun symlinkExtras() {
+ if (!supportsSymlink()) return
+
+ val pathA = base / "a.txt"
+ val pathB = base / "b.txt"
+ fileSystem.createSymlink(pathA, pathB)
+ fakeFileSystem.setExtra(pathA, ContentTypeExtra::class, ContentTypeExtra("text/plain"))
+ val metadata = fileSystem.metadata(pathA)
+ assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class))
+ }
+
+ @Test
+ fun deleteExtra() {
+ val path = base / "a.txt"
+ path.writeUtf8("hello")
+ fakeFileSystem.setExtra(path, ContentTypeExtra::class, ContentTypeExtra("text/plain"))
+ fakeFileSystem.setExtra(path, ContentTypeExtra::class, null)
+ val metadata = fileSystem.metadata(path)
+ assertNull(metadata.extra(ContentTypeExtra::class))
+ assertEquals(mapOf(), metadata.extras)
+ }
+
+ @Test
+ fun extraIsNotCopiedByFileCopy() {
+ val pathA = base / "a.txt"
+ val pathB = base / "b.txt"
+ pathA.writeUtf8("hello")
+ fakeFileSystem.setExtra(pathA, ContentTypeExtra::class, ContentTypeExtra("text/plain"))
+ fileSystem.copy(pathA, pathB)
+ val metadata = fileSystem.metadata(pathB)
+ assertNull(metadata.extra(ContentTypeExtra::class))
+ }
+
+ @Test
+ fun extraIsMovedByAtomicMove() {
+ val pathA = base / "a.txt"
+ val pathB = base / "b.txt"
+ pathA.writeUtf8("hello")
+ fakeFileSystem.setExtra(pathA, ContentTypeExtra::class, ContentTypeExtra("text/plain"))
+ fileSystem.atomicMove(pathA, pathB)
+ val metadata = fileSystem.metadata(pathB)
+ assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class))
+ }
+
+ @Test
+ fun extrasHappyPath() {
+ val metadata = FileMetadata(
+ isRegularFile = true,
+ size = 10L,
+ extras = mapOf(ContentTypeExtra::class to ContentTypeExtra("text/plain")),
+ )
+ assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class))
+ }
+
+ @Test
+ fun createExtrasDefensiveCopy() {
+ val extras = mutableMapOf<KClass<*>, Any>(
+ ContentTypeExtra::class to ContentTypeExtra("text/plain"),
+ )
+ val metadata = FileMetadata(
+ isRegularFile = true,
+ size = 10L,
+ extras = extras,
+ )
+ extras.clear()
+ assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class))
+ }
+
+ @Test
+ fun getExtraAbsent() {
+ val metadata = FileMetadata(
+ isRegularFile = true,
+ size = 10L,
+ extras = mapOf(),
+ )
+ assertNull(metadata.extra(ContentTypeExtra::class))
+ }
+
+ @Test
+ fun getExtraWrongType() {
+ val metadata = FileMetadata(
+ isRegularFile = true,
+ size = 10L,
+ extras = mapOf(ContentTypeExtra::class to "hello"),
+ )
+ assertFailsWith<ClassCastException> {
+ metadata.extra(ContentTypeExtra::class)
+ }
+ }
+
+ internal data class ContentTypeExtra(
+ val contentType: String,
+ )
+}
diff --git a/okio/src/nonWasmTest/kotlin/okio/ForwardingFileSystemTest.kt b/okio/src/nonWasmTest/kotlin/okio/ForwardingFileSystemTest.kt
new file mode 100644
index 00000000..6b7e089b
--- /dev/null
+++ b/okio/src/nonWasmTest/kotlin/okio/ForwardingFileSystemTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
+import kotlinx.datetime.Clock
+import okio.Path.Companion.toPath
+import okio.fakefilesystem.FakeFileSystem
+
+class ForwardingFileSystemTest : AbstractFileSystemTest(
+ clock = Clock.System,
+ fileSystem = object : ForwardingFileSystem(FakeFileSystem().apply { emulateUnix() }) {},
+ windowsLimitations = false,
+ allowClobberingEmptyDirectories = false,
+ allowAtomicMoveFromFileToDirectory = false,
+ temporaryDirectory = "/".toPath(),
+) {
+ @Test
+ fun pathBlocking() {
+ val forwardingFileSystem = object : ForwardingFileSystem(fileSystem) {
+ override fun delete(path: Path, mustExist: Boolean) {
+ throw IOException("synthetic failure!")
+ }
+
+ override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path {
+ if (path.name.contains("blocked")) throw IOException("blocked path!")
+ return path
+ }
+ }
+
+ forwardingFileSystem.createDirectory(base / "okay")
+ assertFailsWith<IOException> {
+ forwardingFileSystem.createDirectory(base / "blocked")
+ }
+ }
+
+ @Test
+ fun operationBlocking() {
+ val forwardingFileSystem = object : ForwardingFileSystem(fileSystem) {
+ override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path {
+ if (functionName == "delete") throw IOException("blocked operation!")
+ return path
+ }
+ }
+
+ forwardingFileSystem.createDirectory(base / "operation-blocking")
+ assertFailsWith<IOException> {
+ forwardingFileSystem.delete(base / "operation-blocking")
+ }
+ }
+
+ @Test
+ fun pathMapping() {
+ val prefix = "/mapped"
+ val source = base / "source"
+ val mappedSource = (prefix + source).toPath()
+ val target = base / "target"
+ val mappedTarget = (prefix + target).toPath()
+
+ source.writeUtf8("hello, world!")
+
+ val forwardingFileSystem = object : ForwardingFileSystem(fileSystem) {
+ override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path {
+ return path.toString().removePrefix(prefix).toPath()
+ }
+
+ override fun onPathResult(path: Path, functionName: String): Path {
+ return (prefix + path).toPath()
+ }
+ }
+
+ forwardingFileSystem.copy(mappedSource, mappedTarget)
+ assertTrue(target in fileSystem.list(base))
+ assertTrue(mappedTarget in forwardingFileSystem.list(base))
+ assertEquals("hello, world!", source.readUtf8())
+ assertEquals("hello, world!", target.readUtf8())
+ }
+
+ /**
+ * Path mapping might impact the sort order. Confirm that list() returns elements in sorted order
+ * even if that order is different in the delegate file system.
+ */
+ @Test
+ fun pathMappingImpactedBySorting() {
+ val az = base / "az"
+ val by = base / "by"
+ val cx = base / "cx"
+ az.writeUtf8("az")
+ by.writeUtf8("by")
+ cx.writeUtf8("cx")
+
+ val forwardingFileSystem = object : ForwardingFileSystem(fileSystem) {
+ override fun onPathResult(path: Path, functionName: String): Path {
+ return path.parent!! / path.name.reversed()
+ }
+ }
+
+ assertEquals(listOf(base / "az", base / "by", base / "cx"), fileSystem.list(base))
+ assertEquals(listOf(base / "xc", base / "yb", base / "za"), forwardingFileSystem.list(base))
+ }
+
+ @Test
+ fun copyIsNotForwarded() {
+ val log = mutableListOf<String>()
+
+ val delegate = object : ForwardingFileSystem(fileSystem) {
+ override fun copy(source: Path, target: Path) {
+ throw AssertionError("unexpected call to copy()")
+ }
+ }
+
+ val forwardingFileSystem = object : ForwardingFileSystem(delegate) {
+ override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path {
+ log += "$functionName($parameterName=$path)"
+ return path
+ }
+ }
+
+ val source = base / "source"
+ source.writeUtf8("hello, world!")
+ val target = base / "target"
+ forwardingFileSystem.copy(source, target)
+ assertTrue(target in fileSystem.list(base))
+ assertEquals("hello, world!", source.readUtf8())
+ assertEquals("hello, world!", target.readUtf8())
+
+ assertEquals(listOf("source(file=$source)", "sink(file=$target)"), log)
+ }
+
+ @Test
+ fun metadataForwardsParameterAndSymlinkTarget() {
+ val log = mutableListOf<String>()
+
+ val forwardingFileSystem = object : ForwardingFileSystem(fileSystem) {
+ override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path {
+ log += "$functionName($parameterName=$path)"
+ return path
+ }
+
+ override fun onPathResult(path: Path, functionName: String): Path {
+ log += "$functionName($path)"
+ return path
+ }
+ }
+
+ val target = base / "symlink-target"
+ val source = base / "symlink-source"
+
+ fileSystem.createSymlink(source, target)
+
+ val sourceMetadata = forwardingFileSystem.metadata(source)
+ assertEquals(target, sourceMetadata.symlinkTarget)
+
+ assertEquals(listOf("metadataOrNull(path=$source)", "metadataOrNull($target)"), log)
+ }
+}
diff --git a/okio/src/nonWasmTest/kotlin/okio/UseTest.kt b/okio/src/nonWasmTest/kotlin/okio/UseTest.kt
new file mode 100644
index 00000000..a36cf068
--- /dev/null
+++ b/okio/src/nonWasmTest/kotlin/okio/UseTest.kt
@@ -0,0 +1,28 @@
+package okio
+
+import kotlin.test.Test
+import okio.Path.Companion.toPath
+import okio.fakefilesystem.FakeFileSystem
+
+class UseTest {
+ val fakeFileSystem = FakeFileSystem(clock = FakeClock()).also { it.emulateUnix() }
+
+ val base = "/cache".toPath().also {
+ fakeFileSystem.createDirectories(it)
+ }
+
+ @Test
+ fun closesWithUseBlock() {
+ fun testMethodWithUse() {
+ val sink = fakeFileSystem.sink(base / "all-files-includes-file")
+
+ sink.use {
+ return@testMethodWithUse
+ }
+ }
+
+ testMethodWithUse()
+
+ fakeFileSystem.checkNoOpenFiles()
+ }
+}
diff --git a/okio/src/unixMain/kotlin/okio/UnixFileHandle.kt b/okio/src/unixMain/kotlin/okio/UnixFileHandle.kt
new file mode 100644
index 00000000..62e7849f
--- /dev/null
+++ b/okio/src/unixMain/kotlin/okio/UnixFileHandle.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.addressOf
+import kotlinx.cinterop.alloc
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.ptr
+import kotlinx.cinterop.usePinned
+import platform.posix.FILE
+import platform.posix.errno
+import platform.posix.fclose
+import platform.posix.fflush
+import platform.posix.fileno
+import platform.posix.fstat
+import platform.posix.ftruncate
+import platform.posix.stat
+
+internal class UnixFileHandle(
+ readWrite: Boolean,
+ private val file: CPointer<FILE>,
+) : FileHandle(readWrite) {
+ override fun protectedSize(): Long {
+ memScoped {
+ val stat = alloc<stat>()
+ if (fstat(fileno(file), stat.ptr) != 0) {
+ throw errnoToIOException(errno)
+ }
+ return stat.st_size
+ }
+ }
+
+ override fun protectedRead(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ): Int {
+ val bytesRead = if (array.isNotEmpty()) {
+ array.usePinned { pinned ->
+ variantPread(file, pinned.addressOf(arrayOffset), byteCount, fileOffset)
+ }
+ } else {
+ 0
+ }
+ if (bytesRead == -1) throw errnoToIOException(errno)
+ if (bytesRead == 0) return -1
+ return bytesRead
+ }
+
+ override fun protectedWrite(
+ fileOffset: Long,
+ array: ByteArray,
+ arrayOffset: Int,
+ byteCount: Int,
+ ) {
+ val bytesWritten = if (array.isNotEmpty()) {
+ array.usePinned { pinned ->
+ variantPwrite(file, pinned.addressOf(arrayOffset), byteCount, fileOffset)
+ }
+ } else {
+ 0
+ }
+ if (bytesWritten != byteCount) throw errnoToIOException(errno)
+ }
+
+ override fun protectedFlush() {
+ if (fflush(file) != 0) {
+ throw errnoToIOException(errno)
+ }
+ }
+
+ override fun protectedResize(size: Long) {
+ if (ftruncate(fileno(file), size) == -1) {
+ throw errnoToIOException(errno)
+ }
+ }
+
+ override fun protectedClose() {
+ if (fclose(file) != 0) {
+ throw errnoToIOException(errno)
+ }
+ }
+}
diff --git a/okio/src/unixMain/kotlin/okio/UnixPosixVariant.kt b/okio/src/unixMain/kotlin/okio/UnixPosixVariant.kt
new file mode 100644
index 00000000..27630d67
--- /dev/null
+++ b/okio/src/unixMain/kotlin/okio/UnixPosixVariant.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import kotlinx.cinterop.ByteVar
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.CValuesRef
+import kotlinx.cinterop.UnsafeNumber
+import kotlinx.cinterop.allocArray
+import kotlinx.cinterop.convert
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.toKString
+import okio.Path.Companion.toPath
+import okio.internal.toPath
+import platform.posix.DEFFILEMODE
+import platform.posix.ENOENT
+import platform.posix.FILE
+import platform.posix.O_CREAT
+import platform.posix.O_EXCL
+import platform.posix.O_RDWR
+import platform.posix.PATH_MAX
+import platform.posix.S_IFLNK
+import platform.posix.S_IFMT
+import platform.posix.errno
+import platform.posix.fdopen
+import platform.posix.fileno
+import platform.posix.fopen
+import platform.posix.free
+import platform.posix.getenv
+import platform.posix.mkdir
+import platform.posix.open
+import platform.posix.pread
+import platform.posix.pwrite
+import platform.posix.readlink
+import platform.posix.realpath
+import platform.posix.remove
+import platform.posix.rename
+import platform.posix.stat
+import platform.posix.symlink
+import platform.posix.timespec
+
+internal actual val PLATFORM_TEMPORARY_DIRECTORY: Path
+ get() {
+ val tmpdir = getenv("TMPDIR")
+ if (tmpdir != null) return tmpdir.toKString().toPath()
+
+ return "/tmp".toPath()
+ }
+
+internal actual val PLATFORM_DIRECTORY_SEPARATOR = "/"
+
+internal actual fun PosixFileSystem.variantDelete(path: Path, mustExist: Boolean) {
+ val result = remove(path.toString())
+ if (result != 0) {
+ if (errno == ENOENT) {
+ if (mustExist) {
+ throw FileNotFoundException("no such file: $path")
+ } else {
+ return
+ }
+ }
+ throw errnoToIOException(errno)
+ }
+}
+
+@OptIn(UnsafeNumber::class)
+internal actual fun PosixFileSystem.variantMkdir(dir: Path): Int {
+ return mkdir(dir.toString(), 0b111111111u.convert() /* octal 777 */)
+}
+
+internal actual fun PosixFileSystem.variantCanonicalize(path: Path): Path {
+ // Note that realpath() fails if the file doesn't exist.
+ val fullpath = realpath(path.toString(), null)
+ ?: throw errnoToIOException(errno)
+ try {
+ return Buffer().writeNullTerminated(fullpath).toPath(normalize = true)
+ } finally {
+ free(fullpath)
+ }
+}
+
+internal actual fun PosixFileSystem.variantMove(
+ source: Path,
+ target: Path,
+) {
+ val result = rename(source.toString(), target.toString())
+ if (result != 0) {
+ throw errnoToIOException(errno)
+ }
+}
+
+internal actual fun PosixFileSystem.variantSource(file: Path): Source {
+ val openFile: CPointer<FILE> = fopen(file.toString(), "r")
+ ?: throw errnoToIOException(errno)
+ return FileSource(openFile)
+}
+
+internal actual fun PosixFileSystem.variantSink(file: Path, mustCreate: Boolean): Sink {
+ val openFile: CPointer<FILE> = fopen(file.toString(), if (mustCreate) "wx" else "w")
+ ?: throw errnoToIOException(errno)
+ return FileSink(openFile)
+}
+
+internal actual fun PosixFileSystem.variantAppendingSink(file: Path, mustExist: Boolean): Sink {
+ // There is a `r+` flag which we could have used to force existence of [file] but this flag
+ // doesn't allow opening for appending, and we don't currently have a way to move the cursor to
+ // the end of the file. We are then forcing existence non-atomically.
+ if (mustExist && !exists(file)) throw IOException("$file doesn't exist.")
+ val openFile: CPointer<FILE> = fopen(file.toString(), "a")
+ ?: throw errnoToIOException(errno)
+ return FileSink(openFile)
+}
+
+internal actual fun PosixFileSystem.variantOpenReadOnly(file: Path): FileHandle {
+ val openFile: CPointer<FILE> = fopen(file.toString(), "r")
+ ?: throw errnoToIOException(errno)
+ return UnixFileHandle(false, openFile)
+}
+
+internal actual fun PosixFileSystem.variantOpenReadWrite(
+ file: Path,
+ mustCreate: Boolean,
+ mustExist: Boolean,
+): FileHandle {
+ // Note that we're using open() followed by fdopen() rather than fopen() because this way we
+ // can pass exactly the flags we want. Note that there's no string mode that opens for reading
+ // and writing that creates if necessary. ("a+" has features but can't do random access).
+ val flags = when {
+ mustCreate && mustExist ->
+ throw IllegalArgumentException("Cannot require mustCreate and mustExist at the same time.")
+ mustCreate -> O_RDWR or O_CREAT or O_EXCL
+ mustExist -> O_RDWR
+ else -> O_RDWR or O_CREAT
+ }
+
+ val fid = open(file.toString(), flags, DEFFILEMODE)
+ if (fid == -1) throw errnoToIOException(errno)
+
+ // Use 'r+' to get reading and writing on the FILE, which is all we need.
+ val openFile = fdopen(fid, "r+") ?: throw errnoToIOException(errno)
+
+ return UnixFileHandle(true, openFile)
+}
+
+internal actual fun PosixFileSystem.variantCreateSymlink(source: Path, target: Path) {
+ if (source.parent == null || !exists(source.parent!!)) {
+ throw IOException("parent directory does not exist: ${source.parent}")
+ }
+
+ if (exists(source)) {
+ throw IOException("already exists: $source")
+ }
+
+ val result = symlink(target.toString(), source.toString())
+ if (result != 0) {
+ throw errnoToIOException(errno)
+ }
+}
+
+@OptIn(UnsafeNumber::class)
+internal fun variantPread(
+ file: CPointer<FILE>,
+ target: CValuesRef<*>,
+ byteCount: Int,
+ offset: Long,
+): Int = pread(fileno(file), target, byteCount.convert(), offset).convert()
+
+@OptIn(UnsafeNumber::class)
+internal fun variantPwrite(
+ file: CPointer<FILE>,
+ source: CValuesRef<*>,
+ byteCount: Int,
+ offset: Long,
+): Int = pwrite(fileno(file), source, byteCount.convert(), offset).convert()
+
+@OptIn(UnsafeNumber::class)
+internal val timespec.epochMillis: Long
+ get() = tv_sec * 1000L + tv_sec / 1_000_000L
+
+@OptIn(UnsafeNumber::class)
+internal fun symlinkTarget(stat: stat, path: Path): Path? {
+ if (stat.st_mode.toInt() and S_IFMT != S_IFLNK) return null
+
+ // `path` is a symlink, let's resolve its target.
+ memScoped {
+ val buffer = allocArray<ByteVar>(PATH_MAX)
+ val byteCount = readlink(path.toString(), buffer, PATH_MAX.convert())
+ if (byteCount.convert<Int>() == -1) {
+ throw errnoToIOException(errno)
+ }
+ return buffer.toKString().toPath()
+ }
+}
diff --git a/okio/src/wasmMain/kotlin/okio/FileSystem.kt b/okio/src/wasmMain/kotlin/okio/FileSystem.kt
new file mode 100644
index 00000000..2152a91b
--- /dev/null
+++ b/okio/src/wasmMain/kotlin/okio/FileSystem.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import okio.Path.Companion.toPath
+import okio.internal.commonCopy
+import okio.internal.commonCreateDirectories
+import okio.internal.commonDeleteRecursively
+import okio.internal.commonExists
+import okio.internal.commonListRecursively
+import okio.internal.commonMetadata
+
+actual abstract class FileSystem {
+ actual abstract fun canonicalize(path: Path): Path
+
+ actual fun metadata(path: Path): FileMetadata = commonMetadata(path)
+
+ actual abstract fun metadataOrNull(path: Path): FileMetadata?
+
+ actual fun exists(path: Path): Boolean = commonExists(path)
+
+ actual abstract fun list(dir: Path): List<Path>
+
+ actual abstract fun listOrNull(dir: Path): List<Path>?
+
+ actual open fun listRecursively(dir: Path, followSymlinks: Boolean): Sequence<Path> =
+ commonListRecursively(dir, followSymlinks)
+
+ actual abstract fun openReadOnly(file: Path): FileHandle
+
+ actual abstract fun openReadWrite(
+ file: Path,
+ mustCreate: Boolean,
+ mustExist: Boolean,
+ ): FileHandle
+
+ actual abstract fun source(file: Path): Source
+
+ actual inline fun <T> read(file: Path, readerAction: BufferedSource.() -> T): T {
+ return source(file).buffer().use {
+ it.readerAction()
+ }
+ }
+
+ actual abstract fun sink(file: Path, mustCreate: Boolean): Sink
+
+ actual inline fun <T> write(
+ file: Path,
+ mustCreate: Boolean,
+ writerAction: BufferedSink.() -> T,
+ ): T {
+ return sink(file, mustCreate).buffer().use {
+ it.writerAction()
+ }
+ }
+
+ actual abstract fun appendingSink(file: Path, mustExist: Boolean): Sink
+
+ actual abstract fun createDirectory(dir: Path, mustCreate: Boolean)
+
+ actual fun createDirectories(dir: Path, mustCreate: Boolean): Unit = commonCreateDirectories(dir, mustCreate)
+
+ actual abstract fun atomicMove(source: Path, target: Path)
+
+ actual open fun copy(source: Path, target: Path): Unit = commonCopy(source, target)
+
+ actual abstract fun delete(path: Path, mustExist: Boolean)
+
+ actual open fun deleteRecursively(fileOrDirectory: Path, mustExist: Boolean): Unit =
+ commonDeleteRecursively(fileOrDirectory, mustExist)
+
+ actual abstract fun createSymlink(source: Path, target: Path)
+
+ actual companion object {
+ actual val SYSTEM_TEMPORARY_DIRECTORY: Path = "/tmp".toPath()
+ }
+}
diff --git a/okio/src/wasmMain/kotlin/okio/WasmPlatform.kt b/okio/src/wasmMain/kotlin/okio/WasmPlatform.kt
new file mode 100644
index 00000000..4a874ec8
--- /dev/null
+++ b/okio/src/wasmMain/kotlin/okio/WasmPlatform.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+internal actual val PLATFORM_DIRECTORY_SEPARATOR: String
+ get() = "/"
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 00000000..1a2efbb4
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": [
+ "config:base"
+ ],
+ "semanticCommits": "disabled"
+}
diff --git a/samples/build.gradle b/samples/build.gradle
deleted file mode 100644
index efaef19e..00000000
--- a/samples/build.gradle
+++ /dev/null
@@ -1,23 +0,0 @@
-apply plugin: 'org.jetbrains.kotlin.multiplatform'
-apply plugin: 'application'
-
-mainClassName = System.getProperty("mainClass")
-
-kotlin {
- jvm {
- withJava()
- }
- sourceSets {
- commonMain {
- dependencies {
- implementation project(':okio')
- }
- }
- jvmTest {
- dependencies {
- implementation deps.test.junit
- implementation deps.test.assertj
- }
- }
- }
-}
diff --git a/samples/build.gradle.kts b/samples/build.gradle.kts
new file mode 100644
index 00000000..8fb0952e
--- /dev/null
+++ b/samples/build.gradle.kts
@@ -0,0 +1,27 @@
+plugins {
+ kotlin("multiplatform")
+ application
+}
+
+application {
+ mainClass.set(System.getProperty("mainClass"))
+}
+
+kotlin {
+ jvm {
+ withJava()
+ }
+ sourceSets {
+ commonMain {
+ dependencies {
+ implementation(projects.okio)
+ }
+ }
+ val jvmTest by getting {
+ dependencies {
+ implementation(libs.test.junit)
+ implementation(libs.test.assertj)
+ }
+ }
+ }
+}
diff --git a/samples/src/jvmMain/java/okio/samples/BitmapEncoder.java b/samples/src/jvmMain/java/okio/samples/BitmapEncoder.java
index a505f6dc..8642170e 100644
--- a/samples/src/jvmMain/java/okio/samples/BitmapEncoder.java
+++ b/samples/src/jvmMain/java/okio/samples/BitmapEncoder.java
@@ -15,10 +15,11 @@
*/
package okio.samples;
-import java.io.File;
import java.io.IOException;
import okio.BufferedSink;
+import okio.FileSystem;
import okio.Okio;
+import okio.Path;
public final class BitmapEncoder {
static final class Bitmap {
@@ -66,8 +67,8 @@ public final class BitmapEncoder {
return new Bitmap(pixels);
}
- void encode(Bitmap bitmap, File file) throws IOException {
- try (BufferedSink sink = Okio.buffer(Okio.sink(file))) {
+ void encode(Bitmap bitmap, FileSystem fileSystem, Path path) throws IOException {
+ try (BufferedSink sink = Okio.buffer(fileSystem.sink(path))) {
encode(bitmap, sink);
}
}
@@ -122,6 +123,6 @@ public final class BitmapEncoder {
public static void main(String[] args) throws Exception {
BitmapEncoder encoder = new BitmapEncoder();
Bitmap bitmap = encoder.generateGradient();
- encoder.encode(bitmap, new File("gradient.bmp"));
+ encoder.encode(bitmap, FileSystem.SYSTEM, Path.get("gradient.bmp"));
}
}
diff --git a/samples/src/jvmMain/java/okio/samples/Hashing.java b/samples/src/jvmMain/java/okio/samples/Hashing.java
index 0f2b4474..66d99ba6 100644
--- a/samples/src/jvmMain/java/okio/samples/Hashing.java
+++ b/samples/src/jvmMain/java/okio/samples/Hashing.java
@@ -15,23 +15,24 @@
*/
package okio.samples;
-import java.io.File;
import java.io.IOException;
import okio.Buffer;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.ByteString;
+import okio.FileSystem;
import okio.HashingSink;
import okio.HashingSource;
import okio.Okio;
+import okio.Path;
import okio.Source;
public final class Hashing {
public void run() throws Exception {
- File file = new File("../README.md");
+ Path path = Path.get("../README.md");
System.out.println("ByteString");
- ByteString byteString = readByteString(file);
+ ByteString byteString = readByteString(path);
System.out.println(" md5: " + byteString.md5().hex());
System.out.println(" sha1: " + byteString.sha1().hex());
System.out.println(" sha256: " + byteString.sha256().hex());
@@ -39,7 +40,7 @@ public final class Hashing {
System.out.println();
System.out.println("Buffer");
- Buffer buffer = readBuffer(file);
+ Buffer buffer = readBuffer(path);
System.out.println(" md5: " + buffer.md5().hex());
System.out.println(" sha1: " + buffer.sha1().hex());
System.out.println(" sha256: " + buffer.sha256().hex());
@@ -47,7 +48,7 @@ public final class Hashing {
System.out.println();
System.out.println("HashingSource");
- try (HashingSource hashingSource = HashingSource.sha256(Okio.source(file));
+ try (HashingSource hashingSource = HashingSource.sha256(FileSystem.SYSTEM.source(path));
BufferedSource source = Okio.buffer(hashingSource)) {
source.readAll(Okio.blackhole());
System.out.println(" sha256: " + hashingSource.hash().hex());
@@ -57,7 +58,7 @@ public final class Hashing {
System.out.println("HashingSink");
try (HashingSink hashingSink = HashingSink.sha256(Okio.blackhole());
BufferedSink sink = Okio.buffer(hashingSink);
- Source source = Okio.source(file)) {
+ Source source = FileSystem.SYSTEM.source(path)) {
sink.writeAll(source);
sink.close(); // Emit anything buffered.
System.out.println(" sha256: " + hashingSink.hash().hex());
@@ -70,14 +71,14 @@ public final class Hashing {
System.out.println();
}
- public ByteString readByteString(File file) throws IOException {
- try (BufferedSource source = Okio.buffer(Okio.source(file))) {
+ public ByteString readByteString(Path path) throws IOException {
+ try (BufferedSource source = Okio.buffer(FileSystem.SYSTEM.source(path))) {
return source.readByteString();
}
}
- public Buffer readBuffer(File file) throws IOException {
- try (Source source = Okio.source(file)) {
+ public Buffer readBuffer(Path path) throws IOException {
+ try (Source source = FileSystem.SYSTEM.source(path)) {
Buffer buffer = new Buffer();
buffer.writeAll(source);
return buffer;
diff --git a/samples/src/jvmMain/java/okio/samples/ReadFileLineByLine.java b/samples/src/jvmMain/java/okio/samples/ReadFileLineByLine.java
index b195a513..010319ef 100644
--- a/samples/src/jvmMain/java/okio/samples/ReadFileLineByLine.java
+++ b/samples/src/jvmMain/java/okio/samples/ReadFileLineByLine.java
@@ -15,19 +15,20 @@
*/
package okio.samples;
-import java.io.File;
import java.io.IOException;
import okio.BufferedSource;
+import okio.FileSystem;
import okio.Okio;
+import okio.Path;
import okio.Source;
public final class ReadFileLineByLine {
public void run() throws Exception {
- readLines(new File("../README.md"));
+ readLines(Path.get("../README.md"));
}
- public void readLines(File file) throws IOException {
- try (Source fileSource = Okio.source(file);
+ public void readLines(Path path) throws IOException {
+ try (Source fileSource = FileSystem.SYSTEM.source(path);
BufferedSource bufferedFileSource = Okio.buffer(fileSource)) {
while (true) {
diff --git a/samples/src/jvmMain/java/okio/samples/ReadFileLineByLine.kt b/samples/src/jvmMain/java/okio/samples/ReadFileLineByLine.kt
new file mode 100644
index 00000000..8dd573a6
--- /dev/null
+++ b/samples/src/jvmMain/java/okio/samples/ReadFileLineByLine.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.samples
+
+import okio.FileSystem
+import okio.IOException
+import okio.Path
+import okio.Path.Companion.toPath
+import okio.buffer
+
+@Throws(IOException::class)
+fun readLines(path: Path) {
+ FileSystem.SYSTEM.source(path).use { fileSource ->
+ fileSource.buffer().use { bufferedFileSource ->
+ while (true) {
+ val line = bufferedFileSource.readUtf8Line() ?: break
+ if ("square" in line) {
+ println(line)
+ }
+ }
+ }
+ }
+}
+
+fun main() {
+ readLines("../README.md".toPath())
+}
diff --git a/samples/src/jvmMain/java/okio/samples/ReadJavaIoFileLineByLine.java b/samples/src/jvmMain/java/okio/samples/ReadJavaIoFileLineByLine.java
new file mode 100644
index 00000000..6479524d
--- /dev/null
+++ b/samples/src/jvmMain/java/okio/samples/ReadJavaIoFileLineByLine.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.samples;
+
+import java.io.File;
+import java.io.IOException;
+import okio.BufferedSource;
+import okio.Okio;
+import okio.Source;
+
+public final class ReadJavaIoFileLineByLine {
+ public void run() throws Exception {
+ readLines(new File("../README.md"));
+ }
+
+ public void readLines(File file) throws IOException {
+ try (Source fileSource = Okio.source(file);
+ BufferedSource bufferedFileSource = Okio.buffer(fileSource)) {
+
+ while (true) {
+ String line = bufferedFileSource.readUtf8Line();
+ if (line == null) break;
+
+ if (line.contains("square")) {
+ System.out.println(line);
+ }
+ }
+
+ }
+ }
+
+ public static void main(String... args) throws Exception {
+ new ReadJavaIoFileLineByLine().run();
+ }
+}
diff --git a/samples/src/jvmMain/java/okio/samples/WriteFile.java b/samples/src/jvmMain/java/okio/samples/WriteFile.java
index e613abe5..c71e912a 100644
--- a/samples/src/jvmMain/java/okio/samples/WriteFile.java
+++ b/samples/src/jvmMain/java/okio/samples/WriteFile.java
@@ -15,20 +15,21 @@
*/
package okio.samples;
-import java.io.File;
import java.io.IOException;
import java.util.Map;
import okio.BufferedSink;
+import okio.FileSystem;
import okio.Okio;
+import okio.Path;
import okio.Sink;
public final class WriteFile {
public void run() throws Exception {
- writeEnv(new File("env.txt"));
+ writeEnv(Path.get("env.txt"));
}
- public void writeEnv(File file) throws IOException {
- try (Sink fileSink = Okio.sink(file);
+ public void writeEnv(Path path) throws IOException {
+ try (Sink fileSink = FileSystem.SYSTEM.sink(path);
BufferedSink bufferedSink = Okio.buffer(fileSink)) {
for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
diff --git a/samples/src/jvmMain/java/okio/samples/WriteJavaIoFile.java b/samples/src/jvmMain/java/okio/samples/WriteJavaIoFile.java
new file mode 100644
index 00000000..d2b0502a
--- /dev/null
+++ b/samples/src/jvmMain/java/okio/samples/WriteJavaIoFile.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.samples;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import okio.BufferedSink;
+import okio.Okio;
+import okio.Sink;
+
+public final class WriteJavaIoFile {
+ public void run() throws Exception {
+ writeEnv(new File("env.txt"));
+ }
+
+ public void writeEnv(File file) throws IOException {
+ try (Sink fileSink = Okio.sink(file);
+ BufferedSink bufferedSink = Okio.buffer(fileSink)) {
+
+ for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
+ bufferedSink.writeUtf8(entry.getKey());
+ bufferedSink.writeUtf8("=");
+ bufferedSink.writeUtf8(entry.getValue());
+ bufferedSink.writeUtf8("\n");
+ }
+
+ }
+ }
+
+ public static void main(String... args) throws Exception {
+ new WriteFile().run();
+ }
+}
diff --git a/samples/src/jvmMain/kotlin/okio/samples/BitmapEncoder.kt b/samples/src/jvmMain/kotlin/okio/samples/BitmapEncoder.kt
index 86422832..e23b3261 100644
--- a/samples/src/jvmMain/kotlin/okio/samples/BitmapEncoder.kt
+++ b/samples/src/jvmMain/kotlin/okio/samples/BitmapEncoder.kt
@@ -15,16 +15,16 @@
*/
package okio.samples
-import okio.BufferedSink
-import okio.buffer
-import okio.sink
-import java.io.File
-import java.io.IOException
import kotlin.math.hypot
+import okio.BufferedSink
+import okio.FileSystem
+import okio.IOException
+import okio.Path
+import okio.Path.Companion.toPath
class KotlinBitmapEncoder {
class Bitmap(
- private val pixels: Array<IntArray>
+ private val pixels: Array<IntArray>,
) {
val width: Int = pixels[0].size
val height: Int = pixels.size
@@ -54,8 +54,8 @@ class KotlinBitmapEncoder {
}
@Throws(IOException::class)
- fun encode(bitmap: Bitmap, file: File) {
- file.sink().buffer().use { sink -> encode(bitmap, sink) }
+ fun encode(bitmap: Bitmap, fileSystem: FileSystem, path: Path) {
+ fileSystem.write(path) { encode(bitmap, this) }
}
/** https://en.wikipedia.org/wiki/BMP_file_format */
@@ -109,5 +109,5 @@ class KotlinBitmapEncoder {
fun main() {
val encoder = KotlinBitmapEncoder()
val bitmap = encoder.generateGradient()
- encoder.encode(bitmap, File("gradient.bmp"))
+ encoder.encode(bitmap, FileSystem.SYSTEM, "gradient.bmp".toPath())
}
diff --git a/samples/src/jvmMain/kotlin/okio/samples/ExploreCharsets.kt b/samples/src/jvmMain/kotlin/okio/samples/ExploreCharsets.kt
index 824a01ed..18744831 100644
--- a/samples/src/jvmMain/kotlin/okio/samples/ExploreCharsets.kt
+++ b/samples/src/jvmMain/kotlin/okio/samples/ExploreCharsets.kt
@@ -15,9 +15,9 @@
*/
package okio.samples
+import java.io.IOException
import okio.ByteString.Companion.encodeUtf8
import okio.utf8Size
-import java.io.IOException
@Throws(IOException::class)
fun dumpStringData(s: String) {
diff --git a/samples/src/jvmMain/kotlin/okio/samples/GoldenValue.kt b/samples/src/jvmMain/kotlin/okio/samples/GoldenValue.kt
index 2e86ff74..aeb5aeca 100644
--- a/samples/src/jvmMain/kotlin/okio/samples/GoldenValue.kt
+++ b/samples/src/jvmMain/kotlin/okio/samples/GoldenValue.kt
@@ -15,13 +15,13 @@
*/
package okio.samples
-import okio.Buffer
-import okio.ByteString
-import okio.ByteString.Companion.decodeBase64
import java.io.IOException
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
+import okio.Buffer
+import okio.ByteString
+import okio.ByteString.Companion.decodeBase64
class KotlinGoldenValue {
fun run() {
@@ -60,7 +60,7 @@ class KotlinGoldenValue {
private fun assertEquals(
a: Point,
- b: Point
+ b: Point,
) {
if (a.x != b.x || a.y != b.y) throw AssertionError()
}
diff --git a/samples/src/jvmMain/kotlin/okio/samples/Hashing.kt b/samples/src/jvmMain/kotlin/okio/samples/Hashing.kt
index 3169d4e1..1584d8b4 100644
--- a/samples/src/jvmMain/kotlin/okio/samples/Hashing.kt
+++ b/samples/src/jvmMain/kotlin/okio/samples/Hashing.kt
@@ -15,23 +15,24 @@
*/
package okio.samples
+import java.io.IOException
import okio.Buffer
import okio.ByteString
import okio.ByteString.Companion.decodeHex
+import okio.FileSystem
import okio.HashingSink.Companion.sha256
import okio.HashingSource.Companion.sha256
+import okio.Path
+import okio.Path.Companion.toPath
import okio.blackholeSink
import okio.buffer
-import okio.source
-import java.io.File
-import java.io.IOException
class KotlinHashing {
fun run() {
- val file = File("../README.md")
+ val path = "../README.md".toPath()
println("ByteString")
- val byteString = readByteString(file)
+ val byteString = readByteString(path)
println(" md5: " + byteString.md5().hex())
println(" sha1: " + byteString.sha1().hex())
println(" sha256: " + byteString.sha256().hex())
@@ -39,7 +40,7 @@ class KotlinHashing {
println()
println("Buffer")
- val buffer = readBuffer(file)
+ val buffer = readBuffer(path)
println(" md5: " + buffer.md5().hex())
println(" sha1: " + buffer.sha1().hex())
println(" sha256: " + buffer.sha256().hex())
@@ -47,7 +48,7 @@ class KotlinHashing {
println()
println("HashingSource")
- sha256(file.source()).use { hashingSource ->
+ sha256(FileSystem.SYSTEM.source(path)).use { hashingSource ->
hashingSource.buffer().use { source ->
source.readAll(blackholeSink())
println(" sha256: " + hashingSource.hash.hex())
@@ -58,7 +59,7 @@ class KotlinHashing {
println("HashingSink")
sha256(blackholeSink()).use { hashingSink ->
hashingSink.buffer().use { sink ->
- file.source().use { source ->
+ FileSystem.SYSTEM.source(path).use { source ->
sink.writeAll(source)
sink.close() // Emit anything buffered.
println(" sha256: " + hashingSink.hash.hex())
@@ -74,14 +75,16 @@ class KotlinHashing {
}
@Throws(IOException::class)
- fun readByteString(file: File): ByteString {
- return file.source().buffer().use { it.readByteString() }
+ fun readByteString(path: Path): ByteString {
+ return FileSystem.SYSTEM.read(path) { readByteString() }
}
@Throws(IOException::class)
- fun readBuffer(file: File): Buffer {
- return file.source().use { source ->
- Buffer().also { it.writeAll(source) }
+ fun readBuffer(path: Path): Buffer {
+ FileSystem.SYSTEM.read(path) {
+ val result = Buffer()
+ readAll(result)
+ return result
}
}
}
diff --git a/samples/src/jvmMain/kotlin/okio/samples/ReadFileLineByLine.kt b/samples/src/jvmMain/kotlin/okio/samples/ReadJavaIoFileLineByLine.kt
index b3fa31ba..05cdd6d3 100644
--- a/samples/src/jvmMain/kotlin/okio/samples/ReadFileLineByLine.kt
+++ b/samples/src/jvmMain/kotlin/okio/samples/ReadJavaIoFileLineByLine.kt
@@ -15,10 +15,10 @@
*/
package okio.samples
-import okio.buffer
-import okio.source
import java.io.File
import java.io.IOException
+import okio.buffer
+import okio.source
@Throws(IOException::class)
fun readLines(file: File) {
diff --git a/samples/src/jvmMain/kotlin/okio/samples/SocksProxyServer.kt b/samples/src/jvmMain/kotlin/okio/samples/SocksProxyServer.kt
index d3b786a1..f68811b9 100644
--- a/samples/src/jvmMain/kotlin/okio/samples/SocksProxyServer.kt
+++ b/samples/src/jvmMain/kotlin/okio/samples/SocksProxyServer.kt
@@ -15,13 +15,6 @@
*/
package okio.samples
-import okio.Buffer
-import okio.BufferedSink
-import okio.Sink
-import okio.Source
-import okio.buffer
-import okio.sink
-import okio.source
import java.io.IOException
import java.net.InetAddress
import java.net.InetSocketAddress
@@ -33,6 +26,13 @@ import java.net.URL
import java.util.Collections
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
+import okio.Buffer
+import okio.BufferedSink
+import okio.Sink
+import okio.Source
+import okio.buffer
+import okio.sink
+import okio.source
private const val VERSION_5 = 5
private const val METHOD_NO_AUTHENTICATION_REQUIRED = 0
@@ -64,7 +64,7 @@ class KotlinSocksProxyServer {
fun proxy(): Proxy = Proxy(
Proxy.Type.SOCKS,
- InetSocketAddress.createUnresolved("localhost", serverSocket.localPort)
+ InetSocketAddress.createUnresolved("localhost", serverSocket.localPort),
)
private fun acceptSockets() {
diff --git a/samples/src/jvmMain/kotlin/okio/samples/WriteFile.kt b/samples/src/jvmMain/kotlin/okio/samples/WriteFile.kt
index 56726932..516e9f67 100644
--- a/samples/src/jvmMain/kotlin/okio/samples/WriteFile.kt
+++ b/samples/src/jvmMain/kotlin/okio/samples/WriteFile.kt
@@ -15,14 +15,15 @@
*/
package okio.samples
+import okio.FileSystem
+import okio.IOException
+import okio.Path
+import okio.Path.Companion.toPath
import okio.buffer
-import okio.sink
-import java.io.File
-import java.io.IOException
@Throws(IOException::class)
-fun writeEnv(file: File) {
- file.sink().buffer().use { sink ->
+fun writeEnv(path: Path) {
+ FileSystem.SYSTEM.sink(path).buffer().use { sink ->
for ((key, value) in System.getenv()) {
sink.writeUtf8(key)
sink.writeUtf8("=")
@@ -33,5 +34,5 @@ fun writeEnv(file: File) {
}
fun main() {
- writeEnv(File("env.txt"))
+ writeEnv("env.txt".toPath())
}
diff --git a/samples/src/jvmMain/kotlin/okio/samples/WriteJavaIoFile.kt b/samples/src/jvmMain/kotlin/okio/samples/WriteJavaIoFile.kt
new file mode 100644
index 00000000..9981462a
--- /dev/null
+++ b/samples/src/jvmMain/kotlin/okio/samples/WriteJavaIoFile.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio.samples
+
+import java.io.File
+import java.io.IOException
+import okio.buffer
+import okio.sink
+
+@Throws(IOException::class)
+fun writeEnv(file: File) {
+ file.sink().buffer().use { sink ->
+ for ((key, value) in System.getenv()) {
+ sink.writeUtf8(key)
+ sink.writeUtf8("=")
+ sink.writeUtf8(value)
+ sink.writeUtf8("\n")
+ }
+ }
+}
+
+fun main() {
+ writeEnv(File("env.txt"))
+}
diff --git a/samples/src/jvmTest/java/okio/samples/ChannelsTest.java b/samples/src/jvmTest/java/okio/samples/ChannelsTest.java
index 8c497475..6b3042b0 100644
--- a/samples/src/jvmTest/java/okio/samples/ChannelsTest.java
+++ b/samples/src/jvmTest/java/okio/samples/ChannelsTest.java
@@ -18,7 +18,6 @@ package okio.samples;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
-import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;
import java.util.Set;
@@ -84,7 +83,7 @@ public final class ChannelsTest {
}
@Test public void testReadWriteFile() throws Exception {
- Path path = temporaryFolder.newFile().toPath();
+ java.nio.file.Path path = temporaryFolder.newFile().toPath();
Sink sink = new FileChannelSink(FileChannel.open(path, w), Timeout.NONE);
sink.write(new Buffer().writeUtf8(quote), 317);
@@ -105,7 +104,7 @@ public final class ChannelsTest {
}
@Test public void testAppend() throws Exception {
- Path path = temporaryFolder.newFile().toPath();
+ java.nio.file.Path path = temporaryFolder.newFile().toPath();
Buffer buffer = new Buffer().writeUtf8(quote);
Sink sink;
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index 97f9d9e9..00000000
--- a/settings.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-rootProject.name = 'okio-parent'
-
-include ':okio'
-include ':okio:jvm:japicmp'
-include ':okio:jvm:jmh'
-include ':samples'
-
-enableFeaturePreview("GRADLE_METADATA")
-
-// The Android test module doesn't work in IntelliJ. Use Android Studio or the command line.
-if (properties.containsKey('android.injected.invoked.from.ide') ||
- System.getenv('ANDROID_SDK_ROOT') != null) {
- include ':android-test'
-}
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 00000000..b07a4b81
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,25 @@
+rootProject.name = "okio-parent"
+
+includeBuild("build-support")
+
+include(":okio")
+include(":okio-assetfilesystem")
+include(":okio-bom")
+include(":okio-fakefilesystem")
+if (System.getProperty("kjs", "true").toBoolean()) {
+ include(":okio-nodefilesystem")
+}
+include(":okio-testing-support")
+include(":okio:jvm:jmh")
+if (System.getProperty("kwasm", "true").toBoolean()) {
+ include(":okio-wasifilesystem")
+}
+include(":samples")
+
+// The Android test module doesn't work in IntelliJ. Use Android Studio or the command line.
+if (System.getProperties().containsKey("android.injected.invoked.from.ide") ||
+ System.getenv("ANDROID_SDK_ROOT") != null) {
+ include(":android-test")
+}
+
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")