From e4a661513ef2b8ef4e7748a15f3bd88d74cf33c7 Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Thu, 29 Mar 2018 22:31:53 +0200 Subject: [PATCH] Implement more robust way of determining binary directory Fixes #34 --- VERSION.md | 1 + .../rhubarb_for_spine/RhubarbTask.kt | 10 +- .../rhubarb_for_spine/classLocation.kt | 92 +++++++++++++++++++ 3 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/classLocation.kt diff --git a/VERSION.md b/VERSION.md index 245ba29..199aa31 100644 --- a/VERSION.md +++ b/VERSION.md @@ -2,6 +2,7 @@ ## Unreleased +* Fixed [issue #34](https://github.com/DanielSWolf/rhubarb-lip-sync/issues/34): Generic error message in Rhubarb for Spine * More helpful error dialogs for internal errors in Rhubarb Lip Sync for Spine * Internal errors in Rhubarb Lip Sync for Spine are logged to stderr diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/RhubarbTask.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/RhubarbTask.kt index 8c23dbc..d0e24a7 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/RhubarbTask.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/RhubarbTask.kt @@ -10,8 +10,6 @@ import java.io.* import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path -import java.nio.file.Paths -import java.time.Duration import java.util.concurrent.Callable class RhubarbTask( @@ -106,12 +104,8 @@ class RhubarbTask( } private val guiBinDirectory: Path by lazy { - var path: String = ClassLoader.getSystemClassLoader().getResource(".")!!.path - if (path.length >= 3 && path[2] == ':') { - // Workaround for https://stackoverflow.com/questions/9834776/java-nio-file-path-issue - path = path.substring(1) - } - return@lazy Paths.get(path) + val path = urlToPath(getLocation(RhubarbTask::class.java)) + return@lazy if (Files.isDirectory(path)) path.parent else path } private val rhubarbBinFilePath: Path by lazy { diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/classLocation.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/classLocation.kt new file mode 100644 index 0000000..b808562 --- /dev/null +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/com/rhubarb_lip_sync/rhubarb_for_spine/classLocation.kt @@ -0,0 +1,92 @@ +package com.rhubarb_lip_sync.rhubarb_for_spine + +import java.io.FileInputStream +import java.net.MalformedURLException +import java.net.URISyntaxException +import java.net.URL +import org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS +import java.nio.file.Path +import java.nio.file.Paths + +// The following code is adapted from https://stackoverflow.com/a/12733172/52041 + +/** + * Gets the base location of the given class. + * + * If the class is directly on the file system (e.g., + * "/path/to/my/package/MyClass.class") then it will return the base directory + * (e.g., "file:/path/to"). + * + * If the class is within a JAR file (e.g., + * "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the + * path to the JAR (e.g., "file:/path/to/my-jar.jar"). + * + * @param c The class whose location is desired. + */ +fun getLocation(c: Class<*>): URL { + // Try the easy way first + try { + val codeSourceLocation = c.protectionDomain.codeSource.location + if (codeSourceLocation != null) return codeSourceLocation + } catch (e: SecurityException) { + // Cannot access protection domain + } catch (e: NullPointerException) { + // Protection domain or code source is null + } + + // The easy way failed, so we try the hard way. We ask for the class + // itself as a resource, then strip the class's path from the URL string, + // leaving the base path. + + // Get the class's raw resource path + val classResource = c.getResource(c.simpleName + ".class") + ?: throw Exception("Cannot find class resource.") + + val url = classResource.toString() + val suffix = c.canonicalName.replace('.', '/') + ".class" + if (!url.endsWith(suffix)) throw Exception("Malformed URL.") + + // strip the class's path from the URL string + val base = url.substring(0, url.length - suffix.length) + + var path = base + + // remove the "jar:" prefix and "!/" suffix, if present + if (path.startsWith("jar:")) path = path.substring(4, path.length - 2) + + return URL(path) +} + +/** + * Converts the given URL to its corresponding [Path]. + * + * @param url The URL to convert. + * @return A file path suitable for use with e.g. [FileInputStream] + */ +fun urlToPath(url: URL): Path { + var pathString = url.toString() + + if (pathString.startsWith("jar:")) { + // Remove "jar:" prefix and "!/" suffix + val index = pathString.indexOf("!/") + pathString = pathString.substring(4, index) + } + + try { + if (IS_OS_WINDOWS && pathString.matches("file:[A-Za-z]:.*".toRegex())) { + pathString = "file:/" + pathString.substring(5) + } + return Paths.get(URL(pathString).toURI()) + } catch (e: MalformedURLException) { + // URL is not completely well-formed. + } catch (e: URISyntaxException) { + // URL is not completely well-formed. + } + + if (pathString.startsWith("file:")) { + // Pass through the URL as-is, minus "file:" prefix + pathString = pathString.substring(5) + return Paths.get(pathString) + } + throw IllegalArgumentException("Invalid URL: $url") +} \ No newline at end of file