From eb7534f3690240471847777073cb2dffce4ee2dd Mon Sep 17 00:00:00 2001 From: Mikhail Mutcianko Date: Thu, 17 Jul 2014 18:01:17 +0400 Subject: [PATCH] add converter output caching - add two level caching: per xml conversion result and complete resulting JetFile, so that only modified xmls are actually converted, and if no change has been made to any of those, last successfully built psi file is returned - merge AndroidUIXmlPathProvider and AndroidUIXmlParser - update tests --- .../cli/jvm/compiler/JetCoreEnvironment.java | 7 +- .../resolve/android/AndroidUIXmlParser.kt | 102 ++++++++++++++---- .../resolve/android/CliAndroidUIXmlParser.kt | 6 ++ .../jet/lang/resolve/android/KotlinWriter.kt | 6 +- .../jvm/TopDownAnalyzerFacadeForJVM.java | 5 +- .../jetbrains/jet/codegen/AndroidXmlTest.java | 9 +- idea/src/META-INF/plugin.xml | 4 +- .../plugin/android/IDEAndroidUIXmlParser.kt | 9 ++ 8 files changed, 108 insertions(+), 40 deletions(-) create mode 100644 compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/CliAndroidUIXmlParser.kt create mode 100644 idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlParser.kt diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JetCoreEnvironment.java b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JetCoreEnvironment.java index 4488cf07eb4..660e2af16ef 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JetCoreEnvironment.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JetCoreEnvironment.java @@ -56,8 +56,8 @@ import kotlin.Function1; import kotlin.Unit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; -import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlPathProvider; -import org.jetbrains.jet.lang.resolve.android.CliAndroidUIXmlPathProvider; +import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser; +import org.jetbrains.jet.lang.resolve.android.CliAndroidUIXmlParser; import org.jetbrains.kotlin.asJava.JavaElementFinder; import org.jetbrains.kotlin.asJava.KotlinLightClassForPackage; import org.jetbrains.kotlin.asJava.LightClassGenerationSupport; @@ -296,8 +296,7 @@ public class JetCoreEnvironment { ); String s = configuration.get(JVMConfigurationKeys.ANDROID_RES_PATH); - project.registerService(AndroidUIXmlPathProvider.class, new CliAndroidUIXmlPathProvider(s)); - + project.registerService(AndroidUIXmlParser.class, new CliAndroidUIXmlParser(s)); project.registerService(VirtualFileFinderFactory.class, new CliVirtualFileFinderFactory(classPath)); } diff --git a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUIXmlParser.kt b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUIXmlParser.kt index 694f39de8ee..73b756f9985 100644 --- a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUIXmlParser.kt +++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUIXmlParser.kt @@ -7,30 +7,61 @@ import java.io.FileInputStream import com.intellij.openapi.project.Project import org.jetbrains.kotlin.psi.JetPsiFactory import org.jetbrains.kotlin.psi.JetFile +import javax.xml.parsers.SAXParser +import java.util.HashMap -class AndroidUIXmlParser(val project: Project?, val searchPaths: Collection) { +abstract class AndroidUIXmlParser { - inner class NoUIXMLsFound: Exception("No android UI xmls found in ${searchPaths.joinToString(", ")}") + inner class NoUIXMLsFound: Exception("No android UI xmls found in $searchPath") + + enum class CacheAction { HIT; MISS } - val ids: MutableCollection = ArrayList() - val kw = KotlinStringWriter() val androidImports = arrayListOf("android.app.Activity", "android.view.View", "android.widget.*") + abstract val searchPath: String? + + val saxParser = initSAX() + + val fileCache = HashMap() + var lastCachedPsi: JetFile? = null + val fileModificationTime = HashMap() public fun parseToString(): String? { - return doParse() + val cacheState = doParse() + if (cacheState == null) return null + return renderString() } - public fun parseToPsi(): JetFile? { - val s = parseToString() - return if (s != null) JetPsiFactory(project).createFile(s) else null + public fun parseToPsi(project: Project): JetFile? { + val cacheState = doParse() + if (cacheState == null) return null + return if (cacheState === CacheAction.MISS) { + try { + val psiFile = JetPsiFactory(project).createFile(renderString()) + lastCachedPsi = psiFile + psiFile + } catch (e: Exception) { + invalidateCaches() + null + } + } else lastCachedPsi } private fun isAndroidUIXml(file: File): Boolean { return file.extension == "xml" } + private fun initSAX(): SAXParser { + val saxFactory = SAXParserFactory.newInstance() + saxFactory?.setNamespaceAware(true) + return saxFactory!!.newSAXParser() + } + + private fun searchForUIXml(path: String): Collection { + return searchForUIXml(arrayListOf(File(path))) + } + private fun searchForUIXml(paths: Collection?): Collection { if (paths == null) return ArrayList(0) val res = ArrayList() @@ -45,31 +76,56 @@ class AndroidUIXmlParser(val project: Project?, val searchPaths: Collection widgetCallback(id, wClass) }) - writeImports() - for (xmlStream in xmlStreams) { - parser.parse(xmlStream, handler) + private fun parseSingleFileWithCache(file: File): Pair { + val lastRecorded = fileModificationTime[file] ?: -1 + if (file.lastModified() > lastRecorded) + return Pair(parseSingleFile(file), CacheAction.MISS) + else + return Pair(fileCache[file]!!, CacheAction.HIT) + } + + private fun parseSingleFile(file: File): String { + val ids: MutableCollection = ArrayList() + val handler = AndroidXmlHandler({ id, wClass -> ids.add(AndroidWidget(id, wClass)) }) + saxParser.parse(FileInputStream(file), handler) + val res = produceKotlinProperties(KotlinStringWriter(), ids).toString() + fileCache[file] = res + fileModificationTime[file] = file.lastModified() + return res + } + + private fun doParse(): CacheAction? { + if (searchPath == null || searchPath == "") return null + val files = searchForUIXml(searchPath!!) + var overallCacheMiss = false + for (file in files) { + val res = parseSingleFileWithCache(file) + overallCacheMiss = overallCacheMiss or (res.second == CacheAction.MISS) } - return produceKotlinSignatures().toString() + return if (overallCacheMiss) CacheAction.MISS else CacheAction.HIT } - private fun widgetCallback(id: String, className: String) { - ids.add(AndroidWidget(id, className)) + private fun renderString(): String { + val buffer = writeImports(KotlinStringWriter()).output() + for (buf in fileCache.values()) + buffer.append(buf) + return buffer.toString() } - private fun produceKotlinSignatures(): StringBuffer { + private fun invalidateCaches() { + fileCache.clear() + fileModificationTime.clear() + lastCachedPsi = null + } + + private fun produceKotlinProperties(kw: KotlinStringWriter, ids: Collection): StringBuffer { for (id in ids) { val body = arrayListOf("return findViewById(R.id.${id.id}) as ${id.className}") kw.writeImmutableExtensionProperty(receiver = "Activity", diff --git a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/CliAndroidUIXmlParser.kt b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/CliAndroidUIXmlParser.kt new file mode 100644 index 00000000000..0d4254cabb5 --- /dev/null +++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/CliAndroidUIXmlParser.kt @@ -0,0 +1,6 @@ +package org.jetbrains.jet.lang.resolve.android + +import com.intellij.openapi.project.Project + +class CliAndroidUIXmlParser(override val searchPath: String?): AndroidUIXmlParser() + diff --git a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/KotlinWriter.kt b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/KotlinWriter.kt index 77ee3c1e58e..c7d00e9f68c 100644 --- a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/KotlinWriter.kt +++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/KotlinWriter.kt @@ -1,6 +1,8 @@ package org.jetbrains.jet.lang.resolve.android -trait KotlinWriter +trait KotlinWriter { + fun output(): StringBuffer +} class KotlinStringWriter : KotlinWriter { @@ -68,7 +70,7 @@ class KotlinStringWriter : KotlinWriter { body.newLine() } - fun output(): StringBuffer { + override fun output(): StringBuffer { ctx.absorbChildren() return ctx.buffer } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/TopDownAnalyzerFacadeForJVM.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/TopDownAnalyzerFacadeForJVM.java index 6ba51df2bdd..e73c79e36d5 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/TopDownAnalyzerFacadeForJVM.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/TopDownAnalyzerFacadeForJVM.java @@ -178,9 +178,8 @@ public enum TopDownAnalyzerFacadeForJVM { } private static Collection searchAndAddAndroidDeclarations(Project project, Collection files) { - AndroidUIXmlPathProvider provider = ServiceManager.getService(project, AndroidUIXmlPathProvider.class); - Collection paths = provider.getPaths(); - JetFile file = new AndroidUIXmlParser(project, paths).parseToPsi(); + AndroidUIXmlParser parser = ServiceManager.getService(project, AndroidUIXmlParser.class); + JetFile file = parser.parseToPsi(project); if (file != null) files.add(file); return files; } diff --git a/compiler/tests/org/jetbrains/jet/codegen/AndroidXmlTest.java b/compiler/tests/org/jetbrains/jet/codegen/AndroidXmlTest.java index e45f3cc22be..2bc4391cbd2 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/AndroidXmlTest.java +++ b/compiler/tests/org/jetbrains/jet/codegen/AndroidXmlTest.java @@ -8,6 +8,7 @@ import org.jetbrains.jet.JetTestUtils; import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment; import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser; +import org.jetbrains.jet.lang.resolve.android.CliAndroidUIXmlParser; import org.jetbrains.jet.test.TestCaseWithTmpdir; import java.io.File; @@ -49,9 +50,7 @@ public class AndroidXmlTest extends TestCaseWithTmpdir { } public void testCompileResult() throws Exception { - ArrayList paths = new ArrayList(); - paths.add(singleFile); - String text = new AndroidUIXmlParser(null, paths).parseToString(); + String text = new CliAndroidUIXmlParser(singleFile.getAbsolutePath()).parseToString(); JetCoreEnvironment jetCoreEnvironment = JetTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(getTestRootDisposable(), ConfigurationKind.ALL); JetFile psiFile = JetTestUtils.createFile(singleFile.getName(), text, jetCoreEnvironment.getProject()); @@ -71,9 +70,7 @@ public class AndroidXmlTest extends TestCaseWithTmpdir { } public void testConverterOneFile() throws Exception { - ArrayList paths = new ArrayList(); - paths.add(singleFile); - AndroidUIXmlParser parser = new AndroidUIXmlParser(null, paths); + AndroidUIXmlParser parser = new CliAndroidUIXmlParser(singleFile.getAbsolutePath()); String actual = parser.parseToString(); String expected = loadOrCreate(new File(getTestDataPath() + "/converter/singleFile/layout.kt"), actual); diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 4d9600e5a91..1fdad4984e7 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -192,8 +192,8 @@ - + diff --git a/idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlParser.kt b/idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlParser.kt new file mode 100644 index 00000000000..74643af68fd --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlParser.kt @@ -0,0 +1,9 @@ +package org.jetbrains.jet.plugin.android + +import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser +import com.intellij.openapi.project.Project + +class IDEAndroidUIXmlParser(project: Project): AndroidUIXmlParser() { + override val searchPath: String? = project.getBasePath() + "/res/layout/" +} +