move android rename handler out of kotlin property renamer, extract resource manager class

This commit is contained in:
Mikhail Mutcianko
2014-07-31 16:28:12 +04:00
committed by Yan Zhulanow
parent 395959bf21
commit 1b50b226ae
14 changed files with 291 additions and 160 deletions
@@ -296,7 +296,7 @@ public class JetCoreEnvironment {
);
String s = configuration.get(JVMConfigurationKeys.ANDROID_RES_PATH);
project.registerService(AndroidUIXmlParser.class, new CliAndroidUIXmlParser(project, s));
project.registerService(AndroidUIXmlProcessor.class, new CliAndroidUIXmlProcessor(project, s));
project.registerService(VirtualFileFinderFactory.class, new CliVirtualFileFinderFactory(classPath));
}
@@ -0,0 +1,28 @@
/*
* 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(val project: Project, val searchPath: String?) {
abstract fun getLayoutXmlFiles(): Collection<PsiFile>
abstract fun idToXmlAttribute(id: String): PsiElement?
abstract fun renameXmlAttr(elem: PsiElement, newName: String)
abstract fun renameProperty(oldName: String, newName: String)
}
@@ -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 com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiManager
import java.util.ArrayList
import com.intellij.openapi.vfs.VirtualFileManager
open 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)
return watchDir?.getChildren()?.toArrayList()?.map { psiManager.findFile(it) }?.mapNotNull { it } ?: ArrayList(0)
}
override fun idToXmlAttribute(id: String): PsiElement? {
return null
}
override fun renameXmlAttr(elem: PsiElement, newName: String) {
}
override fun renameProperty(oldName: String, newName: String) {
}
}
@@ -17,6 +17,9 @@
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
@@ -44,9 +47,8 @@ import com.intellij.psi.impl.PsiModificationTrackerImpl
import java.util.Queue
import com.intellij.psi.PsiFile
import com.intellij.openapi.diagnostic.Logger
import com.intellij.psi.PsiElement
abstract class AndroidUIXmlParser {
abstract class AndroidUIXmlProcessor(val project: Project) {
inner class NoUIXMLsFound : Exception("No android UI xmls found in $searchPath")
class NoAndroidManifestFound : Exception("No android manifest file found in project root")
@@ -85,12 +87,10 @@ abstract class AndroidUIXmlParser {
return renderString()
}
protected fun getXmlLayoutFiles(): Collection<PsiFile> = fileCache.keySet()
public abstract fun idToXmlAttribute(id: String): PsiElement?
public open val resourceManager: AndroidResourceManager = AndroidResourceManagerBase(project, searchPath)
public fun parseToPsi(project: Project): JetFile? {
populateQueue(project)
populateQueue()
val cacheState = doParse()
if (cacheState == null) return null
return if (cacheState == CacheAction.MISS || lastCachedPsi == null) {
@@ -110,30 +110,6 @@ abstract class AndroidUIXmlParser {
else lastCachedPsi
}
private fun isAndroidUIXml(file: File): Boolean {
return file.extension == "xml"
}
private fun searchForUIXml(path: String): Collection<File> {
return searchForUIXml(arrayListOf(File(path)))
}
private fun searchForUIXml(paths: Collection<File>?): Collection<File> {
if (paths == null) return ArrayList(0)
val res = ArrayList<File>()
for (path in paths) {
if (!path.exists()) continue;
if (path.isFile() && isAndroidUIXml(path)) {
res.add(path)
}
else if (path.isDirectory()) {
res.addAll(searchForUIXml(path.listFiles()?.toArrayList()))
}
}
return res
}
private fun writeImports(kw: KotlinStringWriter): KotlinWriter {
kw.writePackage(androidAppPackage)
for (elem in androidImports)
@@ -185,15 +161,8 @@ abstract class AndroidUIXmlParser {
lastCachedPsi = null
}
protected fun getXmlLayouts(project: Project): Collection<PsiFile> {
val fileManager = VirtualFileManager.getInstance()
val watchDir = fileManager.findFileByUrl("file://" + searchPath)
val psiManager = PsiManager.getInstance(project)
return watchDir?.getChildren()?.toArrayList()?.map { psiManager.findFile(it) }?.mapNotNull { it } ?: ArrayList(0)
}
protected fun populateQueue(project: Project) {
filesToProcess.addAll(getXmlLayouts(project))
protected fun populateQueue() {
filesToProcess.addAll(resourceManager.getLayoutXmlFiles())
}
protected abstract fun lazySetup()
@@ -230,6 +199,4 @@ abstract class AndroidUIXmlParser {
}
return kw.output()
}
abstract fun renameId(oldName: String?, newName: String?, allRenames: MutableMap<PsiElement, String>)
}
@@ -19,15 +19,14 @@ package org.jetbrains.jet.lang.resolve.android
import java.util.ArrayList
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiElement
class CliAndroidUIXmlParser(val project: Project, override val searchPath: String?) : AndroidUIXmlParser() {
class CliAndroidUIXmlProcessor(project: Project, override val searchPath: String?) : AndroidUIXmlProcessor(project) {
override var androidAppPackage: String = ""
override fun lazySetup() {
populateQueue(project)
populateQueue()
androidAppPackage = readManifest()._package
}
@@ -43,12 +42,5 @@ class CliAndroidUIXmlParser(val project: Project, override val searchPath: Strin
return ""
}
}
override fun idToXmlAttribute(id: String): PsiElement? {
return null
}
override fun renameId(oldName: String?, newName: String?, allRenames: MutableMap<PsiElement, String>) {
return
}
}
@@ -23,7 +23,7 @@ import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser;
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlProcessor;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.context.GlobalContext;
@@ -176,7 +176,7 @@ public enum TopDownAnalyzerFacadeForJVM {
}
private static Collection<JetFile> searchAndAddAndroidDeclarations(Project project, Collection<JetFile> files) {
AndroidUIXmlParser parser = ServiceManager.getService(project, AndroidUIXmlParser.class);
AndroidUIXmlProcessor parser = ServiceManager.getService(project, AndroidUIXmlProcessor.class);
JetFile file = parser.parseToPsi(project);
if (file != null) files.add(file);
return files;
@@ -106,7 +106,7 @@ public class AndroidXmlTest extends TestCaseWithTmpdir {
public void testConverterOneFile() throws Exception {
JetCoreEnvironment jetCoreEnvironment = getEnvironment(singleFileResPath);
AndroidUIXmlParser parser = new CliAndroidUIXmlParser(jetCoreEnvironment.getProject(), singleFileDir.getAbsolutePath());
AndroidUIXmlProcessor parser = new CliAndroidUIXmlProcessor(jetCoreEnvironment.getProject(), singleFileDir.getAbsolutePath());
String actual = parser.parseToString();
String expected = loadOrCreate(new File(getTestDataPath() + "/converter/singleFile/layout.kt"), actual);
+3
View File
@@ -198,6 +198,9 @@
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.LibraryModificationTracker"
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.LibraryModificationTracker"/>
<projectService serviceInterface="org.jetbrains.jet.lang.resolve.android.AndroidUIXmlProcessor"
serviceImplementation="org.jetbrains.jet.plugin.android.IDEAndroidUIXmlProcessor"/>
<projectService serviceInterface="org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade"
serviceImplementation="org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade"/>
@@ -21,14 +21,14 @@ import com.intellij.psi.PsiElement
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.components.ServiceManager
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlProcessor
import com.intellij.psi.impl.source.tree.LeafPsiElement
public class AndroidGotoDeclarationHandler : GotoDeclarationHandler {
override fun getGotoDeclarationTargets(sourceElement: PsiElement?, offset: Int, editor: Editor?): Array<PsiElement>? {
if (sourceElement is LeafPsiElement) {
val parser = ServiceManager.getService(sourceElement.getProject(), javaClass<AndroidUIXmlParser>())
val psiElement = parser?.idToXmlAttribute(sourceElement.getText())
val parser = ServiceManager.getService(sourceElement.getProject(), javaClass<AndroidUIXmlProcessor>())
val psiElement = parser?.resourceManager?.idToXmlAttribute(sourceElement.getText())
if (psiElement != null) {
return array(psiElement)
}
@@ -0,0 +1,84 @@
/*
* 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.plugin.android
import com.intellij.psi.PsiElement
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import org.jetbrains.jet.asJava.*
import org.jetbrains.jet.lang.psi.JetProperty
import org.jetbrains.jet.lang.resolve.android.isAndroidSyntheticElement
import com.intellij.openapi.components.ServiceManager
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlProcessor
import com.intellij.psi.search.SearchScope
import com.intellij.psi.xml.XmlAttributeValue
import org.jetbrains.android.dom.wrappers.LazyValueResourceElementWrapper
import org.jetbrains.android.util.AndroidResourceUtil
import com.intellij.psi.xml.XmlFile
import com.intellij.psi.XmlElementVisitor
import com.intellij.psi.xml.XmlTag
import com.intellij.psi.xml.XmlAttribute
public class AndroidRenameProcessor : RenamePsiElementProcessor() {
override fun canProcessElement(element: PsiElement): Boolean {
return (element.namedUnwrappedElement is JetProperty &&
isAndroidSyntheticElement(element.namedUnwrappedElement)) || element is XmlAttributeValue
}
override fun prepareRenaming(element: PsiElement?, newName: String?, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
if (element?.namedUnwrappedElement is JetProperty) {
renameSyntheticProperty(element!!.namedUnwrappedElement as JetProperty, newName, allRenames, scope)
}
else if (element is XmlAttributeValue) {
renameAttributeValue(element, newName, allRenames, scope)
}
}
private fun renameSyntheticProperty(jetProperty: JetProperty, newName: String?, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
val oldName = jetProperty.getName()!!
val processor = ServiceManager.getService(jetProperty.getProject(), javaClass<AndroidUIXmlProcessor>())
val resourceManager = processor!!.resourceManager
val attr = resourceManager.idToXmlAttribute(oldName) as XmlAttribute
for (file in resourceManager.getLayoutXmlFiles()) {
if (file is XmlFile) {
file.accept(object : XmlElementVisitor() {
override fun visitElement(element: PsiElement) {
element.acceptChildren(this)
}
override fun visitXmlTag(tag: XmlTag?) {
val idPrefix = "@+id/"
val attribute = tag?.getAttribute("android:id")
if (attribute != null && attribute.getValue() == idPrefix + oldName) {
allRenames[XmlAttributeValueWrapper(attribute.getValueElement()!!)] = idPrefix + newName
}
tag?.acceptChildren(this)
}
})
}
}
val name = AndroidResourceUtil.getResourceNameByReferenceText(newName!!)
for (resField in AndroidResourceUtil.findIdFields(attr)) {
allRenames.put(resField, AndroidResourceUtil.getFieldNameByResourceName(name!!))
}
resourceManager.renameProperty(oldName, newName)
}
private fun renameAttributeValue(attribute: XmlAttributeValue, newName: String?, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
val element1 = LazyValueResourceElementWrapper.computeLazyElement(attribute);
if (element1 == null) return
val id = AndroidResourceUtil.getResourceNameByReferenceText(attribute.getValue()!!)
}
}
@@ -0,0 +1,70 @@
/*
* 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.plugin.android
import com.intellij.openapi.project.Project
import org.jetbrains.jet.lang.resolve.android.AndroidResourceManagerBase
import com.intellij.psi.PsiElement
import java.util.HashMap
import com.intellij.psi.xml.XmlAttribute
import com.intellij.psi.xml.XmlFile
import com.intellij.psi.XmlElementVisitor
import com.intellij.psi.xml.XmlTag
public class IDEAndroidResourceManager(project: Project, searchPath: String?) : AndroidResourceManagerBase(project, searchPath) {
private val idToXmlAttributeCache = HashMap<String, PsiElement>()
;{
setupElementCache()
}
private fun setupElementCache() {
for (file in getLayoutXmlFiles()) {
if (file is XmlFile) {
file.accept(object : XmlElementVisitor() {
override fun visitElement(element: PsiElement) {
element.acceptChildren(this)
}
override fun visitXmlTag(tag: XmlTag?) {
val idPrefix = "@+id/"
val attribute = tag?.getAttribute("android:id")
val s = attribute?.getValue()
if (attribute != null && (s?.startsWith(idPrefix) ?: false)) {
idToXmlAttributeCache[s!!.replace(idPrefix, "")] = attribute
}
tag?.acceptChildren(this)
}
})
}
}
}
override fun idToXmlAttribute(id: String): PsiElement? {
return idToXmlAttributeCache[id]
}
override fun renameXmlAttr(elem: PsiElement, newName: String) {
val xmlAttr = elem as XmlAttribute
idToXmlAttributeCache.remove(xmlAttr.getName())
idToXmlAttributeCache[newName] = xmlAttr
}
override fun renameProperty(oldName: String, newName: String) {
val oldElem = idToXmlAttributeCache[oldName]
idToXmlAttributeCache.remove(oldName)
idToXmlAttributeCache[newName] = oldElem!!
}
}
@@ -1,97 +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.plugin.android
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser
import com.intellij.openapi.project.Project
import java.util.ArrayList
import com.intellij.psi.PsiFile
import org.jetbrains.jet.lang.resolve.android.AndroidWidget
import org.jetbrains.jet.lang.resolve.android.KotlinStringWriter
import com.intellij.psi.xml.XmlFile
import com.intellij.psi.XmlElementVisitor
import com.intellij.psi.xml.XmlTag
import com.intellij.psi.PsiElement
import java.util.HashMap
class IDEAndroidUIXmlParser(val project: Project) : AndroidUIXmlParser() {
override val searchPath: String? = project.getBasePath() + "/res/layout/"
override var androidAppPackage: String = ""
val idToXmlAttributeCache = HashMap<String, PsiElement>()
private fun setupElementCache() {
for (file in getXmlLayouts(project)) {
if (file is XmlFile) {
file.accept(object : XmlElementVisitor() {
override fun visitElement(element: PsiElement) {
element.acceptChildren(this)
}
override fun visitXmlTag(tag: XmlTag?) {
val idPrefix = "@+id/"
val attribute = tag?.getAttribute("android:id")
val s = attribute?.getValue()
if (attribute != null && (s?.startsWith(idPrefix) ?: false)) {
idToXmlAttributeCache[s!!.replace(idPrefix, "")] = attribute
}
tag?.acceptChildren(this)
}
})
}
}
}
override fun idToXmlAttribute(id: String): PsiElement? {
return idToXmlAttributeCache[id]
}
override protected fun lazySetup() {
if (listenerSetUp) return
androidAppPackage = readManifest()._package
populateQueue(project)
setupElementCache()
listenerSetUp = true
}
override fun parseSingleFileImpl(file: PsiFile): String {
val ids: MutableCollection<AndroidWidget> = ArrayList()
file.accept(AndroidXmlVisitor({ id, wClass -> ids.add(AndroidWidget(id, wClass)) }))
return produceKotlinProperties(KotlinStringWriter(), ids).toString()
}
override fun renameId(oldName: String?, newName: String?, allRenames: MutableMap<PsiElement, String>) {
for (file in getXmlLayoutFiles()) {
if (file is XmlFile) {
file.accept(object : XmlElementVisitor() {
override fun visitElement(element: PsiElement) {
element.acceptChildren(this)
}
override fun visitXmlTag(tag: XmlTag?) {
val idPrefix = "@+id/"
val attribute = tag?.getAttribute("android:id")
if (attribute != null && attribute.getValue() == idPrefix + oldName) {
allRenames[XmlAttributeValueWrapper(attribute.getValueElement()!!)] = idPrefix + newName
}
tag?.acceptChildren(this)
}
})
}
}
}
}
@@ -0,0 +1,46 @@
/*
* 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.plugin.android
import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlProcessor
import com.intellij.openapi.project.Project
import java.util.ArrayList
import com.intellij.psi.PsiFile
import org.jetbrains.jet.lang.resolve.android.AndroidWidget
import org.jetbrains.jet.lang.resolve.android.KotlinStringWriter
import org.jetbrains.jet.lang.resolve.android.AndroidResourceManager
class IDEAndroidUIXmlProcessor(project: Project) : AndroidUIXmlProcessor(project) {
override val searchPath: String? = project.getBasePath() + "/res/layout/"
override var androidAppPackage: String = ""
override val resourceManager: AndroidResourceManager = IDEAndroidResourceManager(project, searchPath)
override protected fun lazySetup() {
if (listenerSetUp) return
androidAppPackage = readManifest()._package
populateQueue()
listenerSetUp = true
}
override fun parseSingleFileImpl(file: PsiFile): String {
val ids: MutableCollection<AndroidWidget> = ArrayList()
file.accept(AndroidXmlVisitor({ id, wClass -> ids.add(AndroidWidget(id, wClass)) }))
return produceKotlinProperties(KotlinStringWriter(), ids).toString()
}
}
@@ -91,11 +91,6 @@ public class RenameKotlinPropertyProcessor : RenamePsiElementProcessor() {
for (propertyMethod in propertyMethods) {
addRenameElements(propertyMethod, jetProperty.getName(), newName, allRenames, scope)
}
if (isAndroidSyntheticElement(element)) {
val parser = ServiceManager.getService(element?.getProject()!!, javaClass<AndroidUIXmlParser>())
parser?.renameId(jetProperty.getName(), newName, allRenames)
}
}
private enum class UsageKind {