Psi replace Generics types

I am fixing a plugin that does class transformation on given class. It copies class into new file and replaces all references
to self to the new self (newClass). How to replace references of class in Generics?

~~~
For example I have a class:

class Hello implements Comparable<Hello> {

}
~~~


Here is a piece of code that does this:

~~~
PsiClass newClass = (PsiClass)psiClass.copy();
javaFile.add(newClass);
newClass = facade.findClass(newClass.getName(),  GlobalSearchScope.fileScope(javaFile));
newClass.accept(new JavaRecursiveElementWalkingVisitor() {
   @Override
   public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
      PsiElement targetClass = reference.resolve();
      if(targetClass != null && targetClass.equals(psiClass)) {
          PsiJavaCodeReferenceElement newReference;
          if(reference instanceof PsiReferenceExpression) {
            if(newReferenceExpression == null)
              newReferenceExpression = psiElementFactory.createReferenceExpression(newClass);
            newReference = newReferenceExpression;
          } else {
            if(newClassReferenceElement == null)
              newClassReferenceElement = psiElementFactory.createClassReferenceElement(newClass);
            newReference = newClassReferenceElement;
          }
          reference.replace(newReference);
      }
   }
});
~~~


0
4 comments

So I see here 2 places where the code could be improved:

1. you can replace the first 3 lines with ```PsiClass newClass = (PsiClass)javaFile.add(psiClass.copy())```

2. you can call ```psiReference.bindToElement(newClass)``` instead of creating of references manually.

BTW, this works for top level classes, if you would need to cover inner classes as well, some adjustment would be required. This should process all references including references inside generics. One thing I would change additionally, I would collect all references to adjust during visiting the hierarchy and then will perform modifications to prevent unexpected invalidations.

Anna

0
Avatar
Permanently deleted user

Hi Anna,

thank you for your reply. Your optimizations are helpful.

But I still need to figure out how to change reference inside Generic.

Currently it works find for direct references. But for generics this equality does not work `targetClass.equals(psiClass)` since targetClass will be "Comparable<original.class.package.Hello>". Even If I apply psiReference.bindToElement(newClass) it will change it to Hello<original.class.package.Hello>.

Can visitor go deeper into generics? Or do I need to create a new class on the fly with substituted generics parameters and then do psiReference.bindToElement(newGenericsClass)?

 

0

I oversee the problem with your visitor, sorry: it should call for super. if you call ```super.visitReferenceElement(reference)```, visitor would drill inside generics.

BTW If you do PsiJavaCodeReferenceElement#resolve you should get a class (or package), see com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl.OurGenericsResolver#resolve. 

Anna

1
Avatar
Permanently deleted user

Thank you! super.visitReferenceElement solved my problem.

0

Please sign in to leave a comment.