Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial APIs for FileSystem extensions #1470

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Initial APIs for FileSystem extensions
Closes: #1466
  • Loading branch information
swankjesse committed Apr 15, 2024
commit 07d0c7c107e01e05c037b304b684428bd2d5f659
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
yschimke marked this conversation as resolved.
Show resolved Hide resolved

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:https://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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we don't want to, or cannot support subclasses with this is ForwardingFileSystem ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This coalesces two ForwardingFileSystem wrappers into one. But it only works if there’s no other methods overridden in the ForwardingFileSystem. If there are, then we shouldn’t replace the users’ subtype with the base type.

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:https://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
Loading