diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/FileAttributeService.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/FileAttributeService.kt new file mode 100644 index 00000000000..3907b3d7a85 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/FileAttributeService.kt @@ -0,0 +1,30 @@ +/* + * 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.idea.caches + +import com.intellij.openapi.vfs.VirtualFile + +public data class CachedAttributeData>(val value: T?, val timeStamp: Long) + +interface FileAttributeService { + fun register(id: String, version: Int) {} + + fun > writeAttribute(id: String, file: VirtualFile, value: T): CachedAttributeData = + CachedAttributeData(value, timeStamp = file.timeStamp) + + fun > readAttribute(id: String, file: VirtualFile, klass: Class): CachedAttributeData? = null +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/JarUserDataManager.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/JarUserDataManager.kt new file mode 100644 index 00000000000..5ce5bce9975 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/JarUserDataManager.kt @@ -0,0 +1,106 @@ +/* + * 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.idea.caches + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.util.Key +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.util.application.runReadAction + +public object JarUserDataManager { + val fileAttributeService: FileAttributeService? = ServiceManager.getService(javaClass()) + + public fun register(collector: JarUserDataCollector<*>) { + fileAttributeService?.register(collector.key.toString(), collector.version) + } + + public fun > getValue(collector: JarUserDataCollector, file: VirtualFile): T? { + val jarFile = findJarRoot(file) ?: return null + + val stored = jarFile.getUserData(collector.key) + if (stored != null && jarFile.timeStamp == stored.second) { + return stored.first + } + + if (stored == null && fileAttributeService != null) { + val savedData = fileAttributeService.readAttribute(collector.key.toString(), jarFile, collector.stateClass) + if (savedData != null && savedData.value != null) { + jarFile.putUserData(collector.key, savedData.value to savedData.timeStamp) + + if (jarFile.timeStamp == savedData.timeStamp) { + return savedData.value + } + } + } + + scheduleJarProcessing(collector, jarFile) + + return null + } + + private fun findJarRoot(file: VirtualFile): VirtualFile? { + if (!file.getUrl().startsWith("jar://")) return null + + var jarFile = file + while (jarFile.getParent() != null) jarFile = jarFile.getParent() + + return jarFile + } + + private fun > scheduleJarProcessing(collector: JarUserDataCollector, jarFile: VirtualFile) { + if (jarFile.getUserData(collector.key) != null) return + + jarFile.putUserData(collector.key, collector.init to jarFile.timeStamp) + + ApplicationManager.getApplication().executeOnPooledThread { + runReadAction { + var result = collector.notFoundState + + VfsUtilCore.processFilesRecursively(jarFile) { file -> + if (collector.process(file) == collector.stopState) { + result = collector.stopState + + // stop processing + false + } + else { + // continue processing + true + } + } + + val savedData = fileAttributeService?.writeAttribute(collector.key.toString(), jarFile, result) + jarFile.putUserData(collector.key, result to (savedData?.timeStamp ?: jarFile.timeStamp)) + } + } + } + + interface JarUserDataCollector> { + val key: Key> + val stateClass: Class + + val version: Int get() = 1 + + val init: State + val stopState: State + val notFoundState: State + + fun process(file: VirtualFile): State + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/DecompiledUtils.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/DecompiledUtils.kt index b0888c58d48..4c7f25932c4 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/DecompiledUtils.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/DecompiledUtils.kt @@ -17,8 +17,15 @@ package org.jetbrains.kotlin.idea.decompiler import com.intellij.ide.highlighter.JavaClassFileType +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.impl.FilePropertyPusher +import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VirtualFile +import com.intellij.pom.java.LanguageLevel import com.intellij.psi.ClassFileViewProvider +import com.intellij.util.messages.MessageBus +import org.jetbrains.kotlin.idea.caches.JarUserDataManager import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinClass import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache @@ -32,6 +39,10 @@ public fun isKotlinJvmCompiledFile(file: VirtualFile): Boolean { return false } + if (JarUserDataManager.getValue(HasCompiledKotlinInJar, file) == HasCompiledKotlinInJar.JarKotlinState.NO_KOTLIN) { + return false + } + val header = KotlinBinaryClassCache.getKotlinBinaryClass(file)?.getClassHeader() return header != null && header.syntheticClassKind != KotlinSyntheticClass.Kind.TRAIT_IMPL && @@ -69,3 +80,20 @@ public fun isKotlinInternalCompiledFile(file: VirtualFile): Boolean { public fun isKotlinJavaScriptInternalCompiledFile(file: VirtualFile): Boolean = isKotlinJsMetaFile(file) && file.getNameWithoutExtension().contains('.') + +public object HasCompiledKotlinInJar : JarUserDataManager.JarUserDataCollector { + public enum class JarKotlinState { + HAS_KOTLIN, + NO_KOTLIN, + COUNTING + } + + override val key = Key.create>(HasCompiledKotlinInJar::class.simpleName!!) + override val stateClass = javaClass() + + override val init = JarKotlinState.COUNTING + override val stopState = JarKotlinState.HAS_KOTLIN + override val notFoundState = JarKotlinState.NO_KOTLIN + + override fun process(file: VirtualFile) = if (isKotlinJvmCompiledFile(file)) JarKotlinState.HAS_KOTLIN else JarKotlinState.NO_KOTLIN +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/KotlinJavaScriptLibraryDetectionUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/KotlinJavaScriptLibraryDetectionUtil.kt index 20863e796c1..41eb3ac86b4 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/KotlinJavaScriptLibraryDetectionUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/KotlinJavaScriptLibraryDetectionUtil.kt @@ -19,14 +19,15 @@ package org.jetbrains.kotlin.idea.framework import com.intellij.openapi.roots.OrderRootType import com.intellij.openapi.roots.libraries.Library +import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.caches.JarUserDataManager import org.jetbrains.kotlin.js.JavaScript import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils import kotlin.platform.platformStatic public object KotlinJavaScriptLibraryDetectionUtil { - platformStatic public fun isKotlinJavaScriptLibrary(library: Library): Boolean = isKotlinJavaScriptLibrary(library.getFiles(OrderRootType.CLASSES).toList()) @@ -36,11 +37,48 @@ public object KotlinJavaScriptLibraryDetectionUtil { // Prevent clashing with java runtime if (JavaRuntimeDetectionUtil.getJavaRuntimeVersion(classesRoots) != null) return false - return classesRoots.any { !VfsUtilCore.processFilesRecursively(it, { isJsFileWithMetadata(it) }) } + classesRoots.forEach { root -> + val cachedResult = JarUserDataManager.getValue(HasKotlinJSMetadataInJar, root) + + @suppress("NON_EXHAUSTIVE_WHEN") + when (cachedResult) { + HasKotlinJSMetadataInJar.JsMetadataState.HAS_JS_METADATA -> return true + HasKotlinJSMetadataInJar.JsMetadataState.NO_JS_METADATA -> return false + } + + if (!VfsUtilCore.processFilesRecursively(root, { isJsFileWithMetadata(root) })) { + return true + } + } + + return false } private fun isJsFileWithMetadata(file: VirtualFile): Boolean = !file.isDirectory() && JavaScript.EXTENSION == file.getExtension() && KotlinJavascriptMetadataUtils.hasMetadata(String(file.contentsToByteArray(false))) + + public object HasKotlinJSMetadataInJar : JarUserDataManager.JarUserDataCollector { + public enum class JsMetadataState { + HAS_JS_METADATA, + NO_JS_METADATA, + COUNTING + } + + override val key = Key.create>(HasKotlinJSMetadataInJar::class.simpleName!!) + override val stateClass = javaClass() + + override val init = JsMetadataState.COUNTING + override val stopState = JsMetadataState.HAS_JS_METADATA + override val notFoundState = JsMetadataState.NO_JS_METADATA + + override fun process(file: VirtualFile): JsMetadataState { + return if (KotlinJavaScriptLibraryDetectionUtil.isJsFileWithMetadata(file)) { + JsMetadataState.HAS_JS_METADATA + } else { + JsMetadataState.NO_JS_METADATA + } + } + } } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 6f9292eafde..646a11b98ed 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -54,7 +54,6 @@ org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem - @@ -171,6 +170,9 @@ + + diff --git a/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java b/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java index b8edca12729..d9fbc93f52f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java +++ b/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java @@ -19,7 +19,10 @@ package org.jetbrains.kotlin.idea; import com.intellij.openapi.application.PathMacros; import com.intellij.openapi.components.ApplicationComponent; import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.idea.caches.JarUserDataManager; import org.jetbrains.kotlin.idea.debugger.filter.FilterPackage; +import org.jetbrains.kotlin.idea.decompiler.HasCompiledKotlinInJar; +import org.jetbrains.kotlin.idea.framework.KotlinJavaScriptLibraryDetectionUtil; import org.jetbrains.kotlin.utils.PathUtil; public class PluginStartupComponent implements ApplicationComponent { @@ -35,6 +38,9 @@ public class PluginStartupComponent implements ApplicationComponent { public void initComponent() { registerPathVariable(); + JarUserDataManager.INSTANCE$.register(KotlinJavaScriptLibraryDetectionUtil.HasKotlinJSMetadataInJar.INSTANCE$); + JarUserDataManager.INSTANCE$.register(HasCompiledKotlinInJar.INSTANCE$); + FilterPackage.addKotlinStdlibDebugFilterIfNeeded(); } diff --git a/idea/src/org/jetbrains/kotlin/idea/caches/FileAttributeServiceImpl.kt b/idea/src/org/jetbrains/kotlin/idea/caches/FileAttributeServiceImpl.kt new file mode 100644 index 00000000000..40a8572169c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/caches/FileAttributeServiceImpl.kt @@ -0,0 +1,67 @@ +/* + * 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.idea.caches + +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.newvfs.FileAttribute +import com.intellij.util.containers.ContainerUtil +import com.intellij.util.io.DataInputOutputUtil + +public class FileAttributeServiceImpl : FileAttributeService { + val attributes: MutableMap = ContainerUtil.newConcurrentMap() + + override fun register(id: String, version: Int) { + attributes[id] = FileAttribute(id, version, true) + } + + override fun > writeAttribute(id: String, file: VirtualFile, value: T): CachedAttributeData { + val attribute = attributes[id] ?: throw IllegalArgumentException("Attribute with $id wasn't registered") + + val data = CachedAttributeData(value, timeStamp = file.timeStamp) + + attribute.writeAttribute(file).use { + DataInputOutputUtil.writeTIME(it, data.timeStamp) + DataInputOutputUtil.writeINT(it, data.value?.ordinal() ?: -1) + } + + return data + } + + override fun > readAttribute(id: String, file: VirtualFile, klass: Class): CachedAttributeData? { + val attribute = attributes[id] ?: throw IllegalArgumentException("Attribute with $id wasn't registered") + + val stream = attribute.readAttribute(file) ?: return null + return stream.use { + val timeStamp = DataInputOutputUtil.readTIME(it) + val intValue = DataInputOutputUtil.readINT(it) + + if (file.timeStamp == timeStamp) { + CachedAttributeData(deserializeEnumValue(intValue, klass), timeStamp) + } + else { + null + } + } + } + + private fun > deserializeEnumValue(i: Int, klass: Class): T { + val method = klass.getMethod("values") + val values = method.invoke(null) as Array + return values[i] + } +} +