Custom languages masquarding as a java source file within IntelliJ

I've tried a stub implementation and things didn't work the way that I had expected; so I suspect I am either totally wrong or missing something vital.

First a bit of background. I have a custom language that generates Java code, its usually a one to one mapping (ie one custom file generates one java file, but thats not going to always be the case). Currently in IDEA I get red lines in any Java code that refers to the generated code because IDEA does not know about the generated java files.. I can always point IDEA at the generated source directory but since the build regenerates them every run it sometimes causes IDEA to get out of sync, plus the goto support goes to the wrong file. So what I am trying to achieve is a seamless integration where IDEA knows about the custom language and treats it like a Java file.


I have the lexer and AST parser in place, and am now starting on the PSI bit. Am I correct in thinking that I need to implement the PsiJava* interfaces (eg PsiClass), and have the PsiFile return true for canContainJavaCode?

I briefly tried this with a skeleton implementation, but nothing seemed to happen. By that I mean ctrl-n did not detect the generated class and when I typed the name of the generated class in a Java file there was not 'would you like the import statement added for you' prompt. Sticking some debug code in I noticed that processDeclarations was never called even though getClasses was.

I also tried using ElementFactory.createClassFromText, passing in the generated java source to create a PsiClass and then returned that PsiClass from PsiJavaFile.getClasses but that had no effect.. infact the PsiFile was called _Dummy which made me think that the production of PsiClass was not successful (the code that I passed in was 'package com.foo; public class Account { public String getName() { return null;} }'.

Has anybody else done anything similar that I could take a look at?

Thanks,

- Chris.

3 comments
Comment actions Permalink

Chris,

Long ago the following approach was working OK. You can try it now.

public abstract class GeneratedPsiClass extends MockPsiClass {
private static final Logger LOG =
Logger.getInstance("jetbrains.fabrique.model.impl.psi.GeneratedPsiClass");

private boolean myGeneratingClassTextInProgress;

protected PsiClass myGeneratedClass;
private PsiMethod[] myMethods;
private PsiField[] myFields;
protected String myClassText;

public GeneratedPsiClass(PsiManager manager, String qualifiedName) {
super(manager, qualifiedName);
}

public boolean isInterface() {
return getGeneratedClass().isInterface();
}

public String getQualifiedName() {
return getGeneratedClass().getQualifiedName();
}

public PsiMethod[] getConstructors() {
return getGeneratedClass().getConstructors();
}

public PsiClass getSuperClass() {
return getGeneratedClass().getSuperClass();
}

public PsiTypeParameterList getTypeParameterList() {
return getGeneratedClass().getTypeParameterList();
}

public PsiClass getGeneratedClass() {
if (myGeneratedClass == null || !myGeneratedClass.isValid()) {

if (myGeneratingClassTextInProgress) {
return generateClass("public class fake_generated_psi_class{}");
}

myGeneratedClass = generateClass(getClassText());
}

return myGeneratedClass;
}

public String getText() {
return myClassText;
}

private String getClassText() {
if (myClassText != null) return myClassText;
myGeneratingClassTextInProgress = true;
myClassText = generateClassText();
myGeneratingClassTextInProgress = false;
return myClassText;
}

protected PsiClass generateClass(String classText) {
try {
PsiJavaFile psiFile = (PsiJavaFile)
getManager().getElementFactory().createFileFromText("dummy.java",
classText);
return psiFile.getClasses()[0];
} catch (IncorrectOperationException e) {
LOG.error(e);
assert false: "Error while creating java file: " + e.getMessage();
}

return null;
}

protected abstract String generateClassText();

public PsiReferenceList getExtendsList() {
return new MockReferenceList(getManager(), new PsiClass[0]);
}

public PsiReferenceList getImplementsList() {
return new MockReferenceList(getManager(),
getGeneratedClass().getSupers());
}

public PsiClass[] getInterfaces() {
return getGeneratedClass().getInterfaces();
}

public PsiModifierList getModifierList() {
return getGeneratedClass().getModifierList();
}

public PsiMethod[] getAllMethods() {
PsiMethod[] all = getGeneratedClass().getAllMethods();
for (int i = 0; i methodsList = new ArrayList(); PsiMethod[] methods = getGeneratedClass().getMethods(); for (PsiMethod method : methods) { methodsList.add(createMethodFromGenerated(method)); } myMethods = methodsList.toArray(new PsiMethod[methodsList.size()]); return myMethods; } protected MockPsiMethod createMethodFromGenerated(PsiMethod method) { return MockPsiMethod.createFromPsiMethod(this, method); } public PsiField[] getFields() { if (myFields != null) return myFields; List fieldList = new ArrayList]]>();
PsiField[] fields = getGeneratedClass().getFields();
for (PsiField field : fields) {
fieldList.add(MockPsiField.createFromField(this, field));
}

myFields = fieldList.toArray(new PsiField[fieldList.size()]);

return myFields;
}

public void cleanUpCaches() {
putUserData(PsiPropertyImpl.KEY_PROPERTIES, null);
myGeneratedClass = null;
myMethods = null;
myFields = null;
myClassText = null;
}

public PsiClass[] getSupers() {
return getGeneratedClass().getSupers();
}

/*
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof GeneratedPsiClass)) return false;

final GeneratedPsiClass generatedPsiClass = (GeneratedPsiClass) o;

if (isInModel() && generatedPsiClass.isInModel()) {
return getClassText().equals(generatedPsiClass.getClassText());
} else {
return super.equals(o);
}
}
*/

public String toString() {
return "GeneratedPsiClass:" + (myGeneratedClass != null ?
myGeneratedClass.toString() : myClassText);
}

protected abstract boolean isInModel();
}



