Refactor Android plugin
This commit is contained in:
@@ -83,7 +83,7 @@ public class AndroidCommandLineProcessor : CommandLineProcessor {
|
|||||||
public class CliAndroidDeclarationsProvider(private val project: Project) : ExternalDeclarationsProvider {
|
public class CliAndroidDeclarationsProvider(private val project: Project) : ExternalDeclarationsProvider {
|
||||||
override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> {
|
override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> {
|
||||||
val parser = ServiceManager.getService<AndroidUIXmlProcessor>(project, javaClass<AndroidUIXmlProcessor>())
|
val parser = ServiceManager.getService<AndroidUIXmlProcessor>(project, javaClass<AndroidUIXmlProcessor>())
|
||||||
return emptyOrSingletonList(parser?.parseToPsi(project))
|
return emptyOrSingletonList(parser.parseToPsi())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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 object AndroidConst {
|
||||||
|
val ANDROID_USER_PACKAGE: Key<String> = Key.create<String>("ANDROID_USER_PACKAGE")
|
||||||
|
val SYNTHETIC_FILENAME: String = "ANDROIDXML.kt"
|
||||||
|
|
||||||
|
val ANDROID_NAMESPACE: String = "android"
|
||||||
|
val ID_ATTRIBUTE_NO_NAMESPACE: String = "id"
|
||||||
|
val ID_ATTRIBUTE: String = "$ANDROID_NAMESPACE:$ID_ATTRIBUTE_NO_NAMESPACE"
|
||||||
|
val CLASS_ATTRIBUTE_NO_NAMESPACE: String = "class"
|
||||||
|
|
||||||
|
val ID_DECLARATION_PREFIX = "@+id/"
|
||||||
|
val ID_USAGE_PREFIX = "@id/"
|
||||||
|
}
|
||||||
|
|
||||||
|
class WrongIdFormat(id: String) : Exception("Id \"$id\" has wrong format")
|
||||||
|
|
||||||
|
public fun nameToIdDeclaration(name: String): String = AndroidConst.ID_DECLARATION_PREFIX + name
|
||||||
|
|
||||||
|
public fun idToName(id: String): String {
|
||||||
|
return if (isResourceIdDeclaration(id)) id.replace(AndroidConst.ID_DECLARATION_PREFIX, "")
|
||||||
|
else if (isResourceIdUsage(id)) id.replace(AndroidConst.ID_USAGE_PREFIX, "")
|
||||||
|
else throw WrongIdFormat(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun isResourceIdDeclaration(str: String?): Boolean = str?.startsWith(AndroidConst.ID_DECLARATION_PREFIX) ?: false
|
||||||
|
|
||||||
|
public fun isResourceIdUsage(str: String?): Boolean = str?.startsWith(AndroidConst.ID_USAGE_PREFIX) ?: false
|
||||||
|
|
||||||
|
public fun isResourceDeclarationOrUsage(id: String?): Boolean = isResourceIdDeclaration(id) || isResourceIdUsage(id)
|
||||||
+38
-23
@@ -19,32 +19,47 @@ package org.jetbrains.jet.lang.resolve.android
|
|||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.vfs.VirtualFileManager
|
||||||
|
import com.intellij.psi.PsiManager
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
|
||||||
public abstract class AndroidResourceManager(protected val project: Project, protected val searchPath: String?) {
|
public abstract class AndroidResourceManager(val project: Project) {
|
||||||
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>
|
public abstract val androidModuleInfo: AndroidModuleInfo?
|
||||||
public abstract fun idToXmlAttribute(id: String): PsiElement?
|
|
||||||
public fun nameToIdDeclaration(name: String): String = idDeclarationPrefix + name
|
public open fun idToXmlAttribute(id: String): PsiElement? = null
|
||||||
public fun nameToIdUsage(name: String): String = idUsagePrefix + name
|
|
||||||
public fun idToName(id: String?): String {
|
open fun getLayoutXmlFiles(): List<PsiFile> {
|
||||||
return if (isResourceIdDeclaration(id)) id!!.replace(idDeclarationPrefix, "")
|
val info = androidModuleInfo
|
||||||
else if (isResourceIdUsage(id)) id!!.replace(idUsagePrefix, "")
|
if (info == null) return listOf()
|
||||||
else throw WrongIdFormat(id)
|
|
||||||
|
val psiManager = PsiManager.getInstance(project)
|
||||||
|
val fileManager = VirtualFileManager.getInstance()
|
||||||
|
|
||||||
|
fun VirtualFile.getAllChildren(): List<VirtualFile> {
|
||||||
|
val allChildren = arrayListOf<VirtualFile>()
|
||||||
|
val currentChildren = getChildren() ?: array()
|
||||||
|
for (child in currentChildren) {
|
||||||
|
if (child.isDirectory()) {
|
||||||
|
allChildren.addAll(child.getAllChildren())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
allChildren.add(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
val resDirectory = fileManager.findFileByUrl("file://" + info.mainResDirectory)
|
||||||
|
val allChildren = resDirectory?.getAllChildren() ?: listOf()
|
||||||
|
|
||||||
|
return allChildren
|
||||||
|
.filter { it.getParent().getName().startsWith("layout") && it.getName().toLowerCase().endsWith(".xml") }
|
||||||
|
.map { psiManager.findFile(it) }
|
||||||
|
.filterNotNull()
|
||||||
|
.sortBy { it.getName() }
|
||||||
}
|
}
|
||||||
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 NoLayoutXmlFound : Exception("No android UI xmls found in " + androidModuleInfo?.mainResDirectory)
|
||||||
|
|
||||||
inner class NoUIXMLsFound : Exception("No android UI xmls found in $searchPath")
|
|
||||||
inner class WrongIdFormat(id: String?) : Exception("Id \"$id\" has wrong format")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 } ?: listOf()
|
|
||||||
return files.sortBy({it.getName()})
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun virtualFileToPsi(vf: VirtualFile): PsiFile? {
|
|
||||||
val psiManager = PsiManager.getInstance(project)
|
|
||||||
return psiManager.findFile(vf)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun idToXmlAttribute(id: String): PsiElement? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+60
-95
@@ -46,124 +46,89 @@ import com.intellij.psi.impl.PsiModificationTrackerImpl
|
|||||||
import java.util.Queue
|
import java.util.Queue
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import org.jetbrains.jet.lang.resolve.android.AndroidConst.*
|
|
||||||
import org.jetbrains.jet.analyzer.ModuleInfo
|
|
||||||
import com.intellij.openapi.module.Module
|
import com.intellij.openapi.module.Module
|
||||||
|
import com.intellij.psi.util.CachedValue
|
||||||
|
import com.intellij.psi.util.CachedValueProvider
|
||||||
|
import com.intellij.psi.util.CachedValuesManager
|
||||||
|
import com.intellij.psi.util.CachedValueProvider.Result
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import com.intellij.openapi.roots.ProjectRootModificationTracker
|
||||||
|
import com.intellij.openapi.util.ModificationTracker
|
||||||
|
|
||||||
public abstract class AndroidUIXmlProcessor(protected val project: Project) {
|
public abstract class AndroidUIXmlProcessor(protected val project: Project) {
|
||||||
|
|
||||||
public class NoAndroidManifestFound : Exception("No android manifest file found in project root")
|
public class NoAndroidManifestFound : Exception("No android manifest file found in project root")
|
||||||
|
|
||||||
private enum class CacheAction { HIT; MISS }
|
private val androidImports = listOf(
|
||||||
|
"android.app.Activity",
|
||||||
|
"android.view.View",
|
||||||
|
"android.widget.*")
|
||||||
|
|
||||||
private val androidImports = arrayListOf("android.app.Activity",
|
//TODO
|
||||||
"android.view.View",
|
private val cachedSources = cachedValue {
|
||||||
"android.widget.*")
|
Result.create(parse(), ProjectRootModificationTracker.getInstance(project))
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract val searchPath: String?
|
private val cachedJetFiles = cachedValue {
|
||||||
protected abstract val androidAppPackage: String
|
val psiManager = PsiManager.getInstance(project)
|
||||||
|
val applicationPackage = resourceManager.androidModuleInfo?.applicationPackage
|
||||||
|
|
||||||
private val fileCache = HashMap<PsiFile, String>()
|
val jetFiles = cachedSources.getValue().mapIndexed { (index, text) ->
|
||||||
var lastCachedPsi: JetFile? = null
|
val virtualFile = LightVirtualFile(AndroidConst.SYNTHETIC_FILENAME + index + ".kt", text)
|
||||||
private set
|
val jetFile = psiManager.findFile(virtualFile) as JetFile
|
||||||
protected val fileModificationTime: HashMap<PsiFile, Long> = HashMap()
|
if (applicationPackage != null) {
|
||||||
|
jetFile.putUserData(AndroidConst.ANDROID_USER_PACKAGE, applicationPackage)
|
||||||
|
}
|
||||||
|
jetFile
|
||||||
|
}
|
||||||
|
|
||||||
protected val filesToProcess: Queue<PsiFile> = ConcurrentLinkedQueue()
|
Result.create(jetFiles, cachedSources)
|
||||||
|
}
|
||||||
|
|
||||||
public abstract val resourceManager: AndroidResourceManager
|
public abstract val resourceManager: AndroidResourceManager
|
||||||
|
|
||||||
protected val LOG: Logger = Logger.getInstance(this.javaClass)
|
protected val LOG: Logger = Logger.getInstance(javaClass)
|
||||||
|
|
||||||
public fun parseToString(): String? {
|
public fun parse(): List<String> {
|
||||||
val cacheState = doParse()
|
return resourceManager.getLayoutXmlFiles().map { file ->
|
||||||
if (cacheState == null) return null
|
val widgets = parseSingleFile(file)
|
||||||
return renderString()
|
if (widgets.isNotEmpty()) {
|
||||||
|
val layoutPackage = file.genSyntheticPackageName()
|
||||||
|
val stringWriter = KotlinStringWriter()
|
||||||
|
|
||||||
|
stringWriter.writePackage(layoutPackage)
|
||||||
|
stringWriter.writeAndroidImports()
|
||||||
|
widgets.forEach { stringWriter.writeSyntheticActivityProperty(it) }
|
||||||
|
|
||||||
|
val contents = stringWriter.toStringBuffer().toString()
|
||||||
|
contents
|
||||||
|
} else null
|
||||||
|
}.filterNotNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun parseToPsi(): List<JetFile>? = cachedJetFiles.getValue()
|
||||||
|
|
||||||
public fun parseToPsi(project: Project): JetFile? {
|
protected abstract fun parseSingleFile(file: PsiFile): Collection<AndroidWidget>
|
||||||
val cacheState = doParse()
|
|
||||||
if (cacheState == null) return null
|
private fun KotlinStringWriter.writeAndroidImports() {
|
||||||
return if (cacheState == CacheAction.MISS || lastCachedPsi == null) {
|
androidImports.forEach { writeImport(it) }
|
||||||
try {
|
writeEmptyLine()
|
||||||
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 {
|
private fun PsiFile.genSyntheticPackageName(): String {
|
||||||
kw.writePackage(androidAppPackage)
|
return AndroidConst.SYNTHETIC_PACKAGE + getName().substringBefore('.')
|
||||||
for (elem in androidImports)
|
|
||||||
kw.writeImport(elem)
|
|
||||||
kw.writeEmptyLine()
|
|
||||||
return kw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseSingleFileWithCache(file: PsiFile): Pair<String, CacheAction> {
|
private fun KotlinStringWriter.writeSyntheticActivityProperty(widget: AndroidWidget) {
|
||||||
val lastRecorded = fileModificationTime[file] ?: -1
|
val body = arrayListOf("return findViewById(0) as ${widget.className}")
|
||||||
if (file.getModificationStamp() > lastRecorded)
|
writeImmutableExtensionProperty(receiver = "Activity",
|
||||||
return Pair(parseSingleFile(file), CacheAction.MISS)
|
name = widget.id,
|
||||||
else
|
retType = widget.className,
|
||||||
return Pair(fileCache[file]!!, CacheAction.HIT)
|
getterBody = body)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseSingleFile(file: PsiFile): String {
|
private fun <T> cachedValue(result: () -> CachedValueProvider.Result<T>): CachedValue<T> {
|
||||||
val res = parseSingleFileImpl(file)
|
return CachedValuesManager.getManager(project).createCachedValue(result, false)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-31
@@ -26,25 +26,7 @@ import com.intellij.psi.PsiClass
|
|||||||
import org.jetbrains.jet.utils.emptyOrSingletonList
|
import org.jetbrains.jet.utils.emptyOrSingletonList
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.util.Computable
|
import com.intellij.openapi.util.Computable
|
||||||
|
import com.intellij.openapi.module.ModuleServiceManager
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AndroidWidget(val id: String, val className: String) : AndroidResource
|
|
||||||
|
|
||||||
public class AndroidManifest(public val _package: String) : AndroidResource
|
|
||||||
|
|
||||||
fun isAndroidSyntheticFile(f: PsiFile?): Boolean {
|
fun isAndroidSyntheticFile(f: PsiFile?): Boolean {
|
||||||
return f?.getUserData(org.jetbrains.jet.lang.resolve.android.AndroidConst.ANDROID_USER_PACKAGE) != null
|
return f?.getUserData(org.jetbrains.jet.lang.resolve.android.AndroidConst.ANDROID_USER_PACKAGE) != null
|
||||||
@@ -55,15 +37,3 @@ public fun isAndroidSyntheticElement(element: PsiElement?): Boolean {
|
|||||||
element?.getContainingFile()
|
element?.getContainingFile()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
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 : PsiClass).getQualifiedName()?.startsWith(packageName ?: "") ?: false)
|
|
||||||
true else false
|
|
||||||
}
|
|
||||||
else false
|
|
||||||
}
|
|
||||||
|
|||||||
+7
-4
@@ -20,7 +20,10 @@ import org.xml.sax.helpers.DefaultHandler
|
|||||||
import org.xml.sax.Attributes
|
import org.xml.sax.Attributes
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
|
|
||||||
class AndroidXmlHandler(private val resourceManager: AndroidResourceManager, private val elementCallback: (String, String) -> Unit) : DefaultHandler() {
|
class AndroidXmlHandler(
|
||||||
|
private val resourceManager: AndroidResourceManager,
|
||||||
|
private val elementCallback: (String, String) -> Unit
|
||||||
|
) : DefaultHandler() {
|
||||||
|
|
||||||
override fun startDocument() {
|
override fun startDocument() {
|
||||||
super<DefaultHandler>.startDocument()
|
super<DefaultHandler>.startDocument()
|
||||||
@@ -32,9 +35,9 @@ class AndroidXmlHandler(private val resourceManager: AndroidResourceManager, pri
|
|||||||
|
|
||||||
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
|
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
|
||||||
val attributesMap = attributes.toMap()
|
val attributesMap = attributes.toMap()
|
||||||
val idAttr = attributesMap[resourceManager.idAttributeNoNamespace]
|
val idAttr = attributesMap[AndroidConst.ID_ATTRIBUTE_NO_NAMESPACE]
|
||||||
val classNameAttr = attributesMap[resourceManager.classAttributeNoNamespace] ?: localName
|
val classNameAttr = attributesMap[AndroidConst.CLASS_ATTRIBUTE_NO_NAMESPACE] ?: localName
|
||||||
if (resourceManager.isResourceDeclarationOrUsage(idAttr)) elementCallback(resourceManager.idToName(idAttr), classNameAttr)
|
if (isResourceDeclarationOrUsage(idAttr)) elementCallback(idToName(idAttr), classNameAttr)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun endElement(uri: String?, localName: String, qName: String) {
|
override fun endElement(uri: String?, localName: String, qName: String) {
|
||||||
|
|||||||
+20
-9
@@ -23,35 +23,46 @@ import org.xml.sax.helpers.DefaultHandler
|
|||||||
import org.xml.sax.Attributes
|
import org.xml.sax.Attributes
|
||||||
import javax.xml.parsers.SAXParser
|
import javax.xml.parsers.SAXParser
|
||||||
import javax.xml.parsers.SAXParserFactory
|
import javax.xml.parsers.SAXParserFactory
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
public class CliAndroidResourceManager(project: Project, searchPath: String?, private val manifestPath: String?) : org.jetbrains.jet.lang.resolve.android.AndroidResourceManagerBase(project, searchPath) {
|
public class CliAndroidResourceManager(
|
||||||
|
project: Project,
|
||||||
|
private val manifestPath: String,
|
||||||
|
private val mainResDirectory: String
|
||||||
|
) : AndroidResourceManager(project) {
|
||||||
|
|
||||||
|
override val androidModuleInfo by Delegates.lazy {
|
||||||
|
AndroidModuleInfo(getApplicationPackage(manifestPath), mainResDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
val saxParser: SAXParser = initSAX()
|
val saxParser: SAXParser = initSAX()
|
||||||
|
|
||||||
protected fun initSAX(): SAXParser {
|
protected fun initSAX(): SAXParser {
|
||||||
val saxFactory = SAXParserFactory.newInstance()
|
val saxFactory = SAXParserFactory.newInstance()
|
||||||
saxFactory?.setNamespaceAware(true)
|
saxFactory.setNamespaceAware(true)
|
||||||
return saxFactory!!.newSAXParser()
|
return saxFactory.newSAXParser()
|
||||||
}
|
}
|
||||||
override fun readManifest(): org.jetbrains.jet.lang.resolve.android.AndroidManifest {
|
|
||||||
|
private fun getApplicationPackage(manifestPath: String): String {
|
||||||
try {
|
try {
|
||||||
val manifestXml = File(manifestPath!!)
|
val manifestXml = File(manifestPath)
|
||||||
var _package: String = ""
|
var applicationPackage: String = ""
|
||||||
try {
|
try {
|
||||||
saxParser.parse(FileInputStream(manifestXml), object : DefaultHandler() {
|
saxParser.parse(FileInputStream(manifestXml), object : DefaultHandler() {
|
||||||
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
|
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
|
||||||
if (localName == "manifest")
|
if (localName == "manifest")
|
||||||
_package = attributes.toMap()["package"] ?: ""
|
applicationPackage = attributes.toMap()["package"] ?: ""
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
return org.jetbrains.jet.lang.resolve.android.AndroidManifest(_package)
|
return applicationPackage
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
throw org.jetbrains.jet.lang.resolve.android.AndroidUIXmlProcessor.NoAndroidManifestFound()
|
throw AndroidUIXmlProcessor.NoAndroidManifestFound()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-7
@@ -20,17 +20,21 @@ import java.util.ArrayList
|
|||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
public class CliAndroidUIXmlProcessor(project: Project, override val searchPath: String?, val manifestPath: String?) : org.jetbrains.jet.lang.resolve.android.AndroidUIXmlProcessor(project) {
|
public class CliAndroidUIXmlProcessor(
|
||||||
|
project: Project,
|
||||||
|
private val manifestPath: String,
|
||||||
|
private val mainResDirectory: String
|
||||||
|
) : AndroidUIXmlProcessor(project) {
|
||||||
|
|
||||||
override var androidAppPackage: String = ""
|
override val resourceManager: CliAndroidResourceManager by Delegates.lazy {
|
||||||
get() = resourceManager.readManifest()._package
|
CliAndroidResourceManager(project, manifestPath, mainResDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
override val resourceManager = org.jetbrains.jet.lang.resolve.android.CliAndroidResourceManager(project, searchPath, manifestPath)
|
override fun parseSingleFile(file: PsiFile): String {
|
||||||
|
|
||||||
override fun parseSingleFileImpl(file: PsiFile): String {
|
|
||||||
val ids: MutableCollection<AndroidWidget> = ArrayList()
|
val ids: MutableCollection<AndroidWidget> = ArrayList()
|
||||||
val handler = org.jetbrains.jet.lang.resolve.android.AndroidXmlHandler(resourceManager, { id, wClass -> ids.add(AndroidWidget(id, wClass)) })
|
val handler = AndroidXmlHandler(resourceManager, { id, clazz -> ids.add(AndroidWidget(id, clazz)) })
|
||||||
try {
|
try {
|
||||||
val inputStream = ByteArrayInputStream(file.getVirtualFile().contentsToByteArray())
|
val inputStream = ByteArrayInputStream(file.getVirtualFile().contentsToByteArray())
|
||||||
resourceManager.saxParser.parse(inputStream, handler)
|
resourceManager.saxParser.parse(inputStream, handler)
|
||||||
|
|||||||
+2
-2
@@ -29,12 +29,12 @@ class KotlinStringWriter : KotlinWriter {
|
|||||||
fun writeFunction(name: String,
|
fun writeFunction(name: String,
|
||||||
args: Collection<String>?,
|
args: Collection<String>?,
|
||||||
retType: String,
|
retType: String,
|
||||||
stmts: Collection<String>) {
|
statements: Collection<String>) {
|
||||||
val returnTerm = if (retType == "" || retType == "Unit") "" else ": $retType"
|
val returnTerm = if (retType == "" || retType == "Unit") "" else ": $retType"
|
||||||
val argStr = if (args != null) args.join(", ") else ""
|
val argStr = if (args != null) args.join(", ") else ""
|
||||||
body.writeln("fun $name($argStr)$returnTerm {")
|
body.writeln("fun $name($argStr)$returnTerm {")
|
||||||
body.incIndent()
|
body.incIndent()
|
||||||
for (stmt in stmts)
|
for (stmt in statements)
|
||||||
body.writeln(stmt)
|
body.writeln(stmt)
|
||||||
body.decIndent()
|
body.decIndent()
|
||||||
body.writeln("}")
|
body.writeln("}")
|
||||||
|
|||||||
+5
-6
@@ -14,11 +14,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.jet.lang.resolve.android;
|
package org.jetbrains.jet.lang.resolve.android
|
||||||
|
|
||||||
import com.intellij.openapi.util.Key;
|
public data class AndroidModuleInfo(val applicationPackage: String, val mainResDirectory: String?)
|
||||||
|
|
||||||
public class AndroidConst {
|
trait AndroidResource
|
||||||
public static final Key<String> ANDROID_USER_PACKAGE = Key.create("ANDROID_USER_PACKAGE");
|
|
||||||
public static final String SYNTHETIC_FILENAME = "ANDROIDXML.kt";
|
public class AndroidWidget(val id: String, val className: String) : AndroidResource
|
||||||
}
|
|
||||||
+3
-5
@@ -37,11 +37,11 @@ public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() {
|
|||||||
|
|
||||||
public fun doTest(path: String) {
|
public fun doTest(path: String) {
|
||||||
val jetCoreEnvironment = getEnvironment(path)
|
val jetCoreEnvironment = getEnvironment(path)
|
||||||
val parser = CliAndroidUIXmlProcessor(jetCoreEnvironment.getProject(), path + "/layout", path + "AndroidManifest.xml")
|
val parser = CliAndroidUIXmlProcessor(jetCoreEnvironment.getProject(), path + "AndroidManifest.xml", path + "/layout")
|
||||||
|
|
||||||
val actual = parser.parseToString()
|
val actual = parser.parse()
|
||||||
|
|
||||||
JetTestUtils.assertEqualsToFile(File(path + "/layout.kt"), actual!!)
|
JetTestUtils.assertEqualsToFile(File(path + "/layout.kt"), actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun doNoManifestTest(path: String) {
|
public fun doNoManifestTest(path: String) {
|
||||||
@@ -55,8 +55,6 @@ public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() {
|
|||||||
|
|
||||||
private fun getEnvironment(testPath: String): JetCoreEnvironment {
|
private fun getEnvironment(testPath: String): JetCoreEnvironment {
|
||||||
val configuration = JetTestUtils.compilerConfigurationForTests(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK)
|
val configuration = JetTestUtils.compilerConfigurationForTests(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK)
|
||||||
// configuration.put<String>(JVMConfigurationKeys.ANDROID_RES_PATH, testPath + "/layout")
|
|
||||||
// configuration.put<String>(JVMConfigurationKeys.ANDROID_MANIFEST, testPath + "/AndroidManifest.xml")
|
|
||||||
return JetCoreEnvironment.createForTests(getTestRootDisposable()!!, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
return JetCoreEnvironment.createForTests(getTestRootDisposable()!!, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -36,8 +36,8 @@ private class AndroidTestExternalDeclarationsProvider(
|
|||||||
val manifestPath: String
|
val manifestPath: String
|
||||||
) : ExternalDeclarationsProvider {
|
) : ExternalDeclarationsProvider {
|
||||||
override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> {
|
override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> {
|
||||||
val parser = CliAndroidUIXmlProcessor(project, resPath, manifestPath)
|
val parser = CliAndroidUIXmlProcessor(project, manifestPath, resPath)
|
||||||
return emptyOrSingletonList(parser.parseToPsi(project))
|
return emptyOrSingletonList(parser.parseToPsi())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -28,13 +28,13 @@ import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
|
|||||||
import org.jetbrains.jet.lang.psi.JetProperty
|
import org.jetbrains.jet.lang.psi.JetProperty
|
||||||
import com.intellij.psi.impl.light.LightElement
|
import com.intellij.psi.impl.light.LightElement
|
||||||
import com.intellij.psi.xml.XmlAttribute
|
import com.intellij.psi.xml.XmlAttribute
|
||||||
import org.jetbrains.jet.lang.resolve.android.isRClassField
|
|
||||||
import com.intellij.psi.PsiField
|
import com.intellij.psi.PsiField
|
||||||
import com.intellij.openapi.module.ModuleServiceManager
|
import com.intellij.openapi.module.ModuleServiceManager
|
||||||
import org.jetbrains.jet.plugin.caches.resolve.getModuleInfo
|
import org.jetbrains.jet.plugin.caches.resolve.getModuleInfo
|
||||||
import org.jetbrains.jet.plugin.caches.resolve.ModuleSourceInfo
|
import org.jetbrains.jet.plugin.caches.resolve.ModuleSourceInfo
|
||||||
|
|
||||||
public class AndroidGotoDeclarationHandler : GotoDeclarationHandler {
|
public class AndroidGotoDeclarationHandler : GotoDeclarationHandler {
|
||||||
|
|
||||||
override fun getGotoDeclarationTargets(sourceElement: PsiElement?, offset: Int, editor: Editor?): Array<PsiElement>? {
|
override fun getGotoDeclarationTargets(sourceElement: PsiElement?, offset: Int, editor: Editor?): Array<PsiElement>? {
|
||||||
if (sourceElement is LeafPsiElement && sourceElement.getParent() is JetSimpleNameExpression) {
|
if (sourceElement is LeafPsiElement && sourceElement.getParent() is JetSimpleNameExpression) {
|
||||||
val resolved = JetSimpleNameReference(sourceElement.getParent() as JetSimpleNameExpression).resolve()
|
val resolved = JetSimpleNameReference(sourceElement.getParent() as JetSimpleNameExpression).resolve()
|
||||||
@@ -60,4 +60,5 @@ public class AndroidGotoDeclarationHandler : GotoDeclarationHandler {
|
|||||||
override fun getActionText(context: DataContext?): String? {
|
override fun getActionText(context: DataContext?): String? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-18
@@ -29,22 +29,38 @@ import org.jetbrains.android.dom.wrappers.LazyValueResourceElementWrapper
|
|||||||
import org.jetbrains.android.util.AndroidResourceUtil
|
import org.jetbrains.android.util.AndroidResourceUtil
|
||||||
import com.intellij.psi.xml.XmlAttribute
|
import com.intellij.psi.xml.XmlAttribute
|
||||||
import com.intellij.psi.impl.light.LightElement
|
import com.intellij.psi.impl.light.LightElement
|
||||||
import org.jetbrains.jet.lang.resolve.android.isRClassField
|
|
||||||
import com.intellij.openapi.module.ModuleServiceManager
|
import com.intellij.openapi.module.ModuleServiceManager
|
||||||
import org.jetbrains.jet.plugin.caches.resolve.getModuleInfo
|
import org.jetbrains.jet.plugin.caches.resolve.getModuleInfo
|
||||||
import org.jetbrains.jet.plugin.caches.resolve.ModuleSourceInfo
|
import org.jetbrains.jet.plugin.caches.resolve.ModuleSourceInfo
|
||||||
import com.intellij.openapi.module.Module
|
import com.intellij.openapi.module.Module
|
||||||
import org.jetbrains.jet.lang.psi.JetFile
|
import org.jetbrains.jet.lang.psi.JetFile
|
||||||
import org.jetbrains.jet.lang.psi.moduleInfo
|
import org.jetbrains.jet.lang.psi.moduleInfo
|
||||||
|
import org.jetbrains.jet.lang.resolve.android.nameToIdDeclaration
|
||||||
|
import org.jetbrains.jet.lang.resolve.android.idToName
|
||||||
|
import com.intellij.psi.PsiField
|
||||||
|
import com.intellij.psi.PsiClass
|
||||||
|
|
||||||
public class AndroidRenameProcessor : RenamePsiElementProcessor() {
|
public class AndroidRenameProcessor : RenamePsiElementProcessor() {
|
||||||
|
|
||||||
override fun canProcessElement(element: PsiElement): Boolean {
|
override fun canProcessElement(element: PsiElement): Boolean {
|
||||||
// either renaming synthetic property, or value in ui xml, or R class field
|
// Either renaming synthetic property, or value in layout xml, or R class field
|
||||||
return (element.namedUnwrappedElement is JetProperty &&
|
return (element.namedUnwrappedElement is JetProperty &&
|
||||||
isAndroidSyntheticElement(element.namedUnwrappedElement)) || element is XmlAttributeValue ||
|
isAndroidSyntheticElement(element.namedUnwrappedElement)) || element is XmlAttributeValue ||
|
||||||
isRClassField(element)
|
isRClassField(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isRClassField(element: PsiElement): Boolean {
|
||||||
|
return if (element is PsiField) {
|
||||||
|
val outerClass = element.getParent()?.getParent()
|
||||||
|
if (outerClass !is PsiClass) return false
|
||||||
|
|
||||||
|
if (outerClass.getQualifiedName()?.startsWith(AndroidConst.SYNTHETIC_PACKAGE) ?: false)
|
||||||
|
true else false
|
||||||
|
}
|
||||||
|
else false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun PsiElement.getModule(): Module? {
|
private fun PsiElement.getModule(): Module? {
|
||||||
val moduleInfo = getModuleInfo()
|
val moduleInfo = getModuleInfo()
|
||||||
if (moduleInfo is ModuleSourceInfo) return moduleInfo.module
|
if (moduleInfo is ModuleSourceInfo) return moduleInfo.module
|
||||||
@@ -58,7 +74,12 @@ public class AndroidRenameProcessor : RenamePsiElementProcessor() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun prepareRenaming(element: PsiElement?, newName: String?, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
|
override fun prepareRenaming(
|
||||||
|
element: PsiElement?,
|
||||||
|
newName: String,
|
||||||
|
allRenames: MutableMap<PsiElement, String>,
|
||||||
|
scope: SearchScope
|
||||||
|
) {
|
||||||
if (element != null && element.namedUnwrappedElement is JetProperty) {
|
if (element != null && element.namedUnwrappedElement is JetProperty) {
|
||||||
renameSyntheticProperty(element.namedUnwrappedElement as JetProperty, newName, allRenames, scope)
|
renameSyntheticProperty(element.namedUnwrappedElement as JetProperty, newName, allRenames, scope)
|
||||||
}
|
}
|
||||||
@@ -70,45 +91,66 @@ public class AndroidRenameProcessor : RenamePsiElementProcessor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renameSyntheticProperty(jetProperty: JetProperty, newName: String?, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
|
private fun renameSyntheticProperty(
|
||||||
|
jetProperty: JetProperty,
|
||||||
|
newName: String,
|
||||||
|
allRenames: MutableMap<PsiElement, String>,
|
||||||
|
scope: SearchScope
|
||||||
|
) {
|
||||||
val oldName = jetProperty.getName()
|
val oldName = jetProperty.getName()
|
||||||
val module = jetProperty.getModule()
|
val module = jetProperty.getModule()
|
||||||
if (module == null) return
|
if (module == null) return
|
||||||
|
|
||||||
val processor = ModuleServiceManager.getService(module, javaClass<AndroidUIXmlProcessor>())
|
val processor = ModuleServiceManager.getService(module, javaClass<AndroidUIXmlProcessor>())
|
||||||
val resourceManager = processor!!.resourceManager
|
val resourceManager = processor.resourceManager
|
||||||
val attr = resourceManager.idToXmlAttribute(oldName) as XmlAttribute
|
val attr = resourceManager.idToXmlAttribute(oldName) as XmlAttribute
|
||||||
allRenames[XmlAttributeValueWrapper(attr.getValueElement()!!)] = resourceManager.nameToIdDeclaration(newName!!)
|
allRenames[XmlAttributeValueWrapper(attr.getValueElement())] = nameToIdDeclaration(newName)
|
||||||
val name = AndroidResourceUtil.getResourceNameByReferenceText(newName)
|
val name = AndroidResourceUtil.getResourceNameByReferenceText(newName)
|
||||||
for (resField in AndroidResourceUtil.findIdFields(attr)) {
|
for (resField in AndroidResourceUtil.findIdFields(attr)) {
|
||||||
allRenames.put(resField, AndroidResourceUtil.getFieldNameByResourceName(name!!))
|
allRenames.put(resField, AndroidResourceUtil.getFieldNameByResourceName(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renameAttributeValue(attribute: XmlAttributeValue, newName: String?, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
|
private fun renameAttributeValue(
|
||||||
val element1 = LazyValueResourceElementWrapper.computeLazyElement(attribute)
|
attribute: XmlAttributeValue,
|
||||||
val module = attribute.getModule()
|
newName: String,
|
||||||
|
allRenames: MutableMap<PsiElement, String>,
|
||||||
|
scope: SearchScope
|
||||||
|
) {
|
||||||
|
val element = LazyValueResourceElementWrapper.computeLazyElement(attribute)
|
||||||
|
val module = attribute.getModule() ?: ModuleUtilCore.findModuleForFile(
|
||||||
|
attribute.getContainingFile().getVirtualFile(), attribute.getProject())
|
||||||
if (module == null) return
|
if (module == null) return
|
||||||
|
|
||||||
val processor = ModuleServiceManager.getService(module, javaClass<AndroidUIXmlProcessor>())
|
val processor = ModuleServiceManager.getService(module, javaClass<AndroidUIXmlProcessor>())
|
||||||
if (element1 == null) return
|
if (element == null) return
|
||||||
val oldPropName = AndroidResourceUtil.getResourceNameByReferenceText(attribute.getValue()!!)
|
val oldPropName = AndroidResourceUtil.getResourceNameByReferenceText(attribute.getValue())
|
||||||
val newPropName = processor!!.resourceManager.idToName(newName!!)
|
val newPropName = idToName(newName)
|
||||||
renameSyntheticProperties(allRenames, newPropName, oldPropName, processor)
|
renameSyntheticProperties(allRenames, newPropName, oldPropName, processor)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renameSyntheticProperties(allRenames: MutableMap<PsiElement, String>, newPropName: String, oldPropName: String?, processor: AndroidUIXmlProcessor) {
|
private fun renameSyntheticProperties(
|
||||||
val props = processor.lastCachedPsi?.findChildrenByClass(javaClass<JetProperty>())
|
allRenames: MutableMap<PsiElement, String>,
|
||||||
val matchedProps = props?.filter { it.getName() == oldPropName } ?: arrayListOf()
|
newPropName: String,
|
||||||
|
oldPropName: String,
|
||||||
|
processor: AndroidUIXmlProcessor
|
||||||
|
) {
|
||||||
|
val props = processor.parseToPsi()?.flatMap { it.findChildrenByClass(javaClass<JetProperty>()).toList() }
|
||||||
|
val matchedProps = props?.filter { it.getName() == oldPropName } ?: listOf()
|
||||||
for (prop in matchedProps) {
|
for (prop in matchedProps) {
|
||||||
allRenames[prop] = newPropName
|
allRenames[prop] = newPropName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renameLightClassField(field: LightElement, newName: String?, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
|
private fun renameLightClassField(
|
||||||
|
field: LightElement,
|
||||||
|
newName: String,
|
||||||
|
allRenames: MutableMap<PsiElement, String>,
|
||||||
|
scope: SearchScope
|
||||||
|
) {
|
||||||
val oldName = field.getName()
|
val oldName = field.getName()
|
||||||
val processor = ServiceManager.getService(field.getProject(), javaClass<AndroidUIXmlProcessor>())
|
val processor = ServiceManager.getService(field.getProject(), javaClass<AndroidUIXmlProcessor>())
|
||||||
renameSyntheticProperties(allRenames, newName!!, oldName, processor!!)
|
renameSyntheticProperties(allRenames, newName, oldName, processor)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ public class AndroidSimpleNameReferenceExtension : SimpleNameReferenceExtension
|
|||||||
}
|
}
|
||||||
if (isAndroidSyntheticElement(resolvedElement)) {
|
if (isAndroidSyntheticElement(resolvedElement)) {
|
||||||
if (element is ValueResourceElementWrapper) {
|
if (element is ValueResourceElementWrapper) {
|
||||||
val resource = element.getValue()!!
|
val resource = element.getValue()
|
||||||
return (resolvedElement as JetProperty).getName() == resource.substring(resource.indexOf('/') + 1)
|
return (resolvedElement as JetProperty).getName() == resource.substring(resource.indexOf('/') + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -22,8 +22,13 @@ import com.intellij.psi.xml.XmlElement
|
|||||||
import com.intellij.psi.xml.XmlTag
|
import com.intellij.psi.xml.XmlTag
|
||||||
import com.intellij.psi.xml.XmlAttribute
|
import com.intellij.psi.xml.XmlAttribute
|
||||||
import org.jetbrains.jet.lang.resolve.android.AndroidResourceManager
|
import org.jetbrains.jet.lang.resolve.android.AndroidResourceManager
|
||||||
|
import org.jetbrains.jet.lang.resolve.android.idToName
|
||||||
|
import org.jetbrains.jet.lang.resolve.android.AndroidConst
|
||||||
|
|
||||||
class AndroidXmlVisitor(val resourceManager: AndroidResourceManager, val elementCallback: (String, String, XmlAttribute) -> Unit) : XmlElementVisitor() {
|
class AndroidXmlVisitor(
|
||||||
|
val resourceManager: AndroidResourceManager,
|
||||||
|
val elementCallback: (String, String, XmlAttribute) -> Unit
|
||||||
|
) : XmlElementVisitor() {
|
||||||
|
|
||||||
override fun visitElement(element: PsiElement) {
|
override fun visitElement(element: PsiElement) {
|
||||||
element.acceptChildren(this)
|
element.acceptChildren(this)
|
||||||
@@ -34,10 +39,13 @@ class AndroidXmlVisitor(val resourceManager: AndroidResourceManager, val element
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitXmlTag(tag: XmlTag?) {
|
override fun visitXmlTag(tag: XmlTag?) {
|
||||||
val attribute = tag?.getAttribute(resourceManager.idAttribute)
|
val attribute = tag?.getAttribute(AndroidConst.ID_ATTRIBUTE)
|
||||||
if (attribute != null && attribute.getValue() != null) {
|
if (attribute != null) {
|
||||||
val classNameAttr = tag?.getAttribute(resourceManager.classAttributeNoNamespace)?.getValue() ?: tag?.getLocalName()
|
val attributeValue = attribute.getValue()
|
||||||
elementCallback(resourceManager.idToName(attribute.getValue()), classNameAttr!!, attribute)
|
if (attributeValue != null) {
|
||||||
|
val classNameAttr = tag?.getAttribute(AndroidConst.CLASS_ATTRIBUTE_NO_NAMESPACE)?.getValue() ?: tag?.getLocalName()
|
||||||
|
elementCallback(idToName(attributeValue), classNameAttr!!, attribute)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tag?.acceptChildren(this)
|
tag?.acceptChildren(this)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ public class IDEAndroidExternalDeclarationsProvider(private val project: Project
|
|||||||
|
|
||||||
val module = moduleInfo.module
|
val module = moduleInfo.module
|
||||||
val parser = ModuleServiceManager.getService<AndroidUIXmlProcessor>(module, javaClass<AndroidUIXmlProcessor>())
|
val parser = ModuleServiceManager.getService<AndroidUIXmlProcessor>(module, javaClass<AndroidUIXmlProcessor>())
|
||||||
val syntheticFile = parser.parseToPsi(project)
|
val syntheticFile = parser.parseToPsi()
|
||||||
syntheticFile?.moduleInfo = moduleInfo
|
syntheticFile?.moduleInfo = moduleInfo
|
||||||
|
|
||||||
return if (syntheticFile != null) listOf(syntheticFile) else listOf()
|
return if (syntheticFile != null) listOf(syntheticFile) else listOf()
|
||||||
|
|||||||
+15
-19
@@ -17,36 +17,23 @@
|
|||||||
package org.jetbrains.jet.plugin.android
|
package org.jetbrains.jet.plugin.android
|
||||||
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import org.jetbrains.jet.lang.resolve.android.AndroidResourceManagerBase
|
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
import com.intellij.psi.xml.XmlAttribute
|
import com.intellij.psi.xml.XmlAttribute
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import org.jetbrains.android.facet.AndroidFacet
|
import org.jetbrains.android.facet.AndroidFacet
|
||||||
import org.jetbrains.jet.lang.resolve.android.AndroidManifest
|
|
||||||
import com.intellij.openapi.module.ModuleManager
|
import com.intellij.openapi.module.ModuleManager
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.openapi.module.Module
|
import com.intellij.openapi.module.Module
|
||||||
|
import com.intellij.psi.PsiManager
|
||||||
|
import org.jetbrains.jet.lang.resolve.android.AndroidResourceManager
|
||||||
|
import org.jetbrains.jet.lang.resolve.android.AndroidModuleInfo
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
public class IDEAndroidResourceManager(val module: Module, searchPath: String?) : AndroidResourceManagerBase(module.getProject(), searchPath) {
|
public class IDEAndroidResourceManager(val module: Module) : AndroidResourceManager(module.getProject()) {
|
||||||
|
|
||||||
override fun getLayoutXmlFiles(): Collection<PsiFile> {
|
override val androidModuleInfo: AndroidModuleInfo? by Delegates.lazy { module.androidFacet?.toAndroidModuleInfo() }
|
||||||
val directories = getAndroidFacet()?.getAllResourceDirectories() ?: listOf()
|
|
||||||
return directories.flatMap {
|
|
||||||
(it.findChild("layout")?.getChildren() ?: array<VirtualFile>()).map { virtualFileToPsi(it)!! }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAndroidFacet(): AndroidFacet? {
|
|
||||||
return AndroidFacet.getInstance(module)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun readManifest(): AndroidManifest {
|
|
||||||
val facet = getAndroidFacet()
|
|
||||||
val attributeValue = facet?.getManifest()!!.getPackage()
|
|
||||||
return AndroidManifest(attributeValue.getRawText())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun idToXmlAttribute(id: String): PsiElement? {
|
override fun idToXmlAttribute(id: String): PsiElement? {
|
||||||
var ret: PsiElement? = null
|
var ret: PsiElement? = null
|
||||||
@@ -58,4 +45,13 @@ public class IDEAndroidResourceManager(val module: Module, searchPath: String?)
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val Module.androidFacet: AndroidFacet?
|
||||||
|
get() = AndroidFacet.getInstance(this)
|
||||||
|
|
||||||
|
private fun AndroidFacet.toAndroidModuleInfo(): AndroidModuleInfo {
|
||||||
|
val applicationPackage = getManifest().getPackage().toString()
|
||||||
|
val mainResDirectory = getAllResourceDirectories().firstOrNull()?.getPath()
|
||||||
|
return AndroidModuleInfo(applicationPackage, mainResDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-10
@@ -28,18 +28,16 @@ import org.jetbrains.jet.lang.resolve.android.AndroidResourceManager
|
|||||||
import com.intellij.openapi.module.Module
|
import com.intellij.openapi.module.Module
|
||||||
|
|
||||||
class IDEAndroidUIXmlProcessor(val module: Module) : AndroidUIXmlProcessor(module.getProject()) {
|
class IDEAndroidUIXmlProcessor(val module: Module) : AndroidUIXmlProcessor(module.getProject()) {
|
||||||
override val searchPath: String? = project.getBasePath() + "/res/layout/"
|
|
||||||
override var androidAppPackage: String = ""
|
|
||||||
get() = resourceManager.readManifest()._package
|
|
||||||
|
|
||||||
override val resourceManager: IDEAndroidResourceManager = IDEAndroidResourceManager(module, searchPath)
|
override val resourceManager: IDEAndroidResourceManager = IDEAndroidResourceManager(module)
|
||||||
|
|
||||||
override fun parseSingleFileImpl(file: PsiFile): String {
|
override fun parseSingleFile(file: PsiFile): List<AndroidWidget> {
|
||||||
val ids: MutableCollection<AndroidWidget> = ArrayList()
|
val widgets = arrayListOf<AndroidWidget>()
|
||||||
file.accept(AndroidXmlVisitor(resourceManager, { id, wClass, valueElement ->
|
file.accept(AndroidXmlVisitor(resourceManager, { id, wClass, valueElement ->
|
||||||
ids.add(AndroidWidget(id, wClass))
|
widgets.add(AndroidWidget(id, wClass))
|
||||||
}))
|
}))
|
||||||
return produceKotlinProperties(KotlinStringWriter(), ids).toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return widgets
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+5
-1
@@ -20,6 +20,7 @@ import org.jetbrains.jet.plugin.PluginTestCaseBase
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
import com.intellij.codeInsight.TargetElementUtilBase
|
import com.intellij.codeInsight.TargetElementUtilBase
|
||||||
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction
|
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
public abstract class AbstractAndroidFindUsagesTest : KotlinAndroidTestCase() {
|
public abstract class AbstractAndroidFindUsagesTest : KotlinAndroidTestCase() {
|
||||||
|
|
||||||
@@ -32,7 +33,10 @@ public abstract class AbstractAndroidFindUsagesTest : KotlinAndroidTestCase() {
|
|||||||
f.configureFromExistingVirtualFile(virtualFile)
|
f.configureFromExistingVirtualFile(virtualFile)
|
||||||
|
|
||||||
val targetElement = TargetElementUtilBase.findTargetElement(f.getEditor(), TargetElementUtilBase.ELEMENT_NAME_ACCEPTED or TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED)
|
val targetElement = TargetElementUtilBase.findTargetElement(f.getEditor(), TargetElementUtilBase.ELEMENT_NAME_ACCEPTED or TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED)
|
||||||
val propUsages = f.findUsages(targetElement!!)
|
|
||||||
|
assertNotNull(targetElement)
|
||||||
|
|
||||||
|
val propUsages = f.findUsages(targetElement)
|
||||||
|
|
||||||
assertTrue(propUsages.notEmpty)
|
assertTrue(propUsages.notEmpty)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -24,6 +24,7 @@ import com.intellij.refactoring.rename.RenameProcessor
|
|||||||
import org.jetbrains.jet.plugin.PluginTestCaseBase
|
import org.jetbrains.jet.plugin.PluginTestCaseBase
|
||||||
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction
|
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import org.jetbrains.jet.lang.psi.JetProperty
|
import org.jetbrains.jet.lang.psi.JetProperty
|
||||||
|
|
||||||
@@ -42,9 +43,10 @@ public abstract class AbstractAndroidRenameTest : KotlinAndroidTestCase() {
|
|||||||
val completionEditor = InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(editor, file)
|
val completionEditor = InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(editor, file)
|
||||||
val element = TargetElementUtilBase.findTargetElement(completionEditor, TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED or TargetElementUtilBase.ELEMENT_NAME_ACCEPTED)
|
val element = TargetElementUtilBase.findTargetElement(completionEditor, TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED or TargetElementUtilBase.ELEMENT_NAME_ACCEPTED)
|
||||||
assert(element != null)
|
assert(element != null)
|
||||||
|
assertTrue(element is JetProperty)
|
||||||
|
|
||||||
// rename xml attribute by property
|
// rename xml attribute by property
|
||||||
renameElementWithTextOccurences(element!!, NEW_NAME)
|
renameElementWithTextOccurences(element, NEW_NAME)
|
||||||
val resolved = GotoDeclarationAction.findTargetElement(f.getProject(), f.getEditor(), f.getCaretOffset())
|
val resolved = GotoDeclarationAction.findTargetElement(f.getProject(), f.getEditor(), f.getCaretOffset())
|
||||||
assertEquals("\"@+id/$NEW_NAME\"", resolved?.getText())
|
assertEquals("\"@+id/$NEW_NAME\"", resolved?.getText())
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -32,14 +32,14 @@ import com.intellij.openapi.module.ModuleManager
|
|||||||
|
|
||||||
public abstract class AbstractParserResultEqualityTest : KotlinAndroidTestCase() {
|
public abstract class AbstractParserResultEqualityTest : KotlinAndroidTestCase() {
|
||||||
public fun doTest(path: String) {
|
public fun doTest(path: String) {
|
||||||
val project = myFixture!!.getProject()
|
val project = myFixture.getProject()
|
||||||
project.putUserData(TestConst.TESTDATA_PATH, path)
|
project.putUserData(TestConst.TESTDATA_PATH, path)
|
||||||
myFixture!!.copyDirectoryToProject(getResDir()!!, "res")
|
myFixture.copyDirectoryToProject(getResDir(), "res")
|
||||||
val cliParser = CliAndroidUIXmlProcessor(project, path + getResDir() + "/layout/", path + "../AndroidManifest.xml")
|
val cliParser = CliAndroidUIXmlProcessor(project, path + "../AndroidManifest.xml", path + getResDir() + "/layout/")
|
||||||
val ideParser = IDEAndroidUIXmlProcessor(ModuleManager.getInstance(project).getModules()[0])
|
val ideParser = IDEAndroidUIXmlProcessor(ModuleManager.getInstance(project).getModules()[0])
|
||||||
|
|
||||||
val cliResult = cliParser.parseToPsi(project)!!.getText()
|
val cliResult = cliParser.parseToPsi()!!.getText()
|
||||||
val ideResult = ideParser.parseToPsi(project)!!.getText()
|
val ideResult = ideParser.parseToPsi()!!.getText()
|
||||||
|
|
||||||
assertEquals(cliResult, ideResult)
|
assertEquals(cliResult, ideResult)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user