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/" +} +