/**

  • todo: add caches cleanup on roots change

*/
public class FabriquePsiElementFinder implements ProjectComponent,
PsiElementFinder {
private ModuleManager myModuleManager;
private CachedValue myModuleFindersValue;
private Project myProject;

public FabriquePsiElementFinder(ModuleManager moduleManager, Project
project) {
myModuleManager = moduleManager;
myProject = project;
}

public void projectOpened() { }

public void projectClosed() { }

public String getComponentName() {
return "FabriquePsiElementFinder";
}

public void initComponent() { }

public void disposeComponent() { }

public PsiClass findClass(String qualifiedName, GlobalSearchScope
scope) {
PsiElementFinder[] finders = getElementFinders();
for (int i = 0; i < finders.length; i++) {
PsiElementFinder finder = finders+;
PsiClass aClass = finder.findClass(qualifiedName, scope);
if (aClass != null) return aClass;
}

return null;
}

public PsiClass[] findClasses(String qualifiedName, GlobalSearchScope
scope) {
PsiElementFinder[] elementFinders = getElementFinders();
List result = new ArrayList();
for (int i = 0; i < elementFinders.length; i++) {
PsiElementFinder elementFinder = elementFinders+;

result.addAll(Arrays.asList(elementFinder.findClasses(qualifiedName,
scope)));
}

return (PsiClass[]) result.toArray(new PsiClass[result.size()]);
}

public PsiPackage findPackage(String qualifiedName) {
PsiElementFinder[] finders = getElementFinders();
for (int i = 0; i < finders.length; i++) {
PsiElementFinder finder = finders+;
PsiPackage aPackage = finder.findPackage(qualifiedName);
if (aPackage != null) return aPackage;
}

return null;
}

public PsiPackage[] getSubPackages(PsiPackage psiPackage,
GlobalSearchScope scope) {
List result = new ArrayList();

PsiElementFinder[] finders = getElementFinders();
for (int i = 0; i < finders.length; i++) {
PsiElementFinder finder = finders+;
PsiPackage[] subPackages = finder.getSubPackages(psiPackage, scope);
result.addAll(Arrays.asList(subPackages));
}

return (PsiPackage[]) result.toArray(new PsiPackage[result.size()]);
}

public PsiClass[] getClasses(PsiPackage psiPackage, GlobalSearchScope
scope) {
PsiElementFinder[] elementFinders = getElementFinders();
List result = new ArrayList();
for (int i = 0; i < elementFinders.length; i++) {
PsiElementFinder elementFinder = elementFinders+;
result.addAll(Arrays.asList(elementFinder.getClasses(psiPackage,
scope)));
}

return (PsiClass[]) result.toArray(new PsiClass[result.size()]);
}

private PsiElementFinder[] getElementFinders() {
if (myModuleFindersValue == null) {
myModuleFindersValue =
PsiManager.getInstance(myProject).getCachedValuesManager().createCachedValue(
new CachedValueProvider() {
public Result compute() {
return computeFinders();
}
}, false
);
}

return (PsiElementFinder[]) myModuleFindersValue.getValue();
}

private CachedValueProvider.Result computeFinders() {
List finders = new ArrayList();
Module[] modules = myModuleManager.getModules();
for (int i = 0; i < modules.length; i++) {
Module module = modules+;
Object[] components = module.getComponents(PsiElementFinder.class);
finders.addAll(Arrays.asList(components));
}

PsiElementFinder[] findersArray = (PsiElementFinder[])
finders.toArray(new PsiElementFinder[finders.size()]);

return new CachedValueProvider.Result(findersArray, new Object[]
{getModuleManagerModificationTracker()});
}

private Object getModuleManagerModificationTracker() {
return myProject.getComponent(ModuleManagerModificationTracker.class
);
}
}

0
Comment actions Permalink

Chris Kirk wrote:

First a bit of background. I have a custom language that generates
Java code, its usually a one to one mapping (ie one custom file
generates one java file, but thats not going to always be the case).
Currently in IDEA I get red lines in any Java code that refers to the
generated code because IDEA does not know about the generated java
files.. I can always point IDEA at the generated source directory but
since the build regenerates them every run it sometimes causes IDEA
to get out of sync, plus the goto support goes to the wrong file. So
what I am trying to achieve is a seamless integration where IDEA
knows about the custom language and treats it like a Java file.


Our solution to this problem is to add a pre-build task to ensure that
generated files are up to date. It does get out of sync but we make sure
to rebuild after making changes to generating file.

I would love if you found a solution. I hope you will keep this forum
posted with your findings.

0
Comment actions Permalink

Was this of any help? Do you need any comments on the code?

0

Please sign in to leave a comment.