Add support for renaming/moving classes

This commit is contained in:
Ben Gruver 2015-03-28 12:50:36 -07:00
parent 50810d1f5b
commit 07e6ade7fe
17 changed files with 539 additions and 10 deletions

View File

@ -19,6 +19,7 @@
<syntaxHighlighter key="smali" implementationClass="org.jf.smalidea.SmaliHighlighter"/>
<colorSettingsPage implementation="org.jf.smalidea.SmaliColorsPage"/>
<lang.parserDefinition language="smali" implementationClass="org.jf.smalidea.SmaliParserDefinition"/>
<lang.ast.factory language="smali" implementationClass="org.jf.smalidea.SmaliASTFactory"/>
<java.elementFinder implementation="org.jf.smalidea.psi.index.SmaliClassFinder"/>
<stubIndex implementation="org.jf.smalidea.psi.index.SmaliClassNameIndex"/>
<debugger.positionManagerFactory implementation="org.jf.smalidea.debugging.SmaliPositionManagerFactory"/>

View File

@ -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);
}
}

View File

@ -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<SmaliClassStub> 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<SmaliClassStub> 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() {

View File

@ -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<SmaliClassStatementStub> {
@ -46,6 +47,20 @@ public class SmaliClassStatement extends SmaliStubBasedPsiElement<SmaliClassStat
super(node);
}
@Nullable
public SmaliClassTypeElement getNameElement() {
return findChildByClass(SmaliClassTypeElement.class);
}
@Nullable
public SmaliClassDescriptor getNameIdentifier() {
SmaliClassTypeElement classTypeElement = getNameElement();
if (classTypeElement == null) {
return null;
}
return classTypeElement.getReferenceNameElement();
}
/**
* @return the fully qualified java-style name of the class in this .class statement
*/

View File

@ -33,6 +33,7 @@ package org.jf.smalidea.psi.impl;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.util.IncorrectOperationException;
@ -40,6 +41,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jf.smalidea.psi.SmaliCompositeElementFactory;
import org.jf.smalidea.psi.SmaliElementTypes;
import org.jf.smalidea.psi.leaf.SmaliClassDescriptor;
import org.jf.smalidea.util.NameUtils;
public class SmaliClassTypeElement extends SmaliTypeElement implements PsiJavaCodeReferenceElement {
@ -102,12 +104,23 @@ public class SmaliClassTypeElement extends SmaliTypeElement implements PsiJavaCo
}
@Override public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
//TODO: implement this
throw new IncorrectOperationException();
SmaliClassDescriptor descriptor = getReferenceNameElement();
if (descriptor == null) {
throw new IncorrectOperationException();
}
SmaliClassDescriptor newDescriptor = new SmaliClassDescriptor(NameUtils.javaToSmaliType(newElementName));
CodeEditUtil.setNodeGenerated(newDescriptor, true);
this.replaceChild(descriptor, newDescriptor);
return this;
}
@Override public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
//TODO: implement this
if (element instanceof PsiClass) {
handleElementRename(((PsiClass) element).getQualifiedName());
return this;
}
throw new IncorrectOperationException();
}
@ -135,9 +148,8 @@ public class SmaliClassTypeElement extends SmaliTypeElement implements PsiJavaCo
throw new UnsupportedOperationException();
}
@Nullable @Override public PsiElement getReferenceNameElement() {
// TODO: implement if needed
throw new UnsupportedOperationException();
@Nullable @Override public SmaliClassDescriptor getReferenceNameElement() {
return findChildByClass(SmaliClassDescriptor.class);
}
@Nullable @Override public PsiReferenceParameterList getParameterList() {

View File

@ -85,6 +85,10 @@ public class SmaliFile extends PsiFileBase implements PsiClassOwner {
}
@Override public void setPackageName(String packageName) throws IncorrectOperationException {
// TODO: implement this
SmaliClass smaliClass = getPsiClass();
if (smaliClass == null) {
return;
}
smaliClass.setPackageName(packageName);
}
}

View File

@ -0,0 +1,17 @@
package org.jf.smalidea.psi.leaf;
import com.intellij.psi.PsiIdentifier;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.tree.IElementType;
import org.jf.smalidea.SmaliTokens;
public class SmaliClassDescriptor extends LeafPsiElement implements PsiIdentifier {
public SmaliClassDescriptor(CharSequence text) {
super(SmaliTokens.CLASS_DESCRIPTOR, text);
}
@Override
public IElementType getTokenType() {
return SmaliTokens.CLASS_DESCRIPTOR;
}
}

View File

@ -0,0 +1,68 @@
package org.jf.smalidea;
import com.intellij.openapi.roots.JavaProjectRootsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.refactoring.MultiFileTestCase;
import com.intellij.refactoring.PackageWrapper;
import com.intellij.refactoring.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination;
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesProcessor;
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class ClassMoveTest extends MultiFileTestCase {
@Override
protected String getTestDataPath() {
return "testData";
}
@NotNull
@Override
protected String getTestRoot() {
return "/classMove/";
}
public void testBasicFromNoPackage() {
doTest("blah", "my");
}
public void testBasicToNoPackage() {
doTest("my.blah", "");
}
private void doTest(@NotNull final String oldQualifiedName, @NotNull final String newPackage) {
doTest(new PerformAction() {
@Override
public void performAction(VirtualFile rootDir, VirtualFile rootAfter) throws Exception {
doMove(oldQualifiedName, newPackage);
}
});
}
private void doMove(String oldQualifiedName, final String newPackage) throws Exception {
final PsiClass testClass = myJavaFacade.findClass(oldQualifiedName, GlobalSearchScope.allScope(getProject()));
PsiDirectory newDirectory = MoveClassesOrPackagesUtil.chooseDestinationPackage(getProject(),
newPackage, testClass.getContainingFile().getContainingDirectory());
final List<VirtualFile> 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();*/
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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