From 6b368e445a0045269caa8b7959edf0f50b57758e Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Fri, 27 Feb 2015 20:46:54 +0300 Subject: [PATCH] Cache XML layout widgets using PsiTreeChangePreprocessor --- build.xml | 1 + .../android-compiler-plugin.iml | 2 + .../src/AndroidComponentRegistrar.kt | 9 ++- .../AndroidPsiTreeChangePreprocessor.kt | 66 +++++++++++++++++++ .../resolve/android/AndroidResourceManager.kt | 12 +++- .../resolve/android/AndroidUIXmlProcessor.kt | 64 ++---------------- .../android/CliAndroidUIXmlProcessor.kt | 6 ++ .../resolve/android/test/CompilerTestUtils.kt | 4 ++ .../src/META-INF/plugin.xml | 3 + .../android/IDEAndroidUIXmlProcessor.kt | 10 ++- 10 files changed, 111 insertions(+), 66 deletions(-) create mode 100644 plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidPsiTreeChangePreprocessor.kt diff --git a/build.xml b/build.xml index ca883bdc774..f6acbc49418 100644 --- a/build.xml +++ b/build.xml @@ -662,6 +662,7 @@ + diff --git a/plugins/android-compiler-plugin/android-compiler-plugin.iml b/plugins/android-compiler-plugin/android-compiler-plugin.iml index aa09ebb97a7..e7ad5d837a6 100644 --- a/plugins/android-compiler-plugin/android-compiler-plugin.iml +++ b/plugins/android-compiler-plugin/android-compiler-plugin.iml @@ -18,5 +18,7 @@ + + \ No newline at end of file diff --git a/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt b/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt index f609a5e8c56..9207c04e70d 100644 --- a/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt +++ b/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt @@ -40,9 +40,6 @@ import org.jetbrains.kotlin.psi.JetClassBody import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfigurationKey -import org.jetbrains.kotlin.lang.resolve.android.AndroidConst -import org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor -import org.jetbrains.kotlin.lang.resolve.android.CliAndroidUIXmlProcessor import org.jetbrains.kotlin.resolve.scopes.receivers.ClassReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.kotlin.psi.JetThisExpression @@ -52,6 +49,9 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.codegen.state.* import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.resolve.* +import com.intellij.openapi.extensions.* +import com.intellij.psi.impl.* +import org.jetbrains.kotlin.lang.resolve.android.* public object AndroidConfigurationKeys { @@ -241,9 +241,12 @@ public class AndroidComponentRegistrar : ComponentRegistrar { if (androidResPath != null && androidManifest != null) { project.registerService(javaClass(), CliAndroidUIXmlProcessor(project, androidManifest, androidResPath)) + project.registerService(javaClass(), CliAndroidResourceManager(project, androidManifest, androidResPath)) ExternalDeclarationsProvider.registerExtension(project, CliAndroidDeclarationsProvider(project)) ExpressionCodegenExtension.registerExtension(project, AndroidExpressionCodegenExtension()) + Extensions.getArea(project).getExtensionPoint( + PsiTreeChangePreprocessor.EP_NAME).registerExtension(AndroidPsiTreeChangePreprocessor()) } } } \ No newline at end of file diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidPsiTreeChangePreprocessor.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidPsiTreeChangePreprocessor.kt new file mode 100644 index 00000000000..f8702bd995f --- /dev/null +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidPsiTreeChangePreprocessor.kt @@ -0,0 +1,66 @@ +/* + * 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.lang.resolve.android + +import com.intellij.ide.highlighter.XmlFileType +import com.intellij.openapi.components.ServiceManager +import com.intellij.psi.impl.* +import com.intellij.openapi.util.* +import com.intellij.openapi.roots.* +import com.intellij.openapi.module.* +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile + +public class AndroidPsiTreeChangePreprocessor : PsiTreeChangePreprocessor, SimpleModificationTracker() { + + default object { + private val HANDLED_EVENTS = setOf( + PsiTreeChangeEventImpl.PsiEventType.CHILD_ADDED, + PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED, + PsiTreeChangeEventImpl.PsiEventType.CHILD_REMOVED, + PsiTreeChangeEventImpl.PsiEventType.CHILD_REPLACED, + PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED) + } + + override fun treeChanged(event: PsiTreeChangeEventImpl) { + if (event.getCode() in HANDLED_EVENTS) { + val file = event.getFile() + if (file != null) { + val project = file.getProject() + + val projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex() + val module = projectFileIndex.getModuleForFile(file.getVirtualFile()) + if (module != null) { + val resourceManager = AndroidResourceManager.getInstance(module) + val mainResDirectory = resourceManager.getMainResDirectory() + val baseDirectory = file.getParent()?.getParent()?.getVirtualFile() + + //File from res/ directory was modified + if (mainResDirectory == baseDirectory && file.isLayoutXmlFile()) { + incModificationCount() + } + } + } + } + } + + private fun PsiFile.isLayoutXmlFile(): Boolean { + if (getFileType() != XmlFileType.INSTANCE) return false + return getParent()?.getName()?.startsWith("layout") ?: false + } + +} \ No newline at end of file diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidResourceManager.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidResourceManager.kt index f3b992233ce..475d658fd01 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidResourceManager.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidResourceManager.kt @@ -23,6 +23,9 @@ import com.intellij.psi.PsiManager import com.intellij.openapi.util.* import com.intellij.openapi.vfs.impl.* import com.intellij.openapi.vfs.* +import com.intellij.openapi.components.* +import com.intellij.openapi.extensions.* +import com.intellij.openapi.module.* public abstract class AndroidResourceManager(val project: Project) { @@ -61,11 +64,18 @@ public abstract class AndroidResourceManager(val project: Project) { .sortBy { it.getName() } } - fun getMainLayoutDirectory(): VirtualFile? { + fun getMainResDirectory(): VirtualFile? { val info = androidModuleInfo if (info == null) return null return VirtualFileManager.getInstance().findFileByUrl("file://" + info.mainResDirectory) } + default object { + public fun getInstance(module: Module): AndroidResourceManager { + val service = ModuleServiceManager.getService(module, javaClass()) + return service ?: module.getComponent(javaClass()) + } + } + } diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt index c955484f844..83b366283c5 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt @@ -37,7 +37,6 @@ import org.xml.sax.helpers.DefaultHandler import org.xml.sax.Attributes import com.intellij.psi.PsiFileFactory import com.intellij.psi.util.PsiModificationTracker -import com.intellij.psi.impl.PsiModificationTrackerImpl import java.util.Queue import com.intellij.psi.PsiFile import com.intellij.openapi.diagnostic.Logger @@ -52,6 +51,7 @@ import com.intellij.openapi.util.* import com.intellij.openapi.vfs.impl.* import com.intellij.openapi.vfs.* import kotlin.properties.* +import com.intellij.psi.impl.* public abstract class AndroidUIXmlProcessor(protected val project: Project) { @@ -64,13 +64,11 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { public abstract val resourceManager: AndroidResourceManager - private val vfsTracker: VfsModificationTracker by Delegates.lazy { - VfsModificationTracker(project, resourceManager.getMainLayoutDirectory()) - } + public abstract val psiTreeChangePreprocessor: PsiTreeChangePreprocessor private val cachedSources: CachedValue> by Delegates.lazy { cachedValue { - Result.create(parse(), vfsTracker) + Result.create(parse(), psiTreeChangePreprocessor) } } @@ -136,58 +134,4 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { return CachedValuesManager.getManager(project).createCachedValue(result, false) } -} - -private class VfsModificationTracker(project: Project, resDirectory: VirtualFile?): SimpleModificationTracker() { - { - val connection = project.getMessageBus().connect(); - connection.subscribe(VirtualFileManager.VFS_CHANGES, BulkVirtualFileListenerAdapter( - object : VirtualFileListener { - fun incModificationCountIfLayout(file: VirtualFile) { - if (resDirectory == null) { - incModificationCount() - } else { - val probablyLayoutDir = file.getParent() - val probablyResDir = file.getParent()?.getParent() - if (resDirectory == probablyResDir && probablyLayoutDir?.getName()?.startsWith("layout") ?: false) { - incModificationCount() - } - } - } - - override fun contentsChanged(event: VirtualFileEvent) { - incModificationCountIfLayout(event.getFile()) - } - - override fun propertyChanged(event: VirtualFilePropertyEvent) { - incModificationCountIfLayout(event.getFile()) - } - - override fun fileCreated(event: VirtualFileEvent) { - incModificationCountIfLayout(event.getFile()) - } - - override fun fileDeleted(event: VirtualFileEvent) { - incModificationCountIfLayout(event.getFile()) - } - - override fun fileMoved(event: VirtualFileMoveEvent) { - incModificationCountIfLayout(event.getFile()) - } - - override fun fileCopied(event: VirtualFileCopyEvent) { - incModificationCountIfLayout(event.getFile()) - } - - override fun beforePropertyChange(event: VirtualFilePropertyEvent) {} - - override fun beforeContentsChange(event: VirtualFileEvent) {} - - override fun beforeFileDeletion(event: VirtualFileEvent) {} - - override fun beforeFileMovement(event: VirtualFileMoveEvent) {} - } - )) - } -} - +} \ No newline at end of file diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/CliAndroidUIXmlProcessor.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/CliAndroidUIXmlProcessor.kt index c802eb48081..be7faa2a097 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/CliAndroidUIXmlProcessor.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/CliAndroidUIXmlProcessor.kt @@ -21,6 +21,8 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import java.io.ByteArrayInputStream import kotlin.properties.Delegates +import com.intellij.psi.impl.* +import com.intellij.openapi.components.* public class CliAndroidUIXmlProcessor( project: Project, @@ -32,6 +34,10 @@ public class CliAndroidUIXmlProcessor( CliAndroidResourceManager(project, manifestPath, mainResDirectory) } + override val psiTreeChangePreprocessor: PsiTreeChangePreprocessor by Delegates.lazy { + project.getExtensions(PsiTreeChangePreprocessor.EP_NAME).first { it is AndroidPsiTreeChangePreprocessor } + } + override fun parseSingleFile(file: PsiFile): Collection { val widgets: MutableCollection = ArrayList() val handler = AndroidXmlHandler(resourceManager, { id, clazz -> widgets.add(AndroidWidget(id, clazz)) }) diff --git a/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/CompilerTestUtils.kt b/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/CompilerTestUtils.kt index 4345437b070..f71d789ef34 100644 --- a/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/CompilerTestUtils.kt +++ b/plugins/android-compiler-plugin/tests/org/jetbrains/kotlin/lang/resolve/android/test/CompilerTestUtils.kt @@ -16,7 +16,9 @@ package org.jetbrains.kotlin.lang.resolve.android.test +import com.intellij.openapi.extensions.Extensions import com.intellij.openapi.project.Project +import com.intellij.psi.impl.PsiTreeChangePreprocessor import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider import org.jetbrains.kotlin.android.AndroidConfigurationKeys import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension @@ -27,6 +29,7 @@ import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.cli.jvm.compiler.JetCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.lang.resolve.android.AndroidPsiTreeChangePreprocessor import org.jetbrains.kotlin.lang.resolve.android.CliAndroidUIXmlProcessor private class AndroidTestExternalDeclarationsProvider( @@ -51,5 +54,6 @@ fun UsefulTestCase.createAndroidTestEnvironment( val project = myEnvironment.getProject() ExternalDeclarationsProvider.registerExtension(project, AndroidTestExternalDeclarationsProvider(project, resPath, manifestPath)) ExpressionCodegenExtension.registerExtension(project, AndroidExpressionCodegenExtension()) + Extensions.getArea(project).getExtensionPoint(PsiTreeChangePreprocessor.EP_NAME).registerExtension(AndroidPsiTreeChangePreprocessor()) return myEnvironment } diff --git a/plugins/android-idea-plugin/src/META-INF/plugin.xml b/plugins/android-idea-plugin/src/META-INF/plugin.xml index 6e41fbe2ca6..de5cbff9256 100644 --- a/plugins/android-idea-plugin/src/META-INF/plugin.xml +++ b/plugins/android-idea-plugin/src/META-INF/plugin.xml @@ -14,8 +14,11 @@ + + diff --git a/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt b/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt index b631e07949f..9edd7fe2252 100644 --- a/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt +++ b/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt @@ -16,17 +16,23 @@ package org.jetbrains.kotlin.plugin.android +import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.module.Module import org.jetbrains.kotlin.plugin.android.IDEAndroidResourceManager import com.intellij.psi.PsiFile import org.jetbrains.kotlin.plugin.android.AndroidXmlVisitor -import org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor -import org.jetbrains.kotlin.lang.resolve.android.AndroidWidget +import com.intellij.psi.impl.* +import kotlin.properties.* +import org.jetbrains.kotlin.lang.resolve.android.* class IDEAndroidUIXmlProcessor(val module: Module) : AndroidUIXmlProcessor(module.getProject()) { override val resourceManager: IDEAndroidResourceManager = IDEAndroidResourceManager(module) + override val psiTreeChangePreprocessor by Delegates.lazy { + module.getProject().getExtensions(PsiTreeChangePreprocessor.EP_NAME).first { it is AndroidPsiTreeChangePreprocessor } + } + override fun parseSingleFile(file: PsiFile): List { val widgets = arrayListOf() file.accept(AndroidXmlVisitor(resourceManager, { id, wClass, valueElement ->