How to add an annotation and import to a Java class

Answered

I'm trying to add the @Entity javax persistence annotation to a class. My code is as follows:

PsiJavaFile psiJavaFile = (PsiJavaFile) psiClass.getContainingFile();
PsiImportList psiImportList = psiJavaFile.getImportList();

PsiAnnotation entityAnnotation = elementFactory.createAnnotationFromText("@javax.persistence.Entity", null);
psiJavaFile.addAfter(entityAnnotation, psiImportList);

// shortenClassReferences takes the fully qualified class name used in the annotation and shortens it, adding the import
javaCodeStyleManager.shortenClassReferences(psiJavaFile);
codeStyleManager.reformat(psiClass);

The annotation appears in the class, after the import list, but it is still fully qualified, no import has been added for it. Any ideas what I have got wrong here? In addition, there is one line of white space between it and the start of the class identifier, what is the correct way to position it so I don't get this extra line gap?

Thanks for any help.

Hedley

11 comments
Comment actions Permalink
Official comment

You can use com.intellij.psi.PsiAnnotationOwner#addAnnotation via PsiClass and then call JavaCodeStyleManager.getInstance(project).shortenClassReferences(inserted); on the return value

Comment actions Permalink

Thanks for the advice. I have replaced my code with:

 

PsiModifierList classModifierList = psiClass.getModifierList();
PsiAnnotation entityAnnotation = classModifierList.addAnnotation("javax.persistence.Entity");
javaCodeStyleManager.shortenClassReferences(entityAnnotation);

When I run my unit test, the annotation is added at the correct place (no line of whitespace) but it still has a fully qualified class name. I've also tried calling shortenClassReferences on the psiJavaFile object, but that doesn't help either. Any ideas?

0
Comment actions Permalink

Please show the full method/context of this code.

0
Comment actions Permalink

Here is the section adding the annotation:

PsiClass psiClass = javaPsiFacade.findClass(className, GlobalSearchScope.projectScope(project));
log("Found class: " + psiClass);

// want to add @Entity annotation after import list, before class declaration
PsiJavaFile psiJavaFile = (PsiJavaFile) psiClass.getContainingFile();

PsiModifierList classModifierList = psiClass.getModifierList();

ReadonlyStatusHandler.getInstance(project).ensureFilesWritable();

WriteCommandAction.runWriteCommandAction(project, () -> {

PsiAnnotation entityAnnotation = classModifierList.addAnnotation("javax.persistence.Entity");

log("Reformatting file");
// shortenClassReferences takes the fully qualified class name used in the annotation and shortens it, adding the import
javaCodeStyleManager.shortenClassReferences(entityAnnotation);
javaCodeStyleManager.shortenClassReferences(psiJavaFile);
codeStyleManager.reformat(psiJavaFile);

});

The code is inside an Action. i.e. extends AnAction and called from the actionPerformed method. 

At the moment I'm running it from a test, which extends

LightCodeInsightFixtureTestCase

I then create a new instance of the action, and call:

myFixture.testAction(myActionInstance)

0
Comment actions Permalink

Is the annotation class "javax.persistence.Entity" made available in test (e.g. added as "fake" or library added to ProjectDescriptor)?

0
Comment actions Permalink

Ahh..this will be the step I'm missing. Looking at the IntelliJ notes here:

https://www.jetbrains.org/intellij/sdk/docs/basics/testing_plugins/light_and_heavy_tests.html

It looks like I need to override the method to return the project descriptor:

protected LightProjectDescriptor getProjectDescriptor() 

How do I add a library into the project descriptor?

Thanks.

0
Comment actions Permalink

Override com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor#configureModule and use PsiTestUtil#add(Project)Library

 

alternatively just add a (dummy version) of annotation via com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture#addClass

0
Comment actions Permalink

Hmm. Still can't get this working. I've written a project descriptor like this:

public class HbmProjectDescriptor extends DefaultLightProjectDescriptor {

@Override
public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) {
super.configureModule(module, model, contentEntry);
String libPath = "/Users/proctorh/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar";
File testLib = new File(libPath);
System.out.println("Does library exist? " + testLib.exists());
PsiTestUtil.addLibrary(module, libPath);

PsiTestUtil.addProjectLibrary(module, libPath);
}
}

And then in my test class use this like this:

public class HbmToAnnotationsTest extends LightCodeInsightFixtureTestCase {

@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return new HbmProjectDescriptor();
}

However the fully qualified annotation name is still not being shortened when I run the test. Am I missing something? Is there any debug checks / output I can put into the test to check it is being set up correctly? Thanks.

0
Comment actions Permalink

Apologies, the test is working correctly now with the above code. Many thanks for your help!

0
Comment actions Permalink

Actually, I should clarify. I didn't get this working by overriding the project descriptor. Even though I had print statements in the project descriptor showing that code was being invoked, it only worked when the code was in the test setup() method. 

i.e. test case extends LightCodeInsightFixtureTestCase and has the following:

@Override
protected void setUp() throws Exception {
super.setUp();

String libPath = "/Users/proctorh/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar";
File testLib = new File(libPath);
System.out.println("Does library exist? " + testLib.exists());
PsiTestUtil.addLibrary(myFixture.getModule(), libPath);

PsiTestUtil.addProjectLibrary(myFixture.getModule(), libPath);
}
0
Comment actions Permalink

That's most likely incorrect, because the added library can leak into unrelated light tests with the same descriptor. I'd suggest to investigate why adding it in the descriptor isn't working. I'd do it by putting breakpoints into the place where the libraries are added in your derscriptor, check whether they're really added (i.e. the root model contains them after the descriptor finishes). Then I'd also check if there are other ProjectRootListener#rootsChanged events after that that remove the library for any reason.

0

Please sign in to leave a comment.