Create background jar-processor that can store meta information for jars

This commit is contained in:
Nikolay Krasko
2015-08-19 13:58:06 +03:00
parent e17908e5c6
commit 3b14241224
7 changed files with 280 additions and 3 deletions
@@ -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<T: Enum<T>>(val value: T?, val timeStamp: Long)
interface FileAttributeService {
fun register(id: String, version: Int) {}
fun <T: Enum<T>> writeAttribute(id: String, file: VirtualFile, value: T): CachedAttributeData<T> =
CachedAttributeData(value, timeStamp = file.timeStamp)
fun <T: Enum<T>> readAttribute(id: String, file: VirtualFile, klass: Class<T>): CachedAttributeData<T>? = null
}
@@ -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<FileAttributeService>())
public fun register(collector: JarUserDataCollector<*>) {
fileAttributeService?.register(collector.key.toString(), collector.version)
}
public fun <T: Enum<T>> getValue(collector: JarUserDataCollector<T>, 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 <T: Enum<T>> scheduleJarProcessing(collector: JarUserDataCollector<T>, 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<State: Enum<State>> {
val key: Key<Pair<State, Long>>
val stateClass: Class<State>
val version: Int get() = 1
val init: State
val stopState: State
val notFoundState: State
fun process(file: VirtualFile): State
}
}
@@ -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<HasCompiledKotlinInJar.JarKotlinState> {
public enum class JarKotlinState {
HAS_KOTLIN,
NO_KOTLIN,
COUNTING
}
override val key = Key.create<Pair<HasCompiledKotlinInJar.JarKotlinState, Long>>(HasCompiledKotlinInJar::class.simpleName!!)
override val stateClass = javaClass<JarKotlinState>()
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
}
@@ -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<HasKotlinJSMetadataInJar.JsMetadataState> {
public enum class JsMetadataState {
HAS_JS_METADATA,
NO_JS_METADATA,
COUNTING
}
override val key = Key.create<Pair<HasKotlinJSMetadataInJar.JsMetadataState, Long>>(HasKotlinJSMetadataInJar::class.simpleName!!)
override val stateClass = javaClass<JsMetadataState>()
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
}
}
}
}
+3 -1
View File
@@ -54,7 +54,6 @@
<interface-class>org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem</interface-class>
<implementation-class>org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem</implementation-class>
</component>
</application-components>
<module-components>
@@ -171,6 +170,9 @@
<applicationService serviceInterface="org.jetbrains.kotlin.idea.quickfix.QuickFixes"
serviceImplementation="org.jetbrains.kotlin.idea.quickfix.QuickFixes"/>
<applicationService serviceInterface="org.jetbrains.kotlin.idea.caches.FileAttributeService"
serviceImplementation="org.jetbrains.kotlin.idea.caches.FileAttributeServiceImpl"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.util.ImportInsertHelper"
serviceImplementation="org.jetbrains.kotlin.util.ImportInsertHelperImpl"/>
@@ -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();
}
@@ -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<String, FileAttribute> = ContainerUtil.newConcurrentMap()
override fun register(id: String, version: Int) {
attributes[id] = FileAttribute(id, version, true)
}
override fun <T: Enum<T>> writeAttribute(id: String, file: VirtualFile, value: T): CachedAttributeData<T> {
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 <T: Enum<T>> readAttribute(id: String, file: VirtualFile, klass: Class<T>): CachedAttributeData<T>? {
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 <T: Enum<T>> deserializeEnumValue(i: Int, klass: Class<T>): T {
val method = klass.getMethod("values")
val values = method.invoke(null) as Array<T>
return values[i]
}
}