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
This commit is contained in:
Mikhail Mutcianko
2014-07-17 18:01:17 +04:00
committed by Yan Zhulanow
parent f5b9b155cd
commit eb7534f369
8 changed files with 108 additions and 40 deletions
@@ -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));
}
@@ -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<File>) {
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<AndroidWidget> = 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<File, String>()
var lastCachedPsi: JetFile? = null
val fileModificationTime = HashMap<File, Long>()
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<File> {
return searchForUIXml(arrayListOf(File(path)))
}
private fun searchForUIXml(paths: Collection<File>?): Collection<File> {
if (paths == null) return ArrayList(0)
val res = ArrayList<File>()
@@ -45,31 +76,56 @@ class AndroidUIXmlParser(val project: Project?, val searchPaths: Collection<File
return res
}
private fun writeImports() {
private fun writeImports(kw: KotlinStringWriter): KotlinWriter {
for (elem in androidImports)
kw.writeImport(elem)
kw.writeEmptyLine()
return kw
}
private fun doParse(): String? {
if (searchPaths.empty) return null
val xmlStreams = searchForUIXml(searchPaths).map { FileInputStream(it) }
val factory = SAXParserFactory.newInstance()
factory?.setNamespaceAware(true)
val parser = factory!!.newSAXParser() // TODO: annotate this
val handler = AndroidXmlHandler({ id, wClass -> widgetCallback(id, wClass) })
writeImports()
for (xmlStream in xmlStreams) {
parser.parse(xmlStream, handler)
private fun parseSingleFileWithCache(file: File): Pair<String, CacheAction> {
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<AndroidWidget> = 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<AndroidWidget>): StringBuffer {
for (id in ids) {
val body = arrayListOf("return findViewById(R.id.${id.id}) as ${id.className}")
kw.writeImmutableExtensionProperty(receiver = "Activity",
@@ -0,0 +1,6 @@
package org.jetbrains.jet.lang.resolve.android
import com.intellij.openapi.project.Project
class CliAndroidUIXmlParser(override val searchPath: String?): AndroidUIXmlParser()
@@ -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
}
@@ -178,9 +178,8 @@ public enum TopDownAnalyzerFacadeForJVM {
}
private static Collection<JetFile> searchAndAddAndroidDeclarations(Project project, Collection<JetFile> files) {
AndroidUIXmlPathProvider provider = ServiceManager.getService(project, AndroidUIXmlPathProvider.class);
Collection<File> 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;
}
@@ -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<File> paths = new ArrayList<File>();
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<File> paths = new ArrayList<File>();
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);
+2 -2
View File
@@ -192,8 +192,8 @@
<projectService serviceInterface="org.jetbrains.kotlin.idea.stubindex.SubpackagesIndexService"
serviceImplementation="org.jetbrains.kotlin.idea.stubindex.SubpackagesIndexService"/>
<projectService serviceInterface="org.jetbrains.jet.lang.resolve.android.AndroidUIXmlPathProvider"
serviceImplementation="org.jetbrains.jet.plugin.android.IDEAndroidUIXmlPathProvider"/>
<projectService serviceInterface="org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser"
serviceImplementation="org.jetbrains.jet.plugin.android.IDEAndroidUIXmlParser"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.LibraryModificationTracker"
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.LibraryModificationTracker"/>
@@ -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/"
}