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 all 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
27 changes: 26 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,23 @@ 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 abstract fun map (Lokio/FileSystemExtension$Mapping;)Lokio/FileSystemExtension;
}

public abstract class okio/FileSystemExtension$Mapping {
public static final field Companion Lokio/FileSystemExtension$Mapping$Companion;
public fun <init> ()V
public final fun chain (Lokio/FileSystemExtension$Mapping;)Lokio/FileSystemExtension$Mapping;
public abstract fun mapParameter (Lokio/Path;Ljava/lang/String;Ljava/lang/String;)Lokio/Path;
public abstract fun mapResult (Lokio/Path;Ljava/lang/String;)Lokio/Path;
}

public final class okio/FileSystemExtension$Mapping$Companion {
public final fun getNONE ()Lokio/FileSystemExtension$Mapping;
}

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 +526,13 @@ 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 final fun getExtensionMapping ()Lokio/FileSystemExtension$Mapping;
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;Lokio/FileSystemExtension;)Lokio/FileSystemExtension;
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 +840,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
1 change: 1 addition & 0 deletions okio/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ kotlin {

val nonWasmTest by creating {
dependencies {
implementation(libs.kotlin.test)
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
93 changes: 93 additions & 0 deletions okio/src/commonMain/kotlin/okio/FileSystemExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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 okio.FileSystemExtension.Mapping

/**
* Marks an object that can be attached to a [FileSystem], and that supplements the file system's
* capabilities.
*
* Implementations must support transforms to input and output paths with [Mapping]. To simplify
* implementation, use [Mapping.NONE] by default and use [Mapping.chain] to combine mappings.
*
* ```kotlin
* class DiskUsageExtension private constructor(
* private val mapping: Mapping,
* ) : FileSystemExtension {
* constructor() : this(Mapping.NONE)
*
* override fun map(outer: Mapping): FileSystemExtension {
* return DiskUsageExtension(mapping.chain(outer))
* }
*
* fun sizeOnDisk(path: Path): Long {
* val mappedPath = mapping.mapParameter(path, "sizeOnDisk", "path")
* return lookUpSizeOnDisk(mappedPath)
* }
*
* fun largestFiles(): Sequence<Path> {
* val largestFiles: Sequence<Path> = lookUpLargestFiles()
* return largestFiles.map {
* mapping.mapResult(it, "largestFiles")
* }
* }
* }
* ```
*/
interface FileSystemExtension {
/** Returns a file system of the same type, that applies [outer] to all paths. */
fun map(outer: Mapping): FileSystemExtension

abstract class Mapping {
abstract fun mapParameter(path: Path, functionName: String, parameterName: String): Path
abstract fun mapResult(path: Path, functionName: String): Path

fun chain(outer: Mapping): Mapping {
val inner = this
return object : Mapping() {
override fun mapParameter(path: Path, functionName: String, parameterName: String): Path {
return inner.mapParameter(
outer.mapParameter(
path,
functionName,
parameterName,
),
functionName,
parameterName,
)
}

override fun mapResult(path: Path, functionName: String): Path {
return outer.mapResult(
inner.mapResult(
path,
functionName,
),
functionName,
)
}
}
}

companion object {
val NONE = object : Mapping() {
override fun mapParameter(path: Path, functionName: String, parameterName: String) = path
override fun mapResult(path: Path, functionName: String) = path
}
}
}
}
36 changes: 35 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,25 @@ 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() {
/** Extensions added at this layer. Additional extensions may exist in [delegate]. */
internal val extensions = extensions.toMap()

/** Maps paths with [onPathParameter] and [onPathResult]. */
val extensionMapping: FileSystemExtension.Mapping = object : FileSystemExtension.Mapping() {
override fun mapParameter(path: Path, functionName: String, parameterName: String) =
onPathParameter(path, functionName, parameterName)

override fun mapResult(path: Path, functionName: String) =
onPathResult(path, functionName)
}

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 +158,16 @@ abstract class ForwardingFileSystem(
*/
open fun onPathResult(path: Path, functionName: String): Path = path

/**
* Invoked each time an extension is returned from [ForwardingFileSystem.extension].
*
* Overrides of this function must call [FileSystemExtension.map] with [extensionMapping],
* otherwise path mapping will not be applied. Or call `super.onExtension()` to do this.
*/
open fun <T : FileSystemExtension> onExtension(type: KClass<T>, extension: T): T {
return type.cast(extension.map(extensionMapping))
}

@Throws(IOException::class)
override fun canonicalize(path: Path): Path {
val path = onPathParameter(path, "canonicalize", "path")
Expand Down Expand Up @@ -238,5 +264,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]. If this file
* system already has an extension of this type, [extension] takes precedence.
*/
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
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