android UI xml bridge initial
- add frontend.android module - add xml ids to kotlin extension properties converter - add converter basic functional test
This commit is contained in:
committed by
Yan Zhulanow
parent
c8c1ce521b
commit
1e8769b5f7
Generated
+1
@@ -18,6 +18,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/core/descriptors/descriptors.iml" filepath="$PROJECT_DIR$/core/descriptors/descriptors.iml" group="core" />
|
||||
<module fileurl="file://$PROJECT_DIR$/eval4j/eval4j.iml" filepath="$PROJECT_DIR$/eval4j/eval4j.iml" group="ide" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compiler/frontend/frontend.iml" filepath="$PROJECT_DIR$/compiler/frontend/frontend.iml" group="compiler" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compiler/frontend.android/frontend.android.iml" filepath="$PROJECT_DIR$/compiler/frontend.android/frontend.android.iml" group="compiler" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compiler/frontend.java/frontend.java.iml" filepath="$PROJECT_DIR$/compiler/frontend.java/frontend.java.iml" group="compiler/java" />
|
||||
<module fileurl="file://$PROJECT_DIR$/generators/generators.iml" filepath="$PROJECT_DIR$/generators/generators.iml" group="infrastructure" />
|
||||
<module fileurl="file://$PROJECT_DIR$/grammar/grammar.iml" filepath="$PROJECT_DIR$/grammar/grammar.iml" group="compiler" />
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="kotlin-runtime" level="project" />
|
||||
<orderEntry type="library" name="intellij-core" level="project" />
|
||||
<orderEntry type="module" module-name="frontend" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package org.jetbrains.jet.lang.resolve.android
|
||||
|
||||
import java.nio.file.Path
|
||||
import org.jetbrains.jet.lang.psi.JetFile
|
||||
import java.util.ArrayList
|
||||
import javax.xml.parsers.SAXParserFactory
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.jet.lang.psi.JetPsiFactory
|
||||
|
||||
class AndroidUIXmlParser(val project: Project?, val searchPaths: Collection<File>) {
|
||||
|
||||
val ids: MutableCollection<AndroidID> = ArrayList()
|
||||
val kw = KotlinStringWriter()
|
||||
val androidImports = arrayListOf("android.app.Activity",
|
||||
"android.view.View")
|
||||
|
||||
public fun parse(): String {
|
||||
doParse()
|
||||
return produceKotlinSignatures().toString()
|
||||
}
|
||||
|
||||
public fun parseToPsi(): JetFile {
|
||||
return JetPsiFactory.createFile(project, parse())
|
||||
}
|
||||
|
||||
private fun isAndroidUIXml(file: File): Boolean {
|
||||
return file.extension == "xml"
|
||||
}
|
||||
|
||||
private fun searchForUIXml(paths: Collection<File>?): Collection<File> {
|
||||
if (paths == null) return ArrayList(0)
|
||||
val res = ArrayList<File>()
|
||||
for (path in paths) {
|
||||
if (path.isFile() && isAndroidUIXml(path)) {
|
||||
res.add(path)
|
||||
} else if (path.isDirectory()) {
|
||||
res.addAll(searchForUIXml(path.listFiles()?.toArrayList()))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
private fun writeImports() {
|
||||
for (elem in androidImports)
|
||||
kw.writeImport(elem)
|
||||
kw.writeEmptyLine()
|
||||
}
|
||||
|
||||
private fun doParse() {
|
||||
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 -> ids.add(AndroidID(id)) })
|
||||
writeImports()
|
||||
for (xmlStream in xmlStreams) {
|
||||
parser.parse(xmlStream, handler)
|
||||
}
|
||||
}
|
||||
|
||||
private fun produceKotlinSignatures(): StringBuffer {
|
||||
for (id in ids) {
|
||||
val body = arrayListOf("return findViewById(R.id.${id.toString()})!!")
|
||||
kw.writeImmutableExtensionProperty(receiver = "Activity",
|
||||
name = id.toString(),
|
||||
retType = "View",
|
||||
getterBody = body )
|
||||
}
|
||||
return kw.output()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.jetbrains.jet.lang.resolve.android
|
||||
|
||||
trait AndroidResource
|
||||
|
||||
class AndroidID(val rawID: String): AndroidResource {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is AndroidID && this.rawID == other.rawID
|
||||
}
|
||||
override fun hashCode(): Int {
|
||||
return rawID.hashCode()
|
||||
}
|
||||
override fun toString(): String {
|
||||
return rawID
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package org.jetbrains.jet.lang.resolve.android
|
||||
|
||||
import org.xml.sax.helpers.DefaultHandler
|
||||
import org.xml.sax.Attributes
|
||||
import java.util.HashMap
|
||||
|
||||
class AndroidXmlHandler(val idCallback: (String)-> Unit): DefaultHandler() {
|
||||
|
||||
override fun startDocument() {
|
||||
super<DefaultHandler>.startDocument()
|
||||
}
|
||||
|
||||
override fun endDocument() {
|
||||
super<DefaultHandler>.endDocument()
|
||||
}
|
||||
|
||||
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
|
||||
val hashMap = attributes.toMap()
|
||||
val s = hashMap["id"]
|
||||
val idPrefix = "@+id/"
|
||||
if (s != null && s.startsWith(idPrefix)) idCallback(s.replace(idPrefix, ""))
|
||||
}
|
||||
|
||||
override fun endElement(uri: String?, localName: String, qName: String) {
|
||||
|
||||
}
|
||||
|
||||
public fun Attributes.toMap(): HashMap<String, String> {
|
||||
val res = HashMap<String, String>()
|
||||
for (index in 0..getLength()-1) {
|
||||
val attrName = getLocalName(index)!!
|
||||
val attrVal = getValue(index)!!
|
||||
res[attrName] = attrVal
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.jetbrains.jet.lang.resolve.android
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
|
||||
open class Context(val buffer: StringBuffer = StringBuffer(), var indentDepth: Int = 0) {
|
||||
open class InvalidIndent(num: Int) : RuntimeException("Indentation level < 0: $num")
|
||||
|
||||
val indentUnit = " "
|
||||
protected var currentIndent: String = indentUnit.repeat(indentDepth)
|
||||
val children = ArrayList<Context>()
|
||||
|
||||
public fun incIndent() {
|
||||
indentDepth++
|
||||
currentIndent += indentUnit
|
||||
}
|
||||
|
||||
public fun decIndent() {
|
||||
indentDepth--
|
||||
if (indentDepth < 0)
|
||||
throw InvalidIndent(indentDepth)
|
||||
currentIndent = currentIndent.substring(0, currentIndent.length - indentUnit.length)
|
||||
}
|
||||
|
||||
public open fun write(what: String) {
|
||||
writeNoIndent(currentIndent)
|
||||
writeNoIndent(what)
|
||||
}
|
||||
|
||||
public fun writeNoIndent(what: String) {
|
||||
buffer.append(what)
|
||||
}
|
||||
|
||||
public fun writeln(what: String) {
|
||||
write(what)
|
||||
newLine()
|
||||
}
|
||||
|
||||
public fun newLine() {
|
||||
writeNoIndent("\n")
|
||||
}
|
||||
|
||||
|
||||
public fun trim(num: Int) {
|
||||
buffer.delete(buffer.length - num, buffer.length)
|
||||
}
|
||||
|
||||
public fun fork(newBuffer: StringBuffer = StringBuffer(),
|
||||
newIndentDepth: Int = indentDepth): Context {
|
||||
val child = Context(newBuffer, newIndentDepth)
|
||||
children.add(child)
|
||||
return child
|
||||
}
|
||||
|
||||
public fun adopt<T : Context>(c: T, inheritIndent: Boolean = true): T {
|
||||
children.add(c)
|
||||
if (inheritIndent) c.currentIndent = currentIndent
|
||||
return c
|
||||
}
|
||||
|
||||
public fun absorbChildren(noIndent: Boolean = true) {
|
||||
for (child in children) {
|
||||
child.absorbChildren()
|
||||
if (noIndent)
|
||||
writeNoIndent(child.toString())
|
||||
else
|
||||
write(child.toString())
|
||||
}
|
||||
children.clear()
|
||||
}
|
||||
|
||||
public override fun toString(): String {
|
||||
return buffer.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.jetbrains.jet.lang.resolve.android
|
||||
|
||||
trait KotlinWriter
|
||||
|
||||
class KotlinStringWriter : KotlinWriter {
|
||||
|
||||
val ctx = Context()
|
||||
|
||||
fun writeFunction(name: String,
|
||||
args: Collection<String>?,
|
||||
retType: String,
|
||||
body: Collection<String>) {
|
||||
val returnTerm = if (retType == "" || retType == "Unit") "" else ": $retType"
|
||||
val argStr = if (args != null) args.join(", ") else ""
|
||||
ctx.writeln("fun $name($argStr)$returnTerm {")
|
||||
ctx.incIndent()
|
||||
for (stmt in body)
|
||||
ctx.writeln(stmt)
|
||||
ctx.decIndent()
|
||||
ctx.writeln("}")
|
||||
}
|
||||
|
||||
fun writeExtensionFunction(receiver: String,
|
||||
name: String,
|
||||
args: Collection<String>?,
|
||||
retType: String,
|
||||
body: Collection<String>) {
|
||||
writeFunction("$receiver.$name", args, retType, body)
|
||||
}
|
||||
|
||||
fun writeImmutableProperty(name: String,
|
||||
retType: String,
|
||||
getterBody: Collection<String>) {
|
||||
ctx.writeln("val $name: $retType")
|
||||
ctx.incIndent()
|
||||
ctx.write("get() ")
|
||||
if (getterBody.size > 1) {
|
||||
ctx.writeNoIndent("{\n")
|
||||
ctx.incIndent()
|
||||
for (stmt in getterBody) {
|
||||
ctx.writeln(stmt)
|
||||
}
|
||||
ctx.decIndent()
|
||||
ctx.writeln("}")
|
||||
} else {
|
||||
ctx.writeNoIndent("=")
|
||||
ctx.writeNoIndent(getterBody.join("").replace("return", ""))
|
||||
ctx.newLine()
|
||||
}
|
||||
ctx.decIndent()
|
||||
ctx.newLine()
|
||||
}
|
||||
|
||||
fun writeImmutableExtensionProperty(receiver: String,
|
||||
name: String,
|
||||
retType: String,
|
||||
getterBody: Collection<String>) {
|
||||
writeImmutableProperty("$receiver.$name", retType, getterBody)
|
||||
}
|
||||
|
||||
fun writeImport(what: String) {
|
||||
ctx.writeln("import $what")
|
||||
}
|
||||
|
||||
fun writeEmptyLine() {
|
||||
ctx.newLine()
|
||||
}
|
||||
|
||||
fun output() = ctx.buffer
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import android.app.Activity
|
||||
import android.view.View
|
||||
|
||||
val Activity.item_detail_container: View
|
||||
get() = findViewById(R.id.item_detail_container)!!
|
||||
|
||||
val Activity.textView1: View
|
||||
get() = findViewById(R.id.textView1)!!
|
||||
|
||||
val Activity.password: View
|
||||
get() = findViewById(R.id.password)!!
|
||||
|
||||
val Activity.textView2: View
|
||||
get() = findViewById(R.id.textView2)!!
|
||||
|
||||
val Activity.passwordConfirmation: View
|
||||
get() = findViewById(R.id.passwordConfirmation)!!
|
||||
|
||||
val Activity.login: View
|
||||
get() = findViewById(R.id.login)!!
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/item_detail_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ItemDetailActivity"
|
||||
tools:ignore="MergeRootFrame" >
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Enter your password" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:inputType="textPassword" >
|
||||
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Repeat your password" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/passwordConfirmation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/login"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Sign in" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -21,5 +21,6 @@
|
||||
<orderEntry type="module" module-name="js.tests" />
|
||||
<orderEntry type="module" module-name="preloader" />
|
||||
<orderEntry type="module" module-name="util" />
|
||||
<orderEntry type="module" module-name="frontend.android" scope="TEST" />
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.jetbrains.jet.codegen;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jet.JetTestCaseBuilder;
|
||||
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser;
|
||||
import org.jetbrains.jet.test.TestCaseWithTmpdir;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class AndroidXmlTest extends TestCaseWithTmpdir {
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getTestDataPath() {
|
||||
return JetTestCaseBuilder.getTestDataPathBase() + "/android";
|
||||
}
|
||||
|
||||
@SuppressWarnings({"ResultOfMethodCallIgnored", "IOResourceOpenedButNotSafelyClosed"})
|
||||
protected static String loadOrCreate(File file, String data) throws IOException {
|
||||
try {
|
||||
return new Scanner(file, "UTF-8" ).useDelimiter("\\A").next();
|
||||
} catch (IOException e) {
|
||||
file.createNewFile();
|
||||
FileWriter fileWriter = new FileWriter(file);
|
||||
fileWriter.write(data);
|
||||
fileWriter.close();
|
||||
fail("Empty expected data, creating from actual");
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public void testConverterOneFile() throws Exception {
|
||||
ArrayList<File> paths = new ArrayList<File>();
|
||||
paths.add(new File(getTestDataPath() + "/layout.xml"));
|
||||
AndroidUIXmlParser parser = new AndroidUIXmlParser(null, paths);
|
||||
|
||||
String actual = parser.parse();
|
||||
String expected = loadOrCreate(new File(getTestDataPath() + "/layout.kt"), actual);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user