diff --git a/.idea/modules.xml b/.idea/modules.xml
index 1b08050b935..094a2eef4d5 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -18,6 +18,7 @@
+
diff --git a/compiler/frontend.android/frontend.android.iml b/compiler/frontend.android/frontend.android.iml
new file mode 100644
index 00000000000..95b0d77a8da
--- /dev/null
+++ b/compiler/frontend.android/frontend.android.iml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
new file mode 100644
index 00000000000..fe434cf8b6c
--- /dev/null
+++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUIXmlParser.kt
@@ -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) {
+
+ val ids: MutableCollection = 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?): Collection {
+ if (paths == null) return ArrayList(0)
+ val res = ArrayList()
+ 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()
+ }
+
+}
diff --git a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUtil.kt b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUtil.kt
new file mode 100644
index 00000000000..3d7884877fe
--- /dev/null
+++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUtil.kt
@@ -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
+ }
+}
+
diff --git a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidXmlHandler.kt b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidXmlHandler.kt
new file mode 100644
index 00000000000..ea5baf6447d
--- /dev/null
+++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidXmlHandler.kt
@@ -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.startDocument()
+ }
+
+ override fun endDocument() {
+ super.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 {
+ val res = HashMap()
+ for (index in 0..getLength()-1) {
+ val attrName = getLocalName(index)!!
+ val attrVal = getValue(index)!!
+ res[attrName] = attrVal
+ }
+ return res
+ }
+}
+
+
+
diff --git a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/Context.kt b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/Context.kt
new file mode 100644
index 00000000000..401dd5f81c7
--- /dev/null
+++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/Context.kt
@@ -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()
+
+ 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(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()
+ }
+}
+
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
new file mode 100644
index 00000000000..deece36773e
--- /dev/null
+++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/KotlinWriter.kt
@@ -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?,
+ retType: String,
+ body: Collection) {
+ 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?,
+ retType: String,
+ body: Collection) {
+ writeFunction("$receiver.$name", args, retType, body)
+ }
+
+ fun writeImmutableProperty(name: String,
+ retType: String,
+ getterBody: Collection) {
+ 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) {
+ writeImmutableProperty("$receiver.$name", retType, getterBody)
+ }
+
+ fun writeImport(what: String) {
+ ctx.writeln("import $what")
+ }
+
+ fun writeEmptyLine() {
+ ctx.newLine()
+ }
+
+ fun output() = ctx.buffer
+}
+
diff --git a/compiler/testData/android/layout.kt b/compiler/testData/android/layout.kt
new file mode 100644
index 00000000000..5c69910f2f3
--- /dev/null
+++ b/compiler/testData/android/layout.kt
@@ -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)!!
+
diff --git a/compiler/testData/android/layout.xml b/compiler/testData/android/layout.xml
new file mode 100644
index 00000000000..364427e595d
--- /dev/null
+++ b/compiler/testData/android/layout.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/compiler/tests/compiler-tests.iml b/compiler/tests/compiler-tests.iml
index caacdfc6482..500933988df 100644
--- a/compiler/tests/compiler-tests.iml
+++ b/compiler/tests/compiler-tests.iml
@@ -21,5 +21,6 @@
+
-
\ No newline at end of file
+
diff --git a/compiler/tests/org/jetbrains/jet/codegen/AndroidXmlTest.java b/compiler/tests/org/jetbrains/jet/codegen/AndroidXmlTest.java
new file mode 100644
index 00000000000..38b5f9dbe37
--- /dev/null
+++ b/compiler/tests/org/jetbrains/jet/codegen/AndroidXmlTest.java
@@ -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 paths = new ArrayList();
+ 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);
+ }
+}