Adding and removing fields from a PSIClass programatically through com.jetbrains.openapi
Answered
I have the following maven project pom.xml as given below, with an additional source folder in temp/src/main/java, under the basedir.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>p1</groupId>
<artifactId>p1</artifactId>
<name>p1</name>
<version>1.0</version>
<packaging>jar</packaging>
<build>
<finalName>p1</finalName>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>temp/src/main/java/</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
In the folder temp/src/main/java/org, the following files exist with the below contents
temp/src/main/java/org/Class1.java
package org;
public class Class1 {
public String b = "b";
}
temp/src/main/java/org/Class3.java
package org;
public class Class3 {
public String x = "x";
}
I have another file by name "changes.txt", which has the following contents
+Class1.a
-Class1.b
+Class2
+Class2.p
Class2.p,q
Class2,Class4
-Class3
+ at the beginning of a line indicates class/member to be created/added, - at the beginning of a line indicates class/member to be deleted & no +/- at the beginning of a line indicates rename refactoring of a class/member.
For the above file, the objective is to have the following done.
Add the line "public String a = "a"; as a member in Class1.java
Remove the line "public String b = "b"; from the file Class1.java
Create a new Class in the folder temp/src/main/java/org/Class2.java
Add the line "public String p = "p"; as a member in Class2.java
Rename Refactor member from p to q in Class2.java
Rename Refactor Class2 to Class4
Remove Class3
The final contents in "temp/src/main/java/org/" should be as below:
temp/src/main/java/org/Class1.java
package org;
public class Class1 {
public String a = "a";
}
temp/src/main/java/org/Class4.java
package org;
public class Class4 {
public String q = "q";
}
Please note that Class2.java & Class3.java should not exist in the folder temp/src/main/java/org/, after the AnActionEvent is trigerred.
I have a MyAction class which extends from (com.intellij.openapi.actionSystem.AnAction), with the member function @Override public void actionPerformed(AnActionEvent event). In the member function I iterate through each line in the changes.txt file. The rename of class/field i am able to do. But addition of class/fields & removal of class/fields I am facing issues. While trying to add a field to the PsiClass, it gives an error, "com.intellij.util.IncorrectOperationException: Must not change PSI outside command or undo-transparent action. "
Could you please send me the relevant code snippet to resolve this.
Please sign in to leave a comment.
The full error message points to the API that must be used here:
Must not change PSI outside command or undo-transparent action. See com.intellij.openapi.command.WriteCommandAction or com.intellij.openapi.command.CommandProcessor.
You can simply wrap your PSI modifications using `com.intellij.openapi.command.WriteCommandAction#writeCommandAction()`.
The following code worked like a charm. Thanks for the valuable information.
However I need the additional things to be done.
While adding a field to the class, I use the following snippet
final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiField psiField = elementFactory.createField(fieldName, PsiType.INT);
psiClass.add(psiField);
This adds a integer field as the statement "private int <fieldName>;"
However I want the field to be added in the format "public String <fieldName> = "<fieldName>";
Similarly I am not able to add a new Class.
Could you please share the relevant code snippet?
The addition, deletion & rename refactoring of fields is possible.
The deletion & rename refactoring of classes is possible.
--------------------------------------------------------------------------------------------
final String stmt = line;
WriteCommandAction writeCommandAction = new WriteCommandAction(project) {
@Override
protected void run(final Result result) throws Throwable {
String className = null;
String newClassName = null;
String fieldName = null;
String newFieldName = null;
if (!stmt.contains(".")) {
if ( (stmt.startsWith("+")) || (stmt.startsWith("-")) ) {
className = stmt.substring(1);
} else {
className = stmt.split(",")[0];
newClassName = stmt.split(",")[1];
}
} else {
if ( (stmt.startsWith("+")) || (stmt.startsWith("-")) ) {
className = stmt.substring(1).split("\\.")[0];
fieldName = stmt.substring(1).split("\\.")[1];
} else {
className = stmt.split("\\.")[0];
fieldName = stmt.split("\\.")[1].split(",")[0];
newFieldName = stmt.split("\\.")[1].split(",")[1];
}
}
VirtualFile vf = LocalFileSystem.getInstance().findFileByIoFile(new File(basePath + "/temp/src/main/java/com/" + className + ".java"));
PsiFile psiFile = PsiManager.getInstance(project).findFile(vf);
PsiJavaFile psiJavaFile = (PsiJavaFile) psiFile;
PsiClass psiClass = psiJavaFile.getClasses()[0];
if (fieldName == null) {
changeClass(psiClass, newClassName, stmt);
} else {
changeField(psiClass, fieldName, newFieldName, stmt);
}
}
private void changeClass(PsiClass psiClass, String newClassName, String stmt) {
if (stmt.startsWith("-")) {
psiClass.delete();
} else {
JavaRefactoringFactory javaRefactoringFactory = JavaRefactoringFactory.getInstance(project);
JavaRenameRefactoring javaRenameRefactoring = javaRefactoringFactory.createRename(psiClass, newClassName);
javaRenameRefactoring.setSearchInComments(false);
UsageInfo[] usages = javaRenameRefactoring.findUsages();
javaRenameRefactoring.doRefactoring(usages);
}
}
private void changeField(PsiClass psiClass, String fieldName, String newFieldName, String stmg) {
if (stmt.startsWith("+")) {
boolean found = false;
for (PsiField psiField : psiClass.getFields()) {
if (psiField.getName().equals(fieldName)) {
found = true;
break;
}
}
if (!found) {
final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiField psiField = elementFactory.createField(fieldName, PsiType.INT);
psiClass.add(psiField);
}
} else if (stmt.startsWith("-")) {
for (PsiField psiField : psiClass.getFields()) {
if (psiField.getName().equals(fieldName)) {
psiField.delete();
break;
}
}
} else {
for (PsiField psiField : psiClass.getFields()) {
if (psiField.getName().equals(fieldName)) {
JavaRefactoringFactory javaRefactoringFactory = JavaRefactoringFactory.getInstance(project);
JavaRenameRefactoring javaRenameRefactoring = javaRefactoringFactory.createRename(psiField, newFieldName);
javaRenameRefactoring.setSearchInComments(false);
UsageInfo[] usages = javaRenameRefactoring.findUsages();
javaRenameRefactoring.doRefactoring(usages);
break;
}
}
}
}
};
writeCommandAction.execute();
com.intellij.psi.PsiJavaParserFacade#createTypeFromText
usingcom.intellij.psi.CommonClassNames#JAVA_LANG_STRING
Is the above lines to resolve the issue of creating a field of type String.
Not able to resolve this.
Appreciate if you can give code snippets only for this.
Pass in return value from
createTypeFromText()
instead ofPsiType.INT
forcreateField()
.Thanks a ton...Made the appropriate changes as below and it works.
Now the only thing remaining is creation of a new class (say Class1), which extends from another class, with a constructor body and the physical file (Class1.java) being available in the folder \temp\src\main\java\, which is specified in the pom.xml as a source folder.
Let me know, what functions to be used.
Might be easier to just generate the full .java file as text and use
com.intellij.psi.PsiFileFactory#createFileFromText()
I find different signatures for createFileFromText in the PsiFileFactory
What are the parameters to be passed
& how is that the file will be stored in /temp/src/main/java/com
Which one of the below signature is to be applied?
Given that I have a certain "text" to be put in a file "Class1.java" at the location "/temp/src/main/java/com"
1. PsiFileFactory.createFileFromText(String,String)
2. PsiFileFactory.createFileFromText(String,FileType,CharSequence)
3. PsiFileFactory.createFileFromText(String,FileType,CharSequence,long,boolean)
4. PsiFileFactory.createFileFromText(String,FileType,CharSequence,long,boolean,boolean)
5. PsiFileFactory.createFileFromText(String,Language,CharSequence)
6. PsiFileFactory.createFileFromText(Language,CharSequence)
7. PsiFileFactory.createFileFromText(String,Language,CharSequence,boolean,boolean)
8. PsiFileFactory.createFileFromText(String,Language,CharSequence,boolean,boolean,boolean)
9. PsiFileFactory.createFileFromText(String,Language,CharSequence,boolean,boolean,boolean,VirtualFile)
10.PsiFileFactory.createFileFromText(FileType,String,CharSequence,int,int)
PsiFileFactory.createFileFromText(CharSequence,PsiFile)
Please see https://plugins.jetbrains.com/docs/intellij/psi-files.html#how-do-i-create-a-psi-file There are thousands of sample usages in IntelliJ Community sources.
I tried the below code. It gives an error
non-static method add(PsiElement) cannot be referenced from a static context
PsiDirectory.add(psiFile);
^
is is the only issue that needs resolution now....all other features of adding, rename & removing fields is successfully done.
Rename of classes & deletion of classes is successfuly happening. Only for addition I am stuck with this issue.
Anything else to be added in my code
You need to locate and provide that PsiDirectory instance, e.g. coming from VirtualFile https://plugins.jetbrains.com/docs/intellij/virtual-file.html and then locating it as described in https://plugins.jetbrains.com/docs/intellij/psi-files.html
The below code worked for me. Succesfully the class gets created in the folder "/temp/src/main/java/com/". The only thing required now is to add " extends net.DataRecord" and adding a constructor body
public <className> (String str) {
super(str);
}
What function needs to be called at the psiClass to implement this?
Everything worked finally successfully with the below code.