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:
committed by
Yan Zhulanow
parent
f5b9b155cd
commit
eb7534f369
@@ -56,8 +56,8 @@ import kotlin.Function1;
|
|||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.TestOnly;
|
import org.jetbrains.annotations.TestOnly;
|
||||||
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlPathProvider;
|
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser;
|
||||||
import org.jetbrains.jet.lang.resolve.android.CliAndroidUIXmlPathProvider;
|
import org.jetbrains.jet.lang.resolve.android.CliAndroidUIXmlParser;
|
||||||
import org.jetbrains.kotlin.asJava.JavaElementFinder;
|
import org.jetbrains.kotlin.asJava.JavaElementFinder;
|
||||||
import org.jetbrains.kotlin.asJava.KotlinLightClassForPackage;
|
import org.jetbrains.kotlin.asJava.KotlinLightClassForPackage;
|
||||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport;
|
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport;
|
||||||
@@ -296,8 +296,7 @@ public class JetCoreEnvironment {
|
|||||||
);
|
);
|
||||||
|
|
||||||
String s = configuration.get(JVMConfigurationKeys.ANDROID_RES_PATH);
|
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));
|
project.registerService(VirtualFileFinderFactory.class, new CliVirtualFileFinderFactory(classPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+79
-23
@@ -7,30 +7,61 @@ import java.io.FileInputStream
|
|||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import org.jetbrains.kotlin.psi.JetPsiFactory
|
import org.jetbrains.kotlin.psi.JetPsiFactory
|
||||||
import org.jetbrains.kotlin.psi.JetFile
|
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",
|
val androidImports = arrayListOf("android.app.Activity",
|
||||||
"android.view.View",
|
"android.view.View",
|
||||||
"android.widget.*")
|
"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? {
|
public fun parseToString(): String? {
|
||||||
return doParse()
|
val cacheState = doParse()
|
||||||
|
if (cacheState == null) return null
|
||||||
|
return renderString()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun parseToPsi(): JetFile? {
|
public fun parseToPsi(project: Project): JetFile? {
|
||||||
val s = parseToString()
|
val cacheState = doParse()
|
||||||
return if (s != null) JetPsiFactory(project).createFile(s) else null
|
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 {
|
private fun isAndroidUIXml(file: File): Boolean {
|
||||||
return file.extension == "xml"
|
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> {
|
private fun searchForUIXml(paths: Collection<File>?): Collection<File> {
|
||||||
if (paths == null) return ArrayList(0)
|
if (paths == null) return ArrayList(0)
|
||||||
val res = ArrayList<File>()
|
val res = ArrayList<File>()
|
||||||
@@ -45,31 +76,56 @@ class AndroidUIXmlParser(val project: Project?, val searchPaths: Collection<File
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeImports() {
|
private fun writeImports(kw: KotlinStringWriter): KotlinWriter {
|
||||||
for (elem in androidImports)
|
for (elem in androidImports)
|
||||||
kw.writeImport(elem)
|
kw.writeImport(elem)
|
||||||
kw.writeEmptyLine()
|
kw.writeEmptyLine()
|
||||||
|
return kw
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doParse(): String? {
|
private fun parseSingleFileWithCache(file: File): Pair<String, CacheAction> {
|
||||||
if (searchPaths.empty) return null
|
val lastRecorded = fileModificationTime[file] ?: -1
|
||||||
val xmlStreams = searchForUIXml(searchPaths).map { FileInputStream(it) }
|
if (file.lastModified() > lastRecorded)
|
||||||
val factory = SAXParserFactory.newInstance()
|
return Pair(parseSingleFile(file), CacheAction.MISS)
|
||||||
factory?.setNamespaceAware(true)
|
else
|
||||||
val parser = factory!!.newSAXParser() // TODO: annotate this
|
return Pair(fileCache[file]!!, CacheAction.HIT)
|
||||||
val handler = AndroidXmlHandler({ id, wClass -> widgetCallback(id, wClass) })
|
}
|
||||||
writeImports()
|
|
||||||
for (xmlStream in xmlStreams) {
|
private fun parseSingleFile(file: File): String {
|
||||||
parser.parse(xmlStream, handler)
|
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) {
|
private fun renderString(): String {
|
||||||
ids.add(AndroidWidget(id, className))
|
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) {
|
for (id in ids) {
|
||||||
val body = arrayListOf("return findViewById(R.id.${id.id}) as ${id.className}")
|
val body = arrayListOf("return findViewById(R.id.${id.id}) as ${id.className}")
|
||||||
kw.writeImmutableExtensionProperty(receiver = "Activity",
|
kw.writeImmutableExtensionProperty(receiver = "Activity",
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package org.jetbrains.jet.lang.resolve.android
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
|
||||||
|
class CliAndroidUIXmlParser(override val searchPath: String?): AndroidUIXmlParser()
|
||||||
|
|
||||||
+4
-2
@@ -1,6 +1,8 @@
|
|||||||
package org.jetbrains.jet.lang.resolve.android
|
package org.jetbrains.jet.lang.resolve.android
|
||||||
|
|
||||||
trait KotlinWriter
|
trait KotlinWriter {
|
||||||
|
fun output(): StringBuffer
|
||||||
|
}
|
||||||
|
|
||||||
class KotlinStringWriter : KotlinWriter {
|
class KotlinStringWriter : KotlinWriter {
|
||||||
|
|
||||||
@@ -68,7 +70,7 @@ class KotlinStringWriter : KotlinWriter {
|
|||||||
body.newLine()
|
body.newLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun output(): StringBuffer {
|
override fun output(): StringBuffer {
|
||||||
ctx.absorbChildren()
|
ctx.absorbChildren()
|
||||||
return ctx.buffer
|
return ctx.buffer
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -178,9 +178,8 @@ public enum TopDownAnalyzerFacadeForJVM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Collection<JetFile> searchAndAddAndroidDeclarations(Project project, Collection<JetFile> files) {
|
private static Collection<JetFile> searchAndAddAndroidDeclarations(Project project, Collection<JetFile> files) {
|
||||||
AndroidUIXmlPathProvider provider = ServiceManager.getService(project, AndroidUIXmlPathProvider.class);
|
AndroidUIXmlParser parser = ServiceManager.getService(project, AndroidUIXmlParser.class);
|
||||||
Collection<File> paths = provider.getPaths();
|
JetFile file = parser.parseToPsi(project);
|
||||||
JetFile file = new AndroidUIXmlParser(project, paths).parseToPsi();
|
|
||||||
if (file != null) files.add(file);
|
if (file != null) files.add(file);
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.jetbrains.jet.JetTestUtils;
|
|||||||
import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment;
|
import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment;
|
||||||
import org.jetbrains.jet.lang.psi.JetFile;
|
import org.jetbrains.jet.lang.psi.JetFile;
|
||||||
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser;
|
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser;
|
||||||
|
import org.jetbrains.jet.lang.resolve.android.CliAndroidUIXmlParser;
|
||||||
import org.jetbrains.jet.test.TestCaseWithTmpdir;
|
import org.jetbrains.jet.test.TestCaseWithTmpdir;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -49,9 +50,7 @@ public class AndroidXmlTest extends TestCaseWithTmpdir {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testCompileResult() throws Exception {
|
public void testCompileResult() throws Exception {
|
||||||
ArrayList<File> paths = new ArrayList<File>();
|
String text = new CliAndroidUIXmlParser(singleFile.getAbsolutePath()).parseToString();
|
||||||
paths.add(singleFile);
|
|
||||||
String text = new AndroidUIXmlParser(null, paths).parseToString();
|
|
||||||
JetCoreEnvironment jetCoreEnvironment = JetTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(getTestRootDisposable(),
|
JetCoreEnvironment jetCoreEnvironment = JetTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(getTestRootDisposable(),
|
||||||
ConfigurationKind.ALL);
|
ConfigurationKind.ALL);
|
||||||
JetFile psiFile = JetTestUtils.createFile(singleFile.getName(), text, jetCoreEnvironment.getProject());
|
JetFile psiFile = JetTestUtils.createFile(singleFile.getName(), text, jetCoreEnvironment.getProject());
|
||||||
@@ -71,9 +70,7 @@ public class AndroidXmlTest extends TestCaseWithTmpdir {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testConverterOneFile() throws Exception {
|
public void testConverterOneFile() throws Exception {
|
||||||
ArrayList<File> paths = new ArrayList<File>();
|
AndroidUIXmlParser parser = new CliAndroidUIXmlParser(singleFile.getAbsolutePath());
|
||||||
paths.add(singleFile);
|
|
||||||
AndroidUIXmlParser parser = new AndroidUIXmlParser(null, paths);
|
|
||||||
|
|
||||||
String actual = parser.parseToString();
|
String actual = parser.parseToString();
|
||||||
String expected = loadOrCreate(new File(getTestDataPath() + "/converter/singleFile/layout.kt"), actual);
|
String expected = loadOrCreate(new File(getTestDataPath() + "/converter/singleFile/layout.kt"), actual);
|
||||||
|
|||||||
@@ -192,8 +192,8 @@
|
|||||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.stubindex.SubpackagesIndexService"
|
<projectService serviceInterface="org.jetbrains.kotlin.idea.stubindex.SubpackagesIndexService"
|
||||||
serviceImplementation="org.jetbrains.kotlin.idea.stubindex.SubpackagesIndexService"/>
|
serviceImplementation="org.jetbrains.kotlin.idea.stubindex.SubpackagesIndexService"/>
|
||||||
|
|
||||||
<projectService serviceInterface="org.jetbrains.jet.lang.resolve.android.AndroidUIXmlPathProvider"
|
<projectService serviceInterface="org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser"
|
||||||
serviceImplementation="org.jetbrains.jet.plugin.android.IDEAndroidUIXmlPathProvider"/>
|
serviceImplementation="org.jetbrains.jet.plugin.android.IDEAndroidUIXmlParser"/>
|
||||||
|
|
||||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.LibraryModificationTracker"
|
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.LibraryModificationTracker"
|
||||||
serviceImplementation="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/"
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user