JS backend: LibraryUtils: add support for load and write metadata

This commit is contained in:
Michael Nedzelsky
2015-01-20 12:17:33 +03:00
parent c927b69103
commit a6c21fbe85
2 changed files with 127 additions and 29 deletions
@@ -0,0 +1,101 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.utils
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.io.FileUtil
import java.io.File
import java.io.IOException
import javax.xml.bind.DatatypeConverter.parseBase64Binary
import javax.xml.bind.DatatypeConverter.printBase64Binary
import kotlin.platform.platformStatic
public class KotlinJavascriptMetadata(public val moduleName: String, public val body: ByteArray)
public object KotlinJavascriptMetadataUtils {
private val LOG = Logger.getInstance(javaClass<KotlinJavascriptMetadataUtils>())
public val JS_EXT: String = ".js"
private val KOTLIN_JAVASCRIPT_METHOD_NAME = "kotlin_module_metadata"
/**
* Matches string like <name>.kotlin_module_metadata(<abi version>, <module name>, <base64 data>)
*/
private val METADATA_PATTERN = "(?m)\\w+\\.$KOTLIN_JAVASCRIPT_METHOD_NAME\\((\\d+),\\s*(['\"])([^'\"]*)\\2,\\s*(['\"])([^'\"]*)\\4\\)".toRegex()
private val ABI_VERSION = 1
platformStatic
public fun hasMetadata(text: String): Boolean = METADATA_PATTERN.matcher(text).find()
public fun writeMetadata(moduleName: String, content: ByteArray, metaFile: File) {
val text = "// Kotlin.$KOTLIN_JAVASCRIPT_METHOD_NAME($ABI_VERSION, \"$moduleName\", \"${printBase64Binary(content)}\");\n"
FileUtil.writeToFile(metaFile, text)
}
platformStatic
public fun loadMetadata(file: File): List<KotlinJavascriptMetadata> {
assert(file.exists()) { "Library " + file + " not found" }
var result: MutableList<KotlinJavascriptMetadata> = arrayListOf()
if (file.isDirectory()) {
loadMetadataFromDirectory(file, result)
}
else if (file.getName().endsWith(JS_EXT)) {
loadMetadataFromFile(file, result)
}
else {
loadMetadataFromZip(file, result)
}
return result
}
platformStatic
public fun loadMetadata(path: String): List<KotlinJavascriptMetadata> = loadMetadata(File(path))
private fun parseMetadata(text: String, path: String, metadataList: MutableList<KotlinJavascriptMetadata>) {
val matcher = METADATA_PATTERN.matcher(text)
while (matcher.find()) {
var abiVersion = Integer.parseInt(matcher.group(1));
if (abiVersion != ABI_VERSION) LOG.error("Unsupported abi version in $path, expected $ABI_VERSION, but $abiVersion")
var moduleName = matcher.group(3)
val data = matcher.group(5)
metadataList.add(KotlinJavascriptMetadata(moduleName, parseBase64Binary(data)))
}
}
private fun loadMetadataFromFile(file: File, metadataList: MutableList<KotlinJavascriptMetadata>) {
try {
val content = FileUtil.loadFile(file)
parseMetadata(content, file.getPath(), metadataList)
}
catch (ex: IOException) {
LOG.error("Could not read ${file.getAbsolutePath()}: ${ex.getMessage()}")
}
}
private fun loadMetadataFromDirectory(dir: File, metadataList: MutableList<KotlinJavascriptMetadata>) {
LibraryUtils.traverseDirectory(dir) {
file, relativePath -> parseMetadata(FileUtil.loadFile(file), file.getPath(), metadataList)
}
}
private fun loadMetadataFromZip(file: File, metadataList: MutableList<KotlinJavascriptMetadata>) {
LibraryUtils.traverseArchive(file) { content, relativePath ->
parseMetadata(content, "${file.getPath()}/$relativePath", metadataList)
}
}
}
@@ -20,7 +20,6 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.Processor
import java.io.*
import java.util.Properties
import java.util.jar.Attributes
@@ -28,7 +27,6 @@ import java.util.jar.JarFile
import java.util.jar.Manifest
import java.util.zip.ZipFile
import kotlin.platform.platformStatic
import org.jetbrains.kotlin.utils.fileUtils.*
public object LibraryUtils {
private val LOG = Logger.getInstance(javaClass<LibraryUtils>())
@@ -36,7 +34,6 @@ public object LibraryUtils {
public val KOTLIN_JS_MODULE_NAME: String = "Kotlin-JS-Module-Name"
private var TITLE_KOTLIN_JAVASCRIPT_STDLIB: String
private var TITLE_KOTLIN_JAVASCRIPT_LIB: String
private val JS_EXT = ".js"
private val METAINF = "META-INF/"
private val MANIFEST_PATH = "${METAINF}MANIFEST.MF"
private val METAINF_RESOURCES = "${METAINF}resources/"
@@ -84,9 +81,15 @@ public object LibraryUtils {
}
platformStatic
public fun isKotlinJavascriptLibrary(library: File): Boolean {
return checkAttributeValue(library, TITLE_KOTLIN_JAVASCRIPT_LIB, Attributes.Name.SPECIFICATION_TITLE)
}
public fun isOldKotlinJavascriptLibrary(library: File): Boolean =
checkAttributeValue(library, TITLE_KOTLIN_JAVASCRIPT_LIB, Attributes.Name.SPECIFICATION_TITLE) && getKotlinJsModuleName(library) != null
platformStatic
public fun isKotlinJavascriptLibraryWithMetadata(library: File): Boolean = KotlinJavascriptMetadataUtils.loadMetadata(library).isNotEmpty()
platformStatic
public fun isKotlinJavascriptLibrary(library: File): Boolean =
isOldKotlinJavascriptLibrary(library) || isKotlinJavascriptLibraryWithMetadata(library)
platformStatic
public fun isKotlinJavascriptStdLibrary(library: File): Boolean {
@@ -116,11 +119,11 @@ public object LibraryUtils {
for (lib in libs) {
when {
lib.isDirectory() ->
traverseDirectoryWithReportingIOException(lib) { (file, path) ->
traverseDirectory(lib) { (file, path) ->
files.add(FileUtil.loadFile(file))
}
FileUtil.isJarOrZip(lib) ->
traverseArchiveWithReportingIOException(lib) { (content, path) ->
traverseArchive(lib) { (content, path) ->
files.add(content)
}
else ->
@@ -131,11 +134,17 @@ public object LibraryUtils {
return files
}
private fun processDirectory(dir: File, action: (File, String) -> Unit) {
private fun copyJsFilesFromDirectory(dir: File, outputLibraryJsPath: String) {
traverseDirectory(dir) {
file, relativePath -> FileUtil.copy(file, File(outputLibraryJsPath, relativePath))
}
}
private fun processDirectory(dir: File, action: (File, relativePath: String) -> Unit) {
FileUtil.processFilesRecursively(dir, object : Processor<File> {
override fun process(file: File): Boolean {
val relativePath = FileUtil.getRelativePath(dir, file) ?: throw IllegalArgumentException("relativePath should not be null " + dir + " " + file)
if (file.isFile() && relativePath.endsWith(JS_EXT)) {
if (file.isFile() && relativePath.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) {
val suggestedRelativePath = getSuggestedPath(relativePath)
if (suggestedRelativePath == null) return true
@@ -146,13 +155,7 @@ public object LibraryUtils {
})
}
private fun copyJsFilesFromDirectory(dir: File, outputLibraryJsPath: String) {
traverseDirectoryWithReportingIOException(dir) {
file, relativePath -> FileUtil.copy(file, File(outputLibraryJsPath, relativePath))
}
}
private fun traverseDirectoryWithReportingIOException(dir: File, action: (File, String) -> Unit) {
fun traverseDirectory(dir: File, action: (File, relativePath: String) -> Unit) {
try {
processDirectory(dir, action)
}
@@ -162,28 +165,19 @@ public object LibraryUtils {
}
private fun copyJsFilesFromZip(file: File, outputLibraryJsPath: String) {
traverseArchiveWithReportingIOException(file) { content, relativePath ->
traverseArchive(file) { content, relativePath ->
FileUtil.writeToFile(File(outputLibraryJsPath, relativePath), content)
}
}
private fun traverseArchiveWithReportingIOException(file: File, action: (String, String) -> Unit) {
try {
traverseArchive(file, action)
}
catch (ex: IOException) {
LOG.error("Could not extract files from archive ${file.getName()}: ${ex.getMessage()}")
}
}
private fun traverseArchive(file: File, action: (String, String) -> Unit) {
fun traverseArchive(file: File, action: (content: String, relativePath: String) -> Unit) {
val zipFile = ZipFile(file.getPath())
try {
val zipEntries = zipFile.entries()
while (zipEntries.hasMoreElements()) {
val entry = zipEntries.nextElement()
val entryName = entry.getName()
if (!entry.isDirectory() && entryName.endsWith(JS_EXT)) {
if (!entry.isDirectory() && entryName.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) {
val relativePath = getSuggestedPath(entryName)
if (relativePath == null) continue
@@ -193,6 +187,9 @@ public object LibraryUtils {
}
}
}
catch (ex: IOException) {
LOG.error("Could not extract files from archive ${file.getName()}: ${ex.getMessage()}")
}
finally {
zipFile.close()
}