From 934a3b04369870c34c138afd80568f6b04a948ec Mon Sep 17 00:00:00 2001 From: Mikhail Mutcianko Date: Wed, 30 Jul 2014 19:57:13 +0400 Subject: [PATCH] rename android synthetic id property also renames respective xml resource --- .../resolve/android/AndroidUIXmlParser.kt | 6 +- .../jet/lang/resolve/android/AndroidUtil.kt | 15 +- .../resolve/android/CliAndroidUIXmlParser.kt | 5 + .../plugin/android/IDEAndroidUIXmlParser.kt | 24 + .../android/XmlAttributeValueWrapper.java | 470 ++++++++++++++++++ .../rename/RenameKotlinPropertyProcessor.kt | 8 + 6 files changed, 522 insertions(+), 6 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/plugin/android/XmlAttributeValueWrapper.java diff --git a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUIXmlParser.kt b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUIXmlParser.kt index 8d22e098e29..80f0350196c 100644 --- a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUIXmlParser.kt +++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUIXmlParser.kt @@ -49,6 +49,7 @@ import java.io.ByteArrayInputStream import com.intellij.openapi.diagnostic.Log import org.xml.sax.SAXException import com.intellij.openapi.diagnostic.Logger +import com.intellij.psi.PsiElement abstract class AndroidUIXmlParser { @@ -88,6 +89,8 @@ abstract class AndroidUIXmlParser { return renderString() } + protected fun getXmlLayoutFiles(): Collection = fileCache.keySet() + public fun parseToPsi(project: Project): JetFile? { populateQueue(project) val cacheState = doParse() @@ -185,7 +188,7 @@ abstract class AndroidUIXmlParser { val fileManager = VirtualFileManager.getInstance() val watchDir = fileManager.findFileByUrl("file://" + searchPath) val psiManager = PsiManager.getInstance(project) - filesToProcess.addAll(watchDir?.getChildren()?.toArrayList()?.map { psiManager.findFile(it) } ?.mapNotNull { it }) + filesToProcess.addAll(watchDir?.getChildren()?.toArrayList()?.map { psiManager.findFile(it) } ?.mapNotNull { it } ?: ArrayList(0)) } protected abstract fun lazySetup() @@ -221,4 +224,5 @@ abstract class AndroidUIXmlParser { return kw.output() } + abstract fun renameId(oldName: String?, newName: String?, allRenames: MutableMap) } diff --git a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUtil.kt b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUtil.kt index 7fee491f440..0aed83cc255 100644 --- a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUtil.kt +++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/AndroidUtil.kt @@ -39,7 +39,16 @@ class AndroidWidget(val id: String, val className: String): AndroidResource class AndroidManifest(val _package: String): AndroidResource -fun isAndroidSyntheticFile(f: VirtualFile?): Boolean { +//fun isAndroidSyntheticFile(f: VirtualFile?): Boolean { +// if (f?.getName() == AndroidConst.SYNTHETIC_FILENAME) { +// val userData = f?.getUserData(AndroidConst.ANDROID_SYNTHETIC); +// return (userData != null && userData.equals("OK")) +// } +// return false +//} + +fun isAndroidSyntheticFile(f: PsiFile?): Boolean { +// return isAndroidSyntheticFile(f?.getVirtualFile()) if (f?.getName() == AndroidConst.SYNTHETIC_FILENAME) { val userData = f?.getUserData(AndroidConst.ANDROID_SYNTHETIC); return (userData != null && userData.equals("OK")) @@ -47,10 +56,6 @@ fun isAndroidSyntheticFile(f: VirtualFile?): Boolean { return false } -fun isAndroidSyntheticFile(f: PsiFile?): Boolean { - return isAndroidSyntheticFile(f?.getVirtualFile()) -} - fun isAndroidSyntheticElement(element: PsiElement?): Boolean { return isAndroidSyntheticFile(element?.getContainingFile()) } \ No newline at end of file diff --git a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/CliAndroidUIXmlParser.kt b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/CliAndroidUIXmlParser.kt index 4dcb058c153..7f6ca01b903 100644 --- a/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/CliAndroidUIXmlParser.kt +++ b/compiler/frontend.android/src/org/jetbrains/jet/lang/resolve/android/CliAndroidUIXmlParser.kt @@ -24,6 +24,7 @@ import java.io.ByteArrayInputStream import org.xml.sax.InputSource import javax.xml.parsers.SAXParser import javax.xml.parsers.SAXParserFactory +import com.intellij.psi.PsiElement class CliAndroidUIXmlParser(val project: Project, override val searchPath: String?): AndroidUIXmlParser() { @@ -47,5 +48,9 @@ class CliAndroidUIXmlParser(val project: Project, override val searchPath: Strin return "" } } + + override fun renameId(oldName: String?, newName: String?, allRenames: MutableMap) { + return + } } diff --git a/idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlParser.kt b/idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlParser.kt index 96e9ad2646e..61d31fa52b2 100644 --- a/idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlParser.kt +++ b/idea/src/org/jetbrains/jet/plugin/android/IDEAndroidUIXmlParser.kt @@ -25,6 +25,10 @@ import com.intellij.openapi.vfs.VirtualFileEvent 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 class IDEAndroidUIXmlParser(val project: Project): AndroidUIXmlParser() { override val searchPath: String? = project.getBasePath() + "/res/layout/" @@ -42,5 +46,25 @@ class IDEAndroidUIXmlParser(val project: Project): AndroidUIXmlParser() { file.accept(AndroidXmlVisitor({ id, wClass -> ids.add(AndroidWidget(id, wClass))})) return produceKotlinProperties(KotlinStringWriter(), ids).toString() } + + override fun renameId(oldName: String?, newName: String?, allRenames: MutableMap) { + 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) + } + }) + } + } + } } diff --git a/idea/src/org/jetbrains/jet/plugin/android/XmlAttributeValueWrapper.java b/idea/src/org/jetbrains/jet/plugin/android/XmlAttributeValueWrapper.java new file mode 100644 index 00000000000..dc6c326baaa --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/android/XmlAttributeValueWrapper.java @@ -0,0 +1,470 @@ +/* + * 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.lang.ASTNode; +import com.intellij.lang.Language; +import com.intellij.navigation.ItemPresentation; +import com.intellij.navigation.NavigationItem; +import com.intellij.navigation.PsiElementNavigationItem; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.meta.PsiMetaOwner; +import com.intellij.psi.scope.PsiScopeProcessor; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiElementProcessor; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlAttributeValue; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.xml.DomElement; +import com.intellij.util.xml.DomManager; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.File; + +/** + * Created by IntelliJ IDEA. + * User: Eugene.Kudelevsky + * Date: Aug 6, 2009 + * Time: 7:08:02 PM + * To change this template use File | Settings | File Templates. + */ + +public class XmlAttributeValueWrapper implements XmlAttributeValue, PsiNamedElement, PsiElementNavigationItem { + private final XmlAttributeValue myWrappee; + private final String myFileName; + private final String myDirName; + + public XmlAttributeValueWrapper(@NotNull XmlAttributeValue wrappeeElement) { + if (!(wrappeeElement instanceof NavigationItem)) { + throw new IllegalArgumentException(); + } + if (!(wrappeeElement instanceof PsiMetaOwner)) { + throw new IllegalArgumentException(); + } + myWrappee = wrappeeElement; + final PsiFile file = getContainingFile(); + myFileName = file != null ? file.getName() : null; + final PsiDirectory dir = file != null ? file.getContainingDirectory() : null; + myDirName = dir != null ? dir.getName() : null; + } + + @Override + @NotNull + public Project getProject() throws PsiInvalidElementAccessException { + return myWrappee.getProject(); + } + + @Override + @NotNull + public Language getLanguage() { + return myWrappee.getLanguage(); + } + + @Override + public PsiManager getManager() { + return myWrappee.getManager(); + } + + @Override + @NotNull + public PsiElement[] getChildren() { + return myWrappee.getChildren(); + } + + @Override + public PsiElement getParent() { + return myWrappee.getParent(); + } + + @Override + @Nullable + public PsiElement getFirstChild() { + return myWrappee.getFirstChild(); + } + + @Override + @Nullable + public PsiElement getLastChild() { + return myWrappee.getLastChild(); + } + + @Override + @Nullable + public PsiElement getNextSibling() { + return myWrappee.getNextSibling(); + } + + @Override + @Nullable + public PsiElement getPrevSibling() { + return myWrappee.getPrevSibling(); + } + + @Override + public PsiFile getContainingFile() throws PsiInvalidElementAccessException { + return myWrappee.getContainingFile(); + } + + @Override + public TextRange getTextRange() { + return myWrappee.getTextRange(); + } + + @Override + public int getStartOffsetInParent() { + return myWrappee.getStartOffsetInParent(); + } + + @Override + public int getTextLength() { + return myWrappee.getTextLength(); + } + + @Override + @Nullable + public PsiElement findElementAt(int offset) { + return myWrappee.findElementAt(offset); + } + + @Override + @Nullable + public PsiReference findReferenceAt(int offset) { + return myWrappee.findReferenceAt(offset); + } + + @Override + public int getTextOffset() { + return myWrappee.getTextOffset(); + } + + @Override + @NonNls + public String getText() { + return myWrappee.getText(); + } + + @Override + @NotNull + public char[] textToCharArray() { + return myWrappee.textToCharArray(); + } + + @Override + public PsiElement getNavigationElement() { + return myWrappee.getNavigationElement(); + } + + @Override + public PsiElement getOriginalElement() { + return myWrappee.getOriginalElement(); + } + + @Override + public boolean textMatches(@NotNull @NonNls CharSequence text) { + return myWrappee.textMatches(text); + } + + @Override + public boolean textMatches(@NotNull PsiElement element) { + return myWrappee.textMatches(element); + } + + @Override + public boolean textContains(char c) { + return myWrappee.textContains(c); + } + + @Override + public void accept(@NotNull PsiElementVisitor visitor) { + myWrappee.accept(visitor); + } + + @Override + public void acceptChildren(@NotNull PsiElementVisitor visitor) { + myWrappee.acceptChildren(visitor); + } + + @Override + public PsiElement copy() { + return myWrappee.copy(); + } + + @Override + public PsiElement add(@NotNull PsiElement element) throws IncorrectOperationException { + return myWrappee.add(element); + } + + @Override + public PsiElement addBefore(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException { + return myWrappee.addBefore(element, anchor); + } + + @Override + public PsiElement addAfter(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException { + return myWrappee.addAfter(element, anchor); + } + + @Override + public void checkAdd(@NotNull PsiElement element) throws IncorrectOperationException { + myWrappee.checkAdd(element); + } + + @Override + public PsiElement addRange(PsiElement first, PsiElement last) throws IncorrectOperationException { + return myWrappee.addRange(first, last); + } + + @Override + public PsiElement addRangeBefore(@NotNull PsiElement first, @NotNull PsiElement last, PsiElement anchor) + throws IncorrectOperationException { + return myWrappee.addRangeBefore(first, last, anchor); + } + + @Override + public PsiElement addRangeAfter(PsiElement first, PsiElement last, PsiElement anchor) throws IncorrectOperationException { + return myWrappee.addRangeAfter(first, last, anchor); + } + + @Override + public void delete() throws IncorrectOperationException { + myWrappee.delete(); + } + + @Override + public void checkDelete() throws IncorrectOperationException { + myWrappee.checkDelete(); + } + + @Override + public void deleteChildRange(PsiElement first, PsiElement last) throws IncorrectOperationException { + myWrappee.deleteChildRange(first, last); + } + + @Override + public PsiElement replace(@NotNull PsiElement newElement) throws IncorrectOperationException { + return myWrappee.replace(newElement); + } + + @Override + public boolean isValid() { + return myWrappee.isValid(); + } + + @Override + public boolean isWritable() { + return myWrappee.isWritable(); + } + + @Override + @Nullable + public PsiReference getReference() { + return myWrappee.getReference(); + } + + @Override + @NotNull + public PsiReference[] getReferences() { + return myWrappee.getReferences(); + } + + @Override + @Nullable + public T getCopyableUserData(Key key) { + return myWrappee.getCopyableUserData(key); + } + + @Override + public void putCopyableUserData(Key key, T value) { + myWrappee.putCopyableUserData(key, value); + } + + @Override + public boolean processDeclarations( + @NotNull PsiScopeProcessor processor, + @NotNull ResolveState state, + @Nullable PsiElement lastParent, + @NotNull PsiElement place + ) { + return myWrappee.processDeclarations(processor, state, lastParent, place); + } + + @Override + @Nullable + public PsiElement getContext() { + return myWrappee.getContext(); + } + + @Override + public boolean isPhysical() { + return myWrappee.isPhysical(); + } + + @Override + @NotNull + public GlobalSearchScope getResolveScope() { + return myWrappee.getResolveScope(); + } + + @Override + @NotNull + public SearchScope getUseScope() { + return myWrappee.getUseScope(); + } + + @Override + @Nullable + public ASTNode getNode() { + return myWrappee.getNode(); + } + + @NonNls + public String toString() { + return myWrappee.toString(); + } + + @Override + public boolean isEquivalentTo(PsiElement another) { + return myWrappee == another || myWrappee.isEquivalentTo(another); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + XmlAttributeValueWrapper that = (XmlAttributeValueWrapper) o; + + if (!myWrappee.equals(that.myWrappee)) return false; + + return true; + } + + @Override + public int hashCode() { + return myWrappee.hashCode(); + } + + @Override + public T getUserData(@NotNull Key key) { + return myWrappee.getUserData(key); + } + + @Override + public void putUserData(@NotNull Key key, @Nullable T value) { + myWrappee.putUserData(key, value); + } + + @Override + public Icon getIcon(int flags) { + return myWrappee.getIcon(flags); + } + + @Override + public String getName() { + String value = myWrappee.getValue(); + return ((NavigationItem) myWrappee).getName(); + } + + @Override + @Nullable + public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException { + //if (AndroidResourceUtil.isIdDeclaration(myWrappee)) { + XmlAttribute attribute = (XmlAttribute) myWrappee.getParent(); + attribute.setValue(name); + //} + //else { + // // then it is a value resource + // XmlTag tag = PsiTreeUtil.getParentOfType(myWrappee, XmlTag.class); + // DomElement domElement = DomManager.getDomManager(getProject()).getDomElement(tag); + // assert domElement instanceof ResourceElement; + // ResourceElement resElement = (ResourceElement) domElement; + // resElement.getName().setValue(name); + //} + return null; + } + + @Override + public ItemPresentation getPresentation() { + return new ItemPresentation() { + @Override + @Nullable + public String getPresentableText() { + String name = ((NavigationItem) myWrappee).getName(); + if (myDirName == null || myFileName == null) { + return name; + } + return name + " (..." + File.separatorChar + myDirName + + File.separatorChar + myFileName + ')'; + } + + @Override + public String getLocationString() { + return null; + } + + @Override + public Icon getIcon(boolean open) { + return null; + } + }; + } + + @Override + public void navigate(boolean requestFocus) { + ((NavigationItem) myWrappee).navigate(requestFocus); + } + + @Override + public boolean canNavigate() { + return ((NavigationItem) myWrappee).canNavigate(); + } + + @Override + public boolean canNavigateToSource() { + return ((NavigationItem) myWrappee).canNavigateToSource(); + } + + @Override + public String getValue() { + return myWrappee.getValue(); + } + + @Override + public TextRange getValueTextRange() { + return myWrappee.getValueTextRange(); + } + + @Override + public boolean processElements(PsiElementProcessor processor, PsiElement place) { + return myWrappee.processElements(processor, place); + } + + @Override + public PsiElement getTargetElement() { + return myWrappee; + } +} + diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt index c21988b1d12..d1f20df44c7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt @@ -42,6 +42,9 @@ import org.jetbrains.kotlin.resolve.OverrideResolver import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.jet.lang.resolve.android.isAndroidSyntheticElement +import com.intellij.openapi.components.ServiceManager +import org.jetbrains.jet.lang.resolve.android.AndroidUIXmlParser public class RenameKotlinPropertyProcessor : RenamePsiElementProcessor() { override fun canProcessElement(element: PsiElement): Boolean = element.namedUnwrappedElement is JetProperty @@ -88,6 +91,11 @@ 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()) + parser?.renameId(jetProperty.getName(), newName, allRenames) + } } private enum class UsageKind {