From 07e6ade7fe18de33439db0f3177c01ef7f7f14c2 Mon Sep 17 00:00:00 2001 From: Ben Gruver Date: Sat, 28 Mar 2015 12:50:36 -0700 Subject: [PATCH] Add support for renaming/moving classes --- smalidea/META-INF/plugin.xml | 1 + .../java/org/jf/smalidea/SmaliASTFactory.java | 19 ++++++ .../org/jf/smalidea/psi/impl/SmaliClass.java | 60 +++++++++++++++- .../psi/impl/SmaliClassStatement.java | 15 ++++ .../psi/impl/SmaliClassTypeElement.java | 24 +++++-- .../org/jf/smalidea/psi/impl/SmaliFile.java | 6 +- .../psi/leaf/SmaliClassDescriptor.java | 17 +++++ .../java/org/jf/smalidea/ClassMoveTest.java | 68 +++++++++++++++++++ .../java/org/jf/smalidea/ClassRenameTest.java | 51 ++++++++++++++ .../basicFromNoPackage/after/my/blah.smali | 36 ++++++++++ .../basicFromNoPackage/before/blah.smali | 36 ++++++++++ .../basicToNoPackage/after/blah.smali | 36 ++++++++++ .../basicToNoPackage/before/my/blah.smali | 36 ++++++++++ .../basicNoPackage/after/blah2.smali | 36 ++++++++++ .../basicNoPackage/before/blah.smali | 36 ++++++++++ .../basicWithPackage/after/my/blah2.smali | 36 ++++++++++ .../basicWithPackage/before/my/blah.smali | 36 ++++++++++ 17 files changed, 539 insertions(+), 10 deletions(-) create mode 100644 smalidea/src/main/java/org/jf/smalidea/SmaliASTFactory.java create mode 100644 smalidea/src/main/java/org/jf/smalidea/psi/leaf/SmaliClassDescriptor.java create mode 100644 smalidea/src/test/java/org/jf/smalidea/ClassMoveTest.java create mode 100644 smalidea/src/test/java/org/jf/smalidea/ClassRenameTest.java create mode 100644 smalidea/testData/classMove/basicFromNoPackage/after/my/blah.smali create mode 100644 smalidea/testData/classMove/basicFromNoPackage/before/blah.smali create mode 100644 smalidea/testData/classMove/basicToNoPackage/after/blah.smali create mode 100644 smalidea/testData/classMove/basicToNoPackage/before/my/blah.smali create mode 100644 smalidea/testData/classRename/basicNoPackage/after/blah2.smali create mode 100644 smalidea/testData/classRename/basicNoPackage/before/blah.smali create mode 100644 smalidea/testData/classRename/basicWithPackage/after/my/blah2.smali create mode 100644 smalidea/testData/classRename/basicWithPackage/before/my/blah.smali diff --git a/smalidea/META-INF/plugin.xml b/smalidea/META-INF/plugin.xml index d14d8cc9..866056a4 100644 --- a/smalidea/META-INF/plugin.xml +++ b/smalidea/META-INF/plugin.xml @@ -19,6 +19,7 @@ + diff --git a/smalidea/src/main/java/org/jf/smalidea/SmaliASTFactory.java b/smalidea/src/main/java/org/jf/smalidea/SmaliASTFactory.java new file mode 100644 index 00000000..4e0208c2 --- /dev/null +++ b/smalidea/src/main/java/org/jf/smalidea/SmaliASTFactory.java @@ -0,0 +1,19 @@ +package org.jf.smalidea; + +import com.intellij.lang.ASTFactory; +import com.intellij.psi.impl.source.tree.LeafElement; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.Nullable; +import org.jf.smalidea.psi.leaf.SmaliClassDescriptor; + +public class SmaliASTFactory extends ASTFactory { + + @Nullable + @Override + public LeafElement createLeaf(IElementType type, CharSequence text) { + if (type == SmaliTokens.CLASS_DESCRIPTOR) { + return new SmaliClassDescriptor(text); + } + return super.createLeaf(type, text); + } +} diff --git a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliClass.java b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliClass.java index ea9e89d1..bd731d3f 100644 --- a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliClass.java +++ b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliClass.java @@ -53,6 +53,7 @@ import org.jetbrains.annotations.Nullable; import org.jf.smalidea.SmaliIcons; import org.jf.smalidea.psi.SmaliElementTypes; import org.jf.smalidea.psi.iface.SmaliModifierListOwner; +import org.jf.smalidea.psi.leaf.SmaliClassDescriptor; import org.jf.smalidea.psi.stub.SmaliClassStub; import javax.annotation.Nonnull; @@ -244,8 +245,16 @@ public class SmaliClass extends SmaliStubBasedPsiElement impleme return null; } - @Nullable @Override public PsiIdentifier getNameIdentifier() { - return null; + @Nullable public SmaliClassStatement getClassStatement() { + return getStubOrPsiChild(SmaliElementTypes.CLASS_STATEMENT); + } + + @Nullable @Override public SmaliClassDescriptor getNameIdentifier() { + SmaliClassStatement classStatement = getClassStatement(); + if (classStatement == null) { + return null; + } + return classStatement.getNameIdentifier(); } @Override public PsiElement getScope() { @@ -269,7 +278,52 @@ public class SmaliClass extends SmaliStubBasedPsiElement impleme } @Override public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException { - return null; + SmaliClassStatement classStatement = getClassStatement(); + if (classStatement == null) { + throw new IncorrectOperationException(); + } + + SmaliClassTypeElement classTypeElement = classStatement.getNameElement(); + if (classTypeElement == null) { + throw new IncorrectOperationException(); + } + + String expectedPath = "/" + getName() + ".smali"; + String actualPath = this.getContainingFile().getVirtualFile().getPath(); + if (actualPath.endsWith(expectedPath)) { + getContainingFile().setName(name + ".smali"); + } + + String packageName = this.getPackageName(); + String newName; + if (packageName.length() > 0) { + newName = packageName + "." + name; + } else { + newName = name; + } + classTypeElement.handleElementRename(newName); + return this; + } + + public void setPackageName(@NonNls @NotNull String packageName) { + SmaliClassStatement classStatement = getClassStatement(); + if (classStatement == null) { + throw new IncorrectOperationException(); + } + + SmaliClassTypeElement classTypeElement = classStatement.getNameElement(); + if (classTypeElement == null) { + throw new IncorrectOperationException(); + } + + String newName; + if (packageName.length() > 0) { + newName = packageName + "." + getName(); + } else { + newName = getName(); + } + + classTypeElement.handleElementRename(newName); } @Nullable @Override public PsiDocComment getDocComment() { diff --git a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliClassStatement.java b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliClassStatement.java index 73cdf8de..65ccead6 100644 --- a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliClassStatement.java +++ b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliClassStatement.java @@ -35,6 +35,7 @@ import com.intellij.lang.ASTNode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jf.smalidea.psi.SmaliElementTypes; +import org.jf.smalidea.psi.leaf.SmaliClassDescriptor; import org.jf.smalidea.psi.stub.SmaliClassStatementStub; public class SmaliClassStatement extends SmaliStubBasedPsiElement { @@ -46,6 +47,20 @@ public class SmaliClassStatement extends SmaliStubBasedPsiElement contentSourceRoots = + JavaProjectRootsUtil.getSuitableDestinationSourceRoots(getProject()); + + new MoveClassesOrPackagesProcessor(getProject(), new PsiClass[] {testClass}, + new AutocreatingSingleSourceRootMoveDestination(new PackageWrapper(getPsiManager(), newPackage), + contentSourceRoots.get(0)), false, false, null).run(); + + /*new WriteCommandAction.Simple(getProject(), testClass.getContainingFile()) { + @Override protected void run() throws Throwable { + + + MoveClassesOrPackagesUtil.doMoveClass(testClass, newDirectory); + } + }.execute();*/ + } + +} diff --git a/smalidea/src/test/java/org/jf/smalidea/ClassRenameTest.java b/smalidea/src/test/java/org/jf/smalidea/ClassRenameTest.java new file mode 100644 index 00000000..d1c7365b --- /dev/null +++ b/smalidea/src/test/java/org/jf/smalidea/ClassRenameTest.java @@ -0,0 +1,51 @@ +package org.jf.smalidea; + +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.refactoring.MultiFileTestCase; +import com.intellij.refactoring.rename.RenameProcessor; +import org.jetbrains.annotations.NotNull; + +public class ClassRenameTest extends MultiFileTestCase { + @Override + protected String getTestDataPath() { + return "testData"; + } + + @NotNull + @Override + protected String getTestRoot() { + return "/classRename/"; + } + + public void testBasicNoPackage() { + doTest("blah", "blah2"); + } + + public void testBasicWithPackage() { + doTest("my.blah", "blah2"); + } + + private void doTest(@NotNull final String oldQualifiedName, @NotNull final String newName) { + doTest(new PerformAction() { + @Override + public void performAction(VirtualFile rootDir, VirtualFile rootAfter) throws Exception { + doRename(oldQualifiedName, newName); + } + }); + } + + private void doRename(String oldQualifiedName, String newName) throws Exception { + PsiClass testClass = myJavaFacade.findClass(oldQualifiedName, GlobalSearchScope.allScope(getProject())); + + RenameProcessor processor = new RenameProcessor(getProject(), testClass, newName, false, false); + processor.run(); + + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + FileDocumentManager.getInstance().saveAllDocuments(); + } + +} diff --git a/smalidea/testData/classMove/basicFromNoPackage/after/my/blah.smali b/smalidea/testData/classMove/basicFromNoPackage/after/my/blah.smali new file mode 100644 index 00000000..dc4522cc --- /dev/null +++ b/smalidea/testData/classMove/basicFromNoPackage/after/my/blah.smali @@ -0,0 +1,36 @@ +.class public Lmy/blah; +.super Lmy/blah; +.implements Lmy/blah; + +.annotation build Lmy/blah; + value = .subannotation Lmy/blah; + value = Lmy/blah; + .end subannotation +.end annotation + +.field static public blah:Lmy/blah; = Lmy/blah; + +.method public blah(Lmy/blah;)Lmy/blah; + .registers 2 + .local p0, "this":Lmy/blah; + + :start + iget-object v0, v0, Lmy/blah;->blah:Lmy/blah; + + invoke-virtual {v0}, Lmy/blah;->blah(Lmy/blah;)Lmy/blah; + + instance-of v0, v0, Lmy/blah; + check-cast v0, Lmy/blah; + new-instance v0, Lmy/blah; + const-class v0, Lmy/blah; + throw-verification-error generic-error, Lmy/blah; + + filled-new-array {v0, v0, v0, v0, v0}, Lmy/blah; + new-array v0, v0, Lmy/blah; + filled-new-array/range {v0}, Lmy/blah; + :end + + .catch Lmy/blah; { :start .. :end } :handler + :handler + return-void +.end method \ No newline at end of file diff --git a/smalidea/testData/classMove/basicFromNoPackage/before/blah.smali b/smalidea/testData/classMove/basicFromNoPackage/before/blah.smali new file mode 100644 index 00000000..0a72c715 --- /dev/null +++ b/smalidea/testData/classMove/basicFromNoPackage/before/blah.smali @@ -0,0 +1,36 @@ +.class public Lblah; +.super Lblah; +.implements Lblah; + +.annotation build Lblah; + value = .subannotation Lblah; + value = Lblah; + .end subannotation +.end annotation + +.field static public blah:Lblah; = Lblah; + +.method public blah(Lblah;)Lblah; + .registers 2 + .local p0, "this":Lblah; + + :start + iget-object v0, v0, Lblah;->blah:Lblah; + + invoke-virtual {v0}, Lblah;->blah(Lblah;)Lblah; + + instance-of v0, v0, Lblah; + check-cast v0, Lblah; + new-instance v0, Lblah; + const-class v0, Lblah; + throw-verification-error generic-error, Lblah; + + filled-new-array {v0, v0, v0, v0, v0}, Lblah; + new-array v0, v0, Lblah; + filled-new-array/range {v0}, Lblah; + :end + + .catch Lblah; { :start .. :end } :handler + :handler + return-void +.end method \ No newline at end of file diff --git a/smalidea/testData/classMove/basicToNoPackage/after/blah.smali b/smalidea/testData/classMove/basicToNoPackage/after/blah.smali new file mode 100644 index 00000000..0a72c715 --- /dev/null +++ b/smalidea/testData/classMove/basicToNoPackage/after/blah.smali @@ -0,0 +1,36 @@ +.class public Lblah; +.super Lblah; +.implements Lblah; + +.annotation build Lblah; + value = .subannotation Lblah; + value = Lblah; + .end subannotation +.end annotation + +.field static public blah:Lblah; = Lblah; + +.method public blah(Lblah;)Lblah; + .registers 2 + .local p0, "this":Lblah; + + :start + iget-object v0, v0, Lblah;->blah:Lblah; + + invoke-virtual {v0}, Lblah;->blah(Lblah;)Lblah; + + instance-of v0, v0, Lblah; + check-cast v0, Lblah; + new-instance v0, Lblah; + const-class v0, Lblah; + throw-verification-error generic-error, Lblah; + + filled-new-array {v0, v0, v0, v0, v0}, Lblah; + new-array v0, v0, Lblah; + filled-new-array/range {v0}, Lblah; + :end + + .catch Lblah; { :start .. :end } :handler + :handler + return-void +.end method \ No newline at end of file diff --git a/smalidea/testData/classMove/basicToNoPackage/before/my/blah.smali b/smalidea/testData/classMove/basicToNoPackage/before/my/blah.smali new file mode 100644 index 00000000..dc4522cc --- /dev/null +++ b/smalidea/testData/classMove/basicToNoPackage/before/my/blah.smali @@ -0,0 +1,36 @@ +.class public Lmy/blah; +.super Lmy/blah; +.implements Lmy/blah; + +.annotation build Lmy/blah; + value = .subannotation Lmy/blah; + value = Lmy/blah; + .end subannotation +.end annotation + +.field static public blah:Lmy/blah; = Lmy/blah; + +.method public blah(Lmy/blah;)Lmy/blah; + .registers 2 + .local p0, "this":Lmy/blah; + + :start + iget-object v0, v0, Lmy/blah;->blah:Lmy/blah; + + invoke-virtual {v0}, Lmy/blah;->blah(Lmy/blah;)Lmy/blah; + + instance-of v0, v0, Lmy/blah; + check-cast v0, Lmy/blah; + new-instance v0, Lmy/blah; + const-class v0, Lmy/blah; + throw-verification-error generic-error, Lmy/blah; + + filled-new-array {v0, v0, v0, v0, v0}, Lmy/blah; + new-array v0, v0, Lmy/blah; + filled-new-array/range {v0}, Lmy/blah; + :end + + .catch Lmy/blah; { :start .. :end } :handler + :handler + return-void +.end method \ No newline at end of file diff --git a/smalidea/testData/classRename/basicNoPackage/after/blah2.smali b/smalidea/testData/classRename/basicNoPackage/after/blah2.smali new file mode 100644 index 00000000..112b106e --- /dev/null +++ b/smalidea/testData/classRename/basicNoPackage/after/blah2.smali @@ -0,0 +1,36 @@ +.class public Lblah2; +.super Lblah2; +.implements Lblah2; + +.annotation build Lblah2; + value = .subannotation Lblah2; + value = Lblah2; + .end subannotation +.end annotation + +.field static public blah:Lblah2; = Lblah2; + +.method public blah(Lblah2;)Lblah2; + .registers 2 + .local p0, "this":Lblah2; + + :start + iget-object v0, v0, Lblah2;->blah:Lblah2; + + invoke-virtual {v0}, Lblah2;->blah(Lblah2;)Lblah2; + + instance-of v0, v0, Lblah2; + check-cast v0, Lblah2; + new-instance v0, Lblah2; + const-class v0, Lblah2; + throw-verification-error generic-error, Lblah2; + + filled-new-array {v0, v0, v0, v0, v0}, Lblah2; + new-array v0, v0, Lblah2; + filled-new-array/range {v0}, Lblah2; + :end + + .catch Lblah2; { :start .. :end } :handler + :handler + return-void +.end method \ No newline at end of file diff --git a/smalidea/testData/classRename/basicNoPackage/before/blah.smali b/smalidea/testData/classRename/basicNoPackage/before/blah.smali new file mode 100644 index 00000000..0a72c715 --- /dev/null +++ b/smalidea/testData/classRename/basicNoPackage/before/blah.smali @@ -0,0 +1,36 @@ +.class public Lblah; +.super Lblah; +.implements Lblah; + +.annotation build Lblah; + value = .subannotation Lblah; + value = Lblah; + .end subannotation +.end annotation + +.field static public blah:Lblah; = Lblah; + +.method public blah(Lblah;)Lblah; + .registers 2 + .local p0, "this":Lblah; + + :start + iget-object v0, v0, Lblah;->blah:Lblah; + + invoke-virtual {v0}, Lblah;->blah(Lblah;)Lblah; + + instance-of v0, v0, Lblah; + check-cast v0, Lblah; + new-instance v0, Lblah; + const-class v0, Lblah; + throw-verification-error generic-error, Lblah; + + filled-new-array {v0, v0, v0, v0, v0}, Lblah; + new-array v0, v0, Lblah; + filled-new-array/range {v0}, Lblah; + :end + + .catch Lblah; { :start .. :end } :handler + :handler + return-void +.end method \ No newline at end of file diff --git a/smalidea/testData/classRename/basicWithPackage/after/my/blah2.smali b/smalidea/testData/classRename/basicWithPackage/after/my/blah2.smali new file mode 100644 index 00000000..f08d5137 --- /dev/null +++ b/smalidea/testData/classRename/basicWithPackage/after/my/blah2.smali @@ -0,0 +1,36 @@ +.class public Lmy/blah2; +.super Lmy/blah2; +.implements Lmy/blah2; + +.annotation build Lmy/blah2; + value = .subannotation Lmy/blah2; + value = Lmy/blah2; + .end subannotation +.end annotation + +.field static public blah:Lmy/blah2; = Lmy/blah2; + +.method public blah(Lmy/blah2;)Lmy/blah2; + .registers 2 + .local p0, "this":Lmy/blah2; + + :start + iget-object v0, v0, Lmy/blah2;->blah:Lmy/blah2; + + invoke-virtual {v0}, Lmy/blah2;->blah(Lmy/blah2;)Lmy/blah2; + + instance-of v0, v0, Lmy/blah2; + check-cast v0, Lmy/blah2; + new-instance v0, Lmy/blah2; + const-class v0, Lmy/blah2; + throw-verification-error generic-error, Lmy/blah2; + + filled-new-array {v0, v0, v0, v0, v0}, Lmy/blah2; + new-array v0, v0, Lmy/blah2; + filled-new-array/range {v0}, Lmy/blah2; + :end + + .catch Lmy/blah2; { :start .. :end } :handler + :handler + return-void +.end method \ No newline at end of file diff --git a/smalidea/testData/classRename/basicWithPackage/before/my/blah.smali b/smalidea/testData/classRename/basicWithPackage/before/my/blah.smali new file mode 100644 index 00000000..dc4522cc --- /dev/null +++ b/smalidea/testData/classRename/basicWithPackage/before/my/blah.smali @@ -0,0 +1,36 @@ +.class public Lmy/blah; +.super Lmy/blah; +.implements Lmy/blah; + +.annotation build Lmy/blah; + value = .subannotation Lmy/blah; + value = Lmy/blah; + .end subannotation +.end annotation + +.field static public blah:Lmy/blah; = Lmy/blah; + +.method public blah(Lmy/blah;)Lmy/blah; + .registers 2 + .local p0, "this":Lmy/blah; + + :start + iget-object v0, v0, Lmy/blah;->blah:Lmy/blah; + + invoke-virtual {v0}, Lmy/blah;->blah(Lmy/blah;)Lmy/blah; + + instance-of v0, v0, Lmy/blah; + check-cast v0, Lmy/blah; + new-instance v0, Lmy/blah; + const-class v0, Lmy/blah; + throw-verification-error generic-error, Lmy/blah; + + filled-new-array {v0, v0, v0, v0, v0}, Lmy/blah; + new-array v0, v0, Lmy/blah; + filled-new-array/range {v0}, Lmy/blah; + :end + + .catch Lmy/blah; { :start .. :end } :handler + :handler + return-void +.end method \ No newline at end of file