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 3 commits
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
13 changes: 12 additions & 1 deletion okio/api/okio.api
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ public abstract class okio/FileSystem {
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 fun extend (Lkotlin/reflect/KClass;Lokio/FileSystemExtension;)Lokio/FileSystem;
public fun extension (Lkotlin/reflect/KClass;)Lokio/FileSystemExtension;
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;
Expand All @@ -499,7 +501,10 @@ public final class okio/FileSystem$Companion {
public final fun get (Ljava/nio/file/FileSystem;)Lokio/FileSystem;
}

public abstract class okio/ForwardingFileSystem : okio/FileSystem {
public abstract interface class okio/FileSystemExtension {
}

public 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
Expand All @@ -508,10 +513,12 @@ public abstract class okio/ForwardingFileSystem : okio/FileSystem {
public fun createSymlink (Lokio/Path;Lokio/Path;)V
public final fun delegate ()Lokio/FileSystem;
public fun delete (Lokio/Path;Z)V
public fun extension (Lkotlin/reflect/KClass;)Lokio/FileSystemExtension;
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 onExtension (Lkotlin/reflect/KClass;Ljava/lang/Object;)Ljava/lang/Object;
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;
Expand Down Expand Up @@ -819,6 +826,10 @@ 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/-FileSystem {
public static final fun commonExtend (Lokio/FileSystem;Lkotlin/reflect/KClass;Ljava/lang/Object;)Lokio/FileSystem;
}

public final class okio/internal/_Utf8Kt {
public static final fun commonAsUtf8ToByteArray (Ljava/lang/String;)[B
public static final fun commonToUtf8String ([BII)Ljava/lang/String;
Expand Down
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
14 changes: 14 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,18 @@ expect abstract class FileSystem() {
@Throws(IOException::class)
abstract fun createSymlink(source: Path, target: Path)

/**
* Returns a new file system that forwards all calls to this, and that also returns [extension]
* when it is requested.
*
* When [extensionType] is requested on the returned file system, it will return [extension],
* regardless of what is returned by this file system.
Comment on lines +385 to +386
Copy link
Member

Choose a reason for hiding this comment

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

So you can shadow extensions ?

Copy link
Member

Choose a reason for hiding this comment

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

yes, I sas the overrideExtension test

*/
open fun <E : FileSystemExtension> extend(extensionType: KClass<E>, extension: E): FileSystem
yschimke marked this conversation as resolved.
Show resolved Hide resolved

/** Returns the extension for [type] if it exists, and null otherwise. */
open fun <E : FileSystemExtension> extension(type: KClass<E>): E?

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

/**
* Marks an object that can be attached to a [FileSystem], and that supplements the file system's
* capabilities.
*/
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)"
}
13 changes: 13 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,16 @@ inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
@Suppress("UNCHECKED_CAST")
return result as R
}

/**
* Returns a new file system that forwards all calls to this, and that also returns [extension]
* when it is requested.
*
* When [E] is requested on the returned file system, it will return [extension], regardless of what
* is returned by this file system.
*/
inline fun <reified E : FileSystemExtension> FileSystem.extend(extension: E): FileSystem =
extend(E::class, extension)

/** Returns the extension for [E] if it exists, and null otherwise. */
inline fun <reified E : FileSystemExtension> FileSystem.extension(): E? = 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
96 changes: 96 additions & 0 deletions okio/src/commonTest/kotlin/okio/FileSystemExtensionsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* 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
*
* 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 kotlin.test.assertNotEquals
import kotlin.test.assertNull
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() {
val fakeFileSystem = FakeFileSystem()
val extension = FooExtension(fakeFileSystem)
val fileSystemWithExtension = fakeFileSystem.extend(extension)
assertNull(fileSystemWithExtension.extension<BarExtension>())
}

@Test
fun overrideExtension() {
val fakeFileSystem = FakeFileSystem()
val extension1 = FooExtension(fakeFileSystem)
val fileSystemWithExtension1 = fakeFileSystem.extend(extension1)

val extension2 = FooExtension(fakeFileSystem)
val fileSystemWithExtension2 = fileSystemWithExtension1.extend(extension2)
assertEquals(fileSystemWithExtension2.extension<FooExtension>(), extension2)

// Doesn't interfere with any of the wrapped layers.
assertEquals(fileSystemWithExtension1.extension<FooExtension>(), extension1)
assertNull(fakeFileSystem.extension<FooExtension>(), null)
}

@Test
fun forwardingFileSystemCoalesced() {
val fakeFileSystem = FakeFileSystem()
val fooExtension = FooExtension(fakeFileSystem)
val fileSystemWithFoo = fakeFileSystem.extend(fooExtension)

val barExtension = BarExtension(fakeFileSystem)
val fileSystemWithFooAndBar = fileSystemWithFoo.extend(barExtension)

assertEquals(fileSystemWithFooAndBar.extension<FooExtension>(), fooExtension)
assertEquals(fileSystemWithFooAndBar.extension<BarExtension>(), barExtension)
assertEquals((fileSystemWithFooAndBar as ForwardingFileSystem).delegate, fakeFileSystem)
}

@Test
fun customForwardingFileSystemNotCoalesced() {
val fakeFileSystem = FakeFileSystem()
val fooExtension = FooExtension(fakeFileSystem)
val fileSystemWithFoo = object : ForwardingFileSystem(
delegate = fakeFileSystem,
extensions = mapOf(FooExtension::class to fooExtension),
) {
// This extends ForwardingFileSystem. Usually this would be to add new capabilities.
}

val barExtension = BarExtension(fakeFileSystem)
val fileSystemWithFooAndBar = fileSystemWithFoo.extend(barExtension)

assertEquals(fileSystemWithFooAndBar.extension<FooExtension>(), fooExtension)
assertEquals(fileSystemWithFooAndBar.extension<BarExtension>(), barExtension)
assertNotEquals((fileSystemWithFooAndBar as ForwardingFileSystem).delegate, fakeFileSystem)
}

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
Loading
Loading