Skip to content
Merged
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
10 changes: 10 additions & 0 deletions components/resources/library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,13 @@ tasks.register<GeneratePluralRuleListsTask>("generatePluralRuleLists") {
outputFile = projectDir.file("src/commonMain/kotlin/org/jetbrains/compose/resources/plural/CLDRPluralRuleLists.kt")
samplesOutputFile = projectDir.file("src/commonTest/kotlin/org/jetbrains/compose/resources/CLDRPluralRuleLists.test.kt")
}

tasks {
val desktopTestProcessResources =
named<ProcessResources>("desktopTestProcessResources")

withType<Test> {
dependsOn(desktopTestProcessResources)
environment("RESOURCES_PATH", desktopTestProcessResources.map { it.destinationDir.absolutePath }.get())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import androidx.compose.runtime.ProvidableCompositionLocal
import java.io.FileNotFoundException
import java.io.InputStream

internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader {
@ExperimentalResourceApi
internal actual fun getPlatformResourceReader(): ResourceReader = DefaultAndroidResourceReader

@ExperimentalResourceApi
internal object DefaultAndroidResourceReader : ResourceReader {
private val assets: AssetManager by lazy {
val context = androidContext ?: error(
"Android context is not initialized. " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,21 @@ suspend fun readResourceBytes(path: String): ByteArray = DefaultResourceReader.r
@InternalResourceApi
fun getResourceUri(path: String): String = DefaultResourceReader.getUri(path)

internal interface ResourceReader {
@ExperimentalResourceApi
interface ResourceReader {
suspend fun read(path: String): ByteArray
suspend fun readPart(path: String, offset: Long, size: Long): ByteArray
fun getUri(path: String): String
}

internal expect fun getPlatformResourceReader(): ResourceReader

@ExperimentalResourceApi
internal val DefaultResourceReader = getPlatformResourceReader()

//ResourceReader provider will be overridden for tests
internal val LocalResourceReader = staticCompositionLocalOf { DefaultResourceReader }
@ExperimentalResourceApi
val LocalResourceReader = staticCompositionLocalOf { DefaultResourceReader }

//For an android preview we need to initialize the resource reader with the local context
internal expect val ProvidableCompositionLocal<ResourceReader>.currentOrPreview: ResourceReader
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
package org.jetbrains.compose.resources

import java.io.EOFException
import java.io.IOException
import java.io.InputStream

internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader {
@ExperimentalResourceApi
internal actual fun getPlatformResourceReader(): ResourceReader =
JvmResourceReader.Default

@ExperimentalResourceApi
class JvmResourceReader(
private val classLoader: ClassLoader
) : ResourceReader {

companion object {
internal val Default = JvmResourceReader(JvmResourceReader::class.java.classLoader)
}

override suspend fun read(path: String): ByteArray {
val resource = getResourceAsStream(path)
return resource.use { input -> input.readBytes() }
Expand All @@ -31,17 +41,12 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou
}

override fun getUri(path: String): String {
val classLoader = getClassLoader()
val resource = classLoader.getResource(path) ?: throw MissingResourceException(path)
return resource.toURI().toString()
}

private fun getResourceAsStream(path: String): InputStream {
val classLoader = getClassLoader()
return classLoader.getResourceAsStream(path) ?: throw MissingResourceException(path)
}

private fun getClassLoader(): ClassLoader {
return this.javaClass.classLoader ?: error("Cannot find class loader")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@file:OptIn(ExperimentalTestApi::class)

package org.jetbrains.compose.resources

import androidx.compose.ui.test.ExperimentalTestApi
import kotlinx.coroutines.test.runTest
import java.net.URLClassLoader
import kotlin.io.path.Path
import kotlin.io.path.isDirectory
import kotlin.test.Test
import kotlin.test.assertEquals


class CustomClassloaderTest {

val RESOURCES_PATH
get() = System.getenv("RESOURCES_PATH")
?.let { Path(it) }
?.takeIf { it.isDirectory() }
?: error("RESOURCES_PATH environment variable is not set or is not a directory")

@Test
fun testCustomClassloader() = runTest {
val actualResourceText =
CustomClassloaderTest::class
.java
.classLoader
.getResourceAsStream("hello.txt")
?.readAllBytes()
?.toString(Charsets.UTF_8)

val classloader = URLClassLoader(arrayOf(RESOURCES_PATH.toUri().toURL()), null)
val reader = JvmResourceReader(classloader)
assertEquals(
expected = actualResourceText,
actual = reader.read("hello.txt").toString(Charsets.UTF_8)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello World!
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ import platform.Foundation.readDataOfLength
import platform.Foundation.seekToFileOffset
import platform.posix.memcpy

@ExperimentalResourceApi
@OptIn(BetaInteropApi::class)
internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader {
internal actual fun getPlatformResourceReader(): ResourceReader = DefaultIOsResourceReader

@ExperimentalResourceApi
internal object DefaultIOsResourceReader : ResourceReader {
private val composeResourcesDir: String by lazy { findComposeResourcesPath() }

override suspend fun read(path: String): ByteArray {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import org.w3c.files.Blob
import org.w3c.xhr.XMLHttpRequest
import kotlin.js.Promise

@ExperimentalResourceApi
internal actual fun getPlatformResourceReader(): ResourceReader {
if (isInTestEnvironment()) return TestJsResourceReader
return DefaultJsResourceReader
}

private val DefaultJsResourceReader = object : ResourceReader {
@ExperimentalResourceApi
internal object DefaultJsResourceReader : ResourceReader {
override suspend fun read(path: String): ByteArray {
return readAsBlob(path).asByteArray()
}
Expand Down Expand Up @@ -46,36 +48,34 @@ private val DefaultJsResourceReader = object : ResourceReader {
}

// It uses a synchronous XmlHttpRequest (blocking!!!)
private val TestJsResourceReader by lazy {
object : ResourceReader {
override suspend fun read(path: String): ByteArray {
return readByteArray(path)
}
private object TestJsResourceReader : ResourceReader {
override suspend fun read(path: String): ByteArray {
return readByteArray(path)
}

override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray {
return readByteArray(path).sliceArray(offset.toInt() until (offset + size).toInt())
}
override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray {
return readByteArray(path).sliceArray(offset.toInt() until (offset + size).toInt())
}

override fun getUri(path: String): String {
val location = window.location
return getResourceUrl(location.origin, location.pathname, path)
}
override fun getUri(path: String): String {
val location = window.location
return getResourceUrl(location.origin, location.pathname, path)
}

private fun readByteArray(path: String): ByteArray {
val resPath = WebResourcesConfiguration.getResourcePath(path)
val request = XMLHttpRequest()
request.open("GET", resPath, false)
request.overrideMimeType("text/plain; charset=x-user-defined")
request.send()
if (request.status == 200.toShort()) {
// For blocking XmlHttpRequest the response can be only in text form, so we convert it to bytes manually
val text = request.responseText
val bytes = Uint8Array(text.length)
js("for (var i = 0; i < text.length; i++) { bytes[i] = text.charCodeAt(i) & 0xFF; }")
return bytes.unsafeCast<ByteArray>()
}
throw MissingResourceException("$resPath")
private fun readByteArray(path: String): ByteArray {
val resPath = WebResourcesConfiguration.getResourcePath(path)
val request = XMLHttpRequest()
request.open("GET", resPath, false)
request.overrideMimeType("text/plain; charset=x-user-defined")
request.send()
if (request.status == 200.toShort()) {
// For blocking XmlHttpRequest the response can be only in text form, so we convert it to bytes manually
val text = request.responseText
val bytes = Uint8Array(text.length)
js("for (var i = 0; i < text.length; i++) { bytes[i] = text.charCodeAt(i) & 0xFF; }")
return bytes.unsafeCast<ByteArray>()
}
throw MissingResourceException("$resPath")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@ package org.jetbrains.compose.resources

import kotlinx.cinterop.addressOf
import kotlinx.cinterop.usePinned
import platform.Foundation.*
import platform.Foundation.NSBundle
import platform.Foundation.NSData
import platform.Foundation.NSFileHandle
import platform.Foundation.NSFileManager
import platform.Foundation.NSURL
import platform.Foundation.closeFile
import platform.Foundation.fileHandleForReadingAtPath
import platform.Foundation.readDataOfLength
import platform.posix.memcpy

internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader {
@ExperimentalResourceApi
internal actual fun getPlatformResourceReader(): ResourceReader = DefaultMacOsResourceReader

@ExperimentalResourceApi
internal object DefaultMacOsResourceReader : ResourceReader {
override suspend fun read(path: String): ByteArray {
val data = readData(getPathOnDisk(path))
return ByteArray(data.length.toInt()).apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ private external fun jsExportInt8ArrayToWasm(src: Int8Array, size: Int, dstAddr:
@JsFun("(blob) => blob.arrayBuffer()")
private external fun jsExportBlobAsArrayBuffer(blob: Blob): Promise<ArrayBuffer>

@ExperimentalResourceApi
internal actual fun getPlatformResourceReader(): ResourceReader {
if (isInTestEnvironment()) return TestWasmResourceReader
return DefaultWasmResourceReader
}

private val DefaultWasmResourceReader = object : ResourceReader {
@ExperimentalResourceApi
internal object DefaultWasmResourceReader : ResourceReader {
override suspend fun read(path: String): ByteArray {
return readAsBlob(path).asByteArray()
}
Expand Down Expand Up @@ -72,45 +74,43 @@ private val DefaultWasmResourceReader = object : ResourceReader {
}

// It uses a synchronous XmlHttpRequest (blocking!!!)
private val TestWasmResourceReader by lazy {
object : ResourceReader {
override suspend fun read(path: String): ByteArray {
return readByteArray(path)
}
private object TestWasmResourceReader : ResourceReader {
override suspend fun read(path: String): ByteArray {
return readByteArray(path)
}

override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray {
return readByteArray(path).sliceArray(offset.toInt() until (offset + size).toInt())
}
override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray {
return readByteArray(path).sliceArray(offset.toInt() until (offset + size).toInt())
}

override fun getUri(path: String): String {
val location = window.location
return getResourceUrl(location.origin, location.pathname, path)
}
override fun getUri(path: String): String {
val location = window.location
return getResourceUrl(location.origin, location.pathname, path)
}

private fun readByteArray(path: String): ByteArray {
val resPath = WebResourcesConfiguration.getResourcePath(path)
val request = XMLHttpRequest()
request.open("GET", resPath, false)
request.overrideMimeType("text/plain; charset=x-user-defined")
request.send()
if (request.status == 200.toShort()) {
return requestResponseAsByteArray(request).asByteArray()
}
println("Request status is not 200 - $resPath, status: ${request.status}")
throw MissingResourceException("$resPath")
private fun readByteArray(path: String): ByteArray {
val resPath = WebResourcesConfiguration.getResourcePath(path)
val request = XMLHttpRequest()
request.open("GET", resPath, false)
request.overrideMimeType("text/plain; charset=x-user-defined")
request.send()
if (request.status == 200.toShort()) {
return requestResponseAsByteArray(request).asByteArray()
}
println("Request status is not 200 - $resPath, status: ${request.status}")
throw MissingResourceException(resPath)
}

private fun Int8Array.asByteArray(): ByteArray {
val array = this
val size = array.length

private fun Int8Array.asByteArray(): ByteArray {
val array = this
val size = array.length

@OptIn(UnsafeWasmMemoryApi::class)
return withScopedMemoryAllocator { allocator ->
val memBuffer = allocator.allocate(size)
val dstAddress = memBuffer.address.toInt()
jsExportInt8ArrayToWasm(array, size, dstAddress)
ByteArray(size) { i -> (memBuffer + i).loadByte() }
}
@OptIn(UnsafeWasmMemoryApi::class)
return withScopedMemoryAllocator { allocator ->
val memBuffer = allocator.allocate(size)
val dstAddress = memBuffer.address.toInt()
jsExportInt8ArrayToWasm(array, size, dstAddress)
ByteArray(size) { i -> (memBuffer + i).loadByte() }
}
}
}
Expand Down
Loading