Create background jar-processor that can store meta information for jars
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
+40
-2
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user