frontend.android renamed to android-compiler-plugin

This commit is contained in:
Andrey Breslav
2014-09-23 18:11:03 +04:00
committed by Yan Zhulanow
parent 35028227a0
commit 565ce5a781
19 changed files with 35 additions and 17 deletions
@@ -0,0 +1,16 @@
<?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="module" module-name="plugin-api" exported="" />
<orderEntry type="module" module-name="frontend" />
<orderEntry type="library" name="kotlin-runtime" level="project" />
<orderEntry type="module" module-name="cli" />
</component>
</module>
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve.android;
import com.intellij.openapi.util.Key;
public class AndroidConst {
public static final Key<String> ANDROID_USER_PACKAGE = Key.create("ANDROID_USER_PACKAGE");
public static final String SYNTHETIC_FILENAME = "ANDROIDXML.kt";
}
@@ -0,0 +1,50 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve.android
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiElement
import com.intellij.openapi.project.Project
abstract class AndroidResourceManager(protected val project: Project, protected val searchPath: String?) {
private val idDeclarationPrefix = "@+id/"
private val idUsagePrefix = "@id/"
public val androidNamespace: String = "android"
public val idAttributeNoNamespace: String = "id"
public val idAttribute: String = androidNamespace + ":" + idAttributeNoNamespace
public val classAttributeNoNamespace: String = "class"
public val classAttribute: String = androidNamespace + ":" + classAttributeNoNamespace
abstract fun getLayoutXmlFiles(): Collection<PsiFile>
abstract fun idToXmlAttribute(id: String): PsiElement?
public fun nameToIdDeclaration(name: String): String = idDeclarationPrefix + name
public fun nameToIdUsage(name: String): String = idUsagePrefix + name
public fun idToName(id: String?): String {
return if (isResourceIdDeclaration(id)) id!!.replace(idDeclarationPrefix, "")
else if (isResourceIdUsage(id)) id!!.replace(idUsagePrefix, "")
else throw WrongIdFormat(id)
}
public fun isResourceIdDeclaration(str: String?): Boolean = str?.startsWith(idDeclarationPrefix) ?: false
public fun isResourceIdUsage(str: String?): Boolean = str?.startsWith(idUsagePrefix) ?: false
public fun isResourceDeclarationOrUsage(id: String?): Boolean = isResourceIdDeclaration(id) || isResourceIdUsage(id)
abstract fun readManifest(): AndroidManifest
inner class NoUIXMLsFound : Exception("No android UI xmls found in $searchPath")
inner class WrongIdFormat(id: String?) : Exception("Id \"$id\" has wrong format")
}
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve.android
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiManager
import java.util.ArrayList
import com.intellij.openapi.vfs.VirtualFile
abstract class AndroidResourceManagerBase(project: Project, searchPath: String?) : AndroidResourceManager(project, searchPath) {
override fun getLayoutXmlFiles(): Collection<PsiFile> {
val fileManager = VirtualFileManager.getInstance()
val watchDir = fileManager.findFileByUrl("file://" + searchPath)
val psiManager = PsiManager.getInstance(project)
val files = watchDir?.getChildren()?.toArrayList()?.map { psiManager.findFile(it) }?.mapNotNull { it } ?: ArrayList(0)
return files.sortBy({it.getName()})
}
protected fun vritualFileToPsi(vf: VirtualFile): PsiFile? {
val psiManager = PsiManager.getInstance(project)
return psiManager.findFile(vf)
}
override fun idToXmlAttribute(id: String): PsiElement? {
return null
}
}
@@ -0,0 +1,167 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve.android
import java.util.ArrayList
import java.util.HashMap
import java.util.concurrent.ConcurrentLinkedQueue
import java.io.File
import javax.xml.parsers.SAXParserFactory
import java.io.File
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
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.VirtualFileAdapter
import com.intellij.openapi.vfs.VirtualFileEvent
import com.intellij.openapi.project.Project
import java.util.concurrent.ConcurrentLinkedQueue
import com.intellij.openapi.util.Key
import com.intellij.testFramework.LightVirtualFile
import com.intellij.psi.PsiManager
import java.io.FileInputStream
import org.xml.sax.helpers.DefaultHandler
import org.xml.sax.Attributes
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.psi.impl.PsiModificationTrackerImpl
import java.util.Queue
import com.intellij.psi.PsiFile
import com.intellij.openapi.diagnostic.Logger
import org.jetbrains.jet.lang.resolve.android.AndroidConst.*
public abstract class AndroidUIXmlProcessor(protected val project: Project) {
public class NoAndroidManifestFound : Exception("No android manifest file found in project root")
private enum class CacheAction { HIT; MISS }
private val androidImports = arrayListOf("android.app.Activity",
"android.view.View",
"android.widget.*")
protected abstract val searchPath: String?
protected abstract val androidAppPackage: String
private val fileCache = HashMap<PsiFile, String>()
var lastCachedPsi: JetFile? = null
private set
protected val fileModificationTime: HashMap<PsiFile, Long> = HashMap()
protected val filesToProcess: Queue<PsiFile> = ConcurrentLinkedQueue()
public abstract val resourceManager: AndroidResourceManager
protected val LOG: Logger = Logger.getInstance(this.javaClass)
public fun parseToString(): String? {
val cacheState = doParse()
if (cacheState == null) return null
return renderString()
}
public fun parseToPsi(project: Project): JetFile? {
val cacheState = doParse()
if (cacheState == null) return null
return if (cacheState == CacheAction.MISS || lastCachedPsi == null) {
try {
val vf = LightVirtualFile(SYNTHETIC_FILENAME, renderString())
val psiFile = PsiManager.getInstance(project).findFile(vf) as JetFile
psiFile.putUserData(ANDROID_USER_PACKAGE, androidAppPackage)
lastCachedPsi = psiFile
psiFile
}
catch (e: Exception) {
invalidateCaches()
null
}
}
else lastCachedPsi
}
private fun writeImports(kw: KotlinStringWriter): KotlinWriter {
kw.writePackage(androidAppPackage)
for (elem in androidImports)
kw.writeImport(elem)
kw.writeEmptyLine()
return kw
}
private fun parseSingleFileWithCache(file: PsiFile): Pair<String, CacheAction> {
val lastRecorded = fileModificationTime[file] ?: -1
if (file.getModificationStamp() > lastRecorded)
return Pair(parseSingleFile(file), CacheAction.MISS)
else
return Pair(fileCache[file]!!, CacheAction.HIT)
}
private fun parseSingleFile(file: PsiFile): String {
val res = parseSingleFileImpl(file)
fileModificationTime[file] = file.getModificationStamp()
fileCache[file] = res
return res
}
protected abstract fun parseSingleFileImpl(file: PsiFile): String
private fun doParse(): CacheAction? {
if (searchPath == null || searchPath == "") return null
populateQueue()
var overallCacheMiss = false
var file = filesToProcess.poll()
while (file != null) {
val res = parseSingleFileWithCache(file!!)
overallCacheMiss = overallCacheMiss or (res.second == CacheAction.MISS)
file = filesToProcess.poll()
}
return if (overallCacheMiss) CacheAction.MISS else CacheAction.HIT
}
private fun renderString(): String {
val buffer = writeImports(KotlinStringWriter()).output()
for (buf in fileCache.entrySet().sortBy({it.key.getName()}))
buffer.append(buf.value)
return buffer.toString()
}
private fun invalidateCaches() {
fileCache.clear()
fileModificationTime.clear()
lastCachedPsi = null
}
protected fun populateQueue() {
filesToProcess.addAll(resourceManager.getLayoutXmlFiles())
}
protected fun produceKotlinProperties(kw: KotlinStringWriter, ids: Collection<AndroidWidget>): StringBuffer {
for (id in ids) {
val body = arrayListOf("return findViewById(0) as ${id.className}")
kw.writeImmutableExtensionProperty(receiver = "Activity",
name = id.id,
retType = id.className,
getterBody = body)
}
return kw.output()
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve.android
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiElement
import com.intellij.openapi.project.Project
import org.jetbrains.jet.lang.psi.JetFile
import com.intellij.openapi.components.ServiceManager
import java.util.ArrayList
import com.intellij.psi.PsiField
import com.intellij.psi.PsiClass
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
}
}
class AndroidWidget(val id: String, val className: String) : AndroidResource
class AndroidManifest(val _package: String) : AndroidResource
fun isAndroidSyntheticFile(f: PsiFile?): Boolean {
return f?.getUserData(org.jetbrains.jet.lang.resolve.android.AndroidConst.ANDROID_USER_PACKAGE) != null
}
public fun isAndroidSyntheticElement(element: PsiElement?): Boolean {
return isAndroidSyntheticFile(element?.getContainingFile())
}
public fun searchAndAddAndroidDeclarations(project: Project, originalFiles: Collection<JetFile>): Collection<JetFile> {
val parser = ServiceManager.getService<AndroidUIXmlProcessor>(project, javaClass<AndroidUIXmlProcessor>())
val file = parser?.parseToPsi(project)
val files = ArrayList(originalFiles)
if (file != null) files.add(file)
return files
}
public fun isRClassField(element: PsiElement): Boolean {
return if (element is PsiField) {
val outerClass = element.getParent()?.getParent()
if (outerClass !is PsiClass) return false
val processor = ServiceManager.getService<AndroidUIXmlProcessor>(element.getProject(), javaClass<AndroidUIXmlProcessor>())
val packageName = processor?.resourceManager?.readManifest()?._package
if ((outerClass as PsiClass).getQualifiedName()?.startsWith(packageName ?: "") ?: false)
true else false
}
else false
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve.android
import org.xml.sax.helpers.DefaultHandler
import org.xml.sax.Attributes
import java.util.HashMap
class AndroidXmlHandler(private val resourceManager: AndroidResourceManager, private val elementCallback: (String, 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 attributesMap = attributes.toMap()
val idAttr = attributesMap[resourceManager.idAttributeNoNamespace]
val classNameAttr = attributesMap[resourceManager.classAttributeNoNamespace] ?: localName
if (resourceManager.isResourceDeclarationOrUsage(idAttr)) elementCallback(resourceManager.idToName(idAttr), classNameAttr)
}
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,57 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve.android
import com.intellij.openapi.project.Project
import java.io.File
import java.io.FileInputStream
import org.xml.sax.helpers.DefaultHandler
import org.xml.sax.Attributes
import javax.xml.parsers.SAXParser
import javax.xml.parsers.SAXParserFactory
public class CliAndroidResourceManager(project: Project, searchPath: String?, private val manifestPath: String?) : org.jetbrains.jet.lang.resolve.android.AndroidResourceManagerBase(project, searchPath) {
val saxParser: SAXParser = initSAX()
protected fun initSAX(): SAXParser {
val saxFactory = SAXParserFactory.newInstance()
saxFactory?.setNamespaceAware(true)
return saxFactory!!.newSAXParser()
}
override fun readManifest(): org.jetbrains.jet.lang.resolve.android.AndroidManifest {
try {
val manifestXml = File(manifestPath!!)
var _package: String = ""
try {
saxParser.parse(FileInputStream(manifestXml), object : DefaultHandler() {
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
if (localName == "manifest")
_package = attributes.toMap()["package"] ?: ""
}
})
}
catch (e: Exception) {
throw e
}
return org.jetbrains.jet.lang.resolve.android.AndroidManifest(_package)
}
catch (e: Exception) {
throw org.jetbrains.jet.lang.resolve.android.AndroidUIXmlProcessor.NoAndroidManifestFound()
}
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve.android
import java.util.ArrayList
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
public class CliAndroidUIXmlProcessor(project: Project, override val searchPath: String?, val manifestPath: String?) : org.jetbrains.jet.lang.resolve.android.AndroidUIXmlProcessor(project) {
override var androidAppPackage: String = ""
get() = resourceManager.readManifest()._package
override val resourceManager = org.jetbrains.jet.lang.resolve.android.CliAndroidResourceManager(project, searchPath, manifestPath)
override fun parseSingleFileImpl(file: PsiFile): String {
val ids: MutableCollection<AndroidWidget> = ArrayList()
val handler = org.jetbrains.jet.lang.resolve.android.AndroidXmlHandler(resourceManager, { id, wClass -> ids.add(AndroidWidget(id, wClass)) })
try {
resourceManager.saxParser.parse(file.getVirtualFile()?.getInputStream()!!, handler)
return produceKotlinProperties(KotlinStringWriter(), ids).toString()
}
catch (e: Throwable) {
LOG.error(e)
return ""
}
}
}
@@ -0,0 +1,92 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve.android
import java.util.ArrayList
open class Context(val buffer: StringBuffer = StringBuffer(), private var indentDepth: Int = 0) {
open class InvalidIndent(num: Int) : RuntimeException("Indentation level < 0: $num")
val indentUnit = " "
protected var currentIndent: String = indentUnit.repeat(indentDepth)
private 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,99 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve.android
trait KotlinWriter {
fun output(): StringBuffer
}
class KotlinStringWriter : KotlinWriter {
private val ctx = org.jetbrains.jet.lang.resolve.android.Context()
private val imports = ctx.fork()
private val body = ctx.fork()
fun writeFunction(name: String,
args: Collection<String>?,
retType: String,
stmts: Collection<String>) {
val returnTerm = if (retType == "" || retType == "Unit") "" else ": $retType"
val argStr = if (args != null) args.join(", ") else ""
body.writeln("fun $name($argStr)$returnTerm {")
body.incIndent()
for (stmt in stmts)
body.writeln(stmt)
body.decIndent()
body.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>) {
body.writeln("val $name: $retType")
body.incIndent()
body.write("get() ")
if (getterBody.size > 1) {
body.writeNoIndent("{\n")
body.incIndent()
for (stmt in getterBody) {
body.writeln(stmt)
}
body.decIndent()
body.writeln("}")
}
else {
body.writeNoIndent("=")
body.writeNoIndent(getterBody.join("").replace("return", ""))
body.newLine()
}
body.decIndent()
body.newLine()
}
fun writeImmutableExtensionProperty(receiver: String,
name: String,
retType: String,
getterBody: Collection<String>) {
writeImmutableProperty("$receiver.$name", retType, getterBody)
}
fun writeImport(what: String) {
imports.writeln("import $what")
}
fun writePackage(_package: String) {
ctx.writeln("package $_package\n")
}
fun writeEmptyLine() {
body.newLine()
}
override fun output(): StringBuffer {
ctx.absorbChildren()
return ctx.buffer
}
}