Skip to content

Commit

Permalink
Initial APIs for FileSystem extensions
Browse files Browse the repository at this point in the history
Closes: #1466
  • Loading branch information
swankjesse committed Apr 15, 2024
1 parent a647246 commit 07d0c7c
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 2 deletions.
2 changes: 1 addition & 1 deletion okio/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ kotlin {
dependencies {
implementation(libs.kotlin.test)
implementation(projects.okioTestingSupport)
implementation(projects.okioFakefilesystem)
}
}

Expand All @@ -80,7 +81,6 @@ kotlin {
val nonWasmTest by creating {
dependencies {
implementation(libs.kotlin.time)
implementation(projects.okioFakefilesystem)
}
}

Expand Down
6 changes: 6 additions & 0 deletions okio/src/commonMain/kotlin/okio/FileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package okio

import kotlin.reflect.KClass

/**
* 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.
Expand Down Expand Up @@ -376,6 +378,10 @@ expect abstract class FileSystem() {
@Throws(IOException::class)
abstract fun createSymlink(source: Path, target: Path)

open fun <E : FileSystemExtension> extend(extensionType: KClass<E>, extension: E): FileSystem

open fun <E : FileSystemExtension> extension(type: KClass<E>): E?

companion object {
/**
* Returns a writable temporary directory on [SYSTEM].
Expand Down
21 changes: 21 additions & 0 deletions okio/src/commonMain/kotlin/okio/FileSystemExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2024 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

/**
*
*/
interface FileSystemExtension
18 changes: 17 additions & 1 deletion okio/src/commonMain/kotlin/okio/ForwardingFileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
package okio

import kotlin.jvm.JvmName
import kotlin.reflect.KClass
import kotlin.reflect.cast

/**
* A [FileSystem] that forwards calls to another, intended for subclassing.
Expand Down Expand Up @@ -101,11 +103,15 @@ import kotlin.jvm.JvmName
* other functions of this class. If desired, subclasses may override non-abstract functions to
* forward them.
*/
abstract class ForwardingFileSystem(
open class ForwardingFileSystem internal constructor(
/** [FileSystem] to which this instance is delegating. */
@get:JvmName("delegate")
val delegate: FileSystem,
extensions: Map<KClass<*>, Any>,
) : FileSystem() {
internal val extensions = extensions.toMap()

constructor(delegate: FileSystem) : this(delegate, emptyMap())

/**
* Invoked each time a path is passed as a parameter to this file system. This returns the path to
Expand Down Expand Up @@ -142,6 +148,8 @@ abstract class ForwardingFileSystem(
*/
open fun onPathResult(path: Path, functionName: String): Path = path

open fun <T : Any> onExtension(type: KClass<T>, extension: T): T = extension

@Throws(IOException::class)
override fun canonicalize(path: Path): Path {
val path = onPathParameter(path, "canonicalize", "path")
Expand Down Expand Up @@ -238,5 +246,13 @@ abstract class ForwardingFileSystem(
delegate.createSymlink(source, target)
}

override fun <E : FileSystemExtension> extension(type: KClass<E>): E? {
val result = extensions[type]?.let { type.cast(it) }
?: delegate.extension(type)
?: return null

return onExtension(type, result)
}

override fun toString() = "${this::class.simpleName}($delegate)"
}
8 changes: 8 additions & 0 deletions okio/src/commonMain/kotlin/okio/Okio.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,11 @@ inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
@Suppress("UNCHECKED_CAST")
return result as R
}

inline fun <reified E : FileSystemExtension> FileSystem.extend(extension: E): FileSystem {
return extend(E::class, extension)
}

inline fun <reified E : FileSystemExtension> FileSystem.extension(): E? {
return extension(E::class)
}
19 changes: 19 additions & 0 deletions okio/src/commonMain/kotlin/okio/internal/FileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
package okio.internal

import kotlin.jvm.JvmName
import kotlin.reflect.KClass
import okio.FileMetadata
import okio.FileNotFoundException
import okio.FileSystem
import okio.ForwardingFileSystem
import okio.IOException
import okio.Path
import okio.buffer
Expand Down Expand Up @@ -69,6 +71,23 @@ internal fun FileSystem.commonCopy(source: Path, target: Path) {
}
}

fun <T : Any> FileSystem.commonExtend(extensionType: KClass<T>, extension: T): FileSystem {
// If this file system is already an extension wrapper, replace it rather than wrapping again.
// Note that this optimization doesn't apply to ForwardingFileSystem subclasses, only to the
// ForwardingFileSystem base class.
if (this::class === ForwardingFileSystem::class) {
this as ForwardingFileSystem
val newExtensions = extensions.toMutableMap()
newExtensions[extensionType] = extension
return ForwardingFileSystem(delegate, newExtensions)
}

return ForwardingFileSystem(
delegate = this,
extensions = mapOf(extensionType to extension)
)
}

@Throws(IOException::class)
internal fun FileSystem.commonDeleteRecursively(fileOrDirectory: Path, mustExist: Boolean) {
val sequence = sequence {
Expand Down
55 changes: 55 additions & 0 deletions okio/src/commonTest/kotlin/okio/FileSystemExtensionsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2024 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.fakefilesystem.FakeFileSystem

class FileSystemExtensionsTest {
@Test
fun happyPath() {
val fakeFileSystem = FakeFileSystem()
val extension = FooExtension(fakeFileSystem)
val fileSystemWithExtension = fakeFileSystem.extend(extension)
assertEquals(fileSystemWithExtension.extension<FooExtension>(), extension)
}

@Test
fun absentExtension() {
}

@Test
fun overrideExtension() {
}

@Test
fun forwardingFileSystemCoalesced() {
}

@Test
fun customForwardingFileSystemNotCoalesced() {
}

class FooExtension(
val target: FileSystem,
) : FileSystemExtension

class BarExtension(
val target: FileSystem,
) : FileSystemExtension
}
9 changes: 9 additions & 0 deletions okio/src/jsMain/kotlin/okio/FileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
*/
package okio

import kotlin.reflect.KClass
import okio.Path.Companion.toPath
import okio.internal.commonCopy
import okio.internal.commonCreateDirectories
import okio.internal.commonDeleteRecursively
import okio.internal.commonExists
import okio.internal.commonExtend
import okio.internal.commonListRecursively
import okio.internal.commonMetadata

Expand Down Expand Up @@ -84,6 +86,13 @@ actual abstract class FileSystem {

actual abstract fun createSymlink(source: Path, target: Path)

actual open fun <E : FileSystemExtension> extend(
extensionType: KClass<E>,
extension: E,
): FileSystem = commonExtend(extensionType, extension)

actual open fun <E : FileSystemExtension> extension(type: KClass<E>): E? = null

actual companion object {
actual val SYSTEM_TEMPORARY_DIRECTORY: Path = tmpdir.toPath()
}
Expand Down
9 changes: 9 additions & 0 deletions okio/src/jvmMain/kotlin/okio/FileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
package okio

import java.nio.file.FileSystem as JavaNioFileSystem
import kotlin.reflect.KClass
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.commonExtend
import okio.internal.commonListRecursively
import okio.internal.commonMetadata

Expand Down Expand Up @@ -125,6 +127,13 @@ actual abstract class FileSystem {
@Throws(IOException::class)
actual abstract fun createSymlink(source: Path, target: Path)

actual open fun <E : FileSystemExtension> extend(
extensionType: KClass<E>,
extension: E,
): FileSystem = commonExtend(extensionType, extension)

actual open fun <E : FileSystemExtension> extension(type: KClass<E>): E? = null

actual companion object {
/**
* The current process's host file system. Use this instance directly, or dependency inject a
Expand Down
9 changes: 9 additions & 0 deletions okio/src/nativeMain/kotlin/okio/FileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
*/
package okio

import kotlin.reflect.KClass
import okio.internal.commonCopy
import okio.internal.commonCreateDirectories
import okio.internal.commonDeleteRecursively
import okio.internal.commonExists
import okio.internal.commonExtend
import okio.internal.commonListRecursively
import okio.internal.commonMetadata

Expand Down Expand Up @@ -102,6 +104,13 @@ actual abstract class FileSystem {
@Throws(IOException::class)
actual abstract fun createSymlink(source: Path, target: Path)

actual open fun <E : FileSystemExtension> extend(
extensionType: KClass<E>,
extension: E,
): FileSystem = commonExtend(extensionType, extension)

actual open fun <E : FileSystemExtension> extension(type: KClass<E>): E? = null

actual companion object {
/**
* The current process's host file system. Use this instance directly, or dependency inject a
Expand Down
9 changes: 9 additions & 0 deletions okio/src/wasmMain/kotlin/okio/FileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
*/
package okio

import kotlin.reflect.KClass
import okio.Path.Companion.toPath
import okio.internal.commonCopy
import okio.internal.commonCreateDirectories
import okio.internal.commonDeleteRecursively
import okio.internal.commonExists
import okio.internal.commonExtend
import okio.internal.commonListRecursively
import okio.internal.commonMetadata

Expand Down Expand Up @@ -84,6 +86,13 @@ actual abstract class FileSystem {

actual abstract fun createSymlink(source: Path, target: Path)

actual open fun <E : FileSystemExtension> extend(
extensionType: KClass<E>,
extension: E,
): FileSystem = commonExtend(extensionType, extension)

actual open fun <E : FileSystemExtension> extension(type: KClass<E>): E? = null

actual companion object {
actual val SYSTEM_TEMPORARY_DIRECTORY: Path = "/tmp".toPath()
}
Expand Down

0 comments on commit 07d0c7c

Please sign in to comment.