What is or where is the internal classpath supplied to the groovyScript runtime in Live Templates?

I am attempting to utilize the groovyScript() functionality of live templates to generate copy constructors on the fly.

Generally my process to create a POJO for this project involves the following process.

1) Custom File Template - Named Processable, executes flawlessly.
2) Add attributes for class using some live template autocompletion or good ole typing.
3) Ctrl + N -> g s enter, select everything and use the builder setters.
4) cc TAB to generate my custom copy constructor, which iterates over all the methods of the class and calls every setter with the contents of a supplied object to be copied using its getters.

Then all I need to do is close the file, except 4 doesn't work all together.

EDIT: While it should probably be easily inferred, I am using Class.forName() for reflection to obtain the list of Methods via Class.getDeclaredMethods().  As alluded to below, I have also done some searching to find Groovy specific alternatives to using the java libraries, again to no avail.

The live template portion of the process works flawlessly, I get my constructors named, formatted and set up correctly.  The included groovyScript, when executed via the built in groovyConsole of the project executes flawlessly, returning the exact strings of expected code.  What doesn't work, is when I put them together.  I have tried all manner of uses to execute this, down to manually attempting to add the current folder to the classpath via URLClassLoader in java.  I have used all manner of groovy specific classes as well on the off chance that this issue is somehow groovy classloader not behaving well with the java classloader.  Nothing I do, seems to be able to get the parser to be able to find the class files which are within my project (contiuously built via maven).  I am now operating under the assumption that jvm which is executing this groovyScript is not project scope but likely IDE scoped, so my question is.. where the hell is this classpath and or how can I flag my packages in such a way that this classloader will be able to locate these class files.  It does not use PATH environment variable, as I have explicitly put the target/classes/ folder on the path to no avail.

While doing all manner of testing with the class loaders the exception I kept getting was ClassNotFoundException, since CNFE and NSCE are thrown in different circumstances.  I have rejected the thought that it is a permissions issue with the loader not being allowed to read the file as that just doesn't make sense, but I have not done any work to see if that is the cause.

I'm working on OSX - El Capitan (such a bad update).

Message was edited by: Original Poster

9 comments
Comment actions Permalink

Hi, sorry for the delay.

First of all, you might be interested in voting for https://youtrack.jetbrains.com/issue/IDEABKL-5546 and/or trying the plugin mentioned there: https://plugins.jetbrains.com/plugin?pr=idea&pluginId=7225 . Plugin action seems a better tool for copy constructor generation than live templates, so I'd advise to try that approach.

groovyScript uses the classpath of the IDE. But even if it included your project, that won't help you because you want to be operating on source, not class files, so reflection is useless. To achieve your goal your groovy script needs to access the internal IDE representation of the source code and iterate over it. Unfortunately the UI for that is extremely user-unfriendly. I can provide some code samples for you, if you still want to do that via live templates.

0
Comment actions Permalink

Peter, first of all thank you for the reply.

I do understand the thought that a more structured approach using the API might be very well be a more robust and extensible solution for this specific functionality, however the implementation is weighty, tightly coupled, and more complicated than necessary for this use case.  And I don't see much of a way to easily deliver different stylized copy calls necessary without coupling the API classes against my projects classes.  By that I mean, iterating over the declared methods of a class starting with "set" , checking the parameters to see which implement a given interface "Processable", and therefor including a set$ATTRIBUTENAME$(new $TYPE$(param.get$ATTRIBUTENAME$()));, where as final values can be easily included as set$ATTRIBUTENAME$(param.get$ATTRIBUTENAME$());.  The process is further complicated when you want to talk about deep copies of collections.  Java 8 syntax for this would resemble the following set$ATTRIBUTENAME$(param.get$ATTRIBUTENAME$().stream().map(p->new $TYPE$(p)).collect(Collectors.toList())); for a list or set, which is vastly different from the syntax for a map: set$ATTRIBUTENAME$(param.get$ATTRIBUTENAME$().entrySet().stream().collect(Collectors.toMap(e->e.getKey(), e-> e.getValue())));.  This syntax, specifically the creation of the deep copied object requires the same coupling to determine what format the setting will take.

Now, my usecase does not use much inherititance, so in a situation where the heirarchy is much longer than mine, it appears like the plugin approach would be a better fit.  However, I do not see a way to implement functionality that would not be tightly coupling my project files and structure to the IDE API, or that would be any better than simply hardcoding in a super(param); call at the first line of the string result, deleting it afterwords if the call is unecessary/non-existent.

Additionally, being aware of the classpath or which property contains the classpath for the internal groovyscript calls will provide other lightweight functionality potential that is more in-line with what I expect from a groovyScript environment.  The inclusion of groovyscript is extremely powerful for quick and dirty tools and solutions for problems right now.  Being able to provide or get access to a classLoader which can correctly locate and process requests on project modules delivers on the expectations of inclusion of groovyscript functionality.  It's far more light-weight and easy to write a script, with a single function and string response and let the editor framework manage the formatting on the result.

Additionally, I have seen similar advice given in this thread but I did not see a way to accomplish this without tight coupling.  Please advise if you are aware of some functionality in the API I am missing which would allow me to get this functionality in a plugin format, without binding my IDE to my project code.

As far as the plugin you linked, if you browse the comments of that plugin, you'll see I have already commented on it's use, and design issues.  In addition, I've already cloned the repository, and started working on a fix which would actually perform a true deep copy with java conventions, but I have stopped for the moment, because I have run into the issues outlined above.

The only real viable and flexible solution I can see is being able to execute reflection on project classes within groovyScript, and this requires getting a modified classpath to the engine executing the groovyScript.



Additional Note: I did add my target folder, and local maven repo to the IDE classpath and it still was not able to locate the class.  I have my project set up to build the files, and regardless during testing I had already built the file manually to verify it wasn't a race condition causing the exception.  If you are talking about iterating over the Psi components, let me tell you I am both ahead of you there, and in agreement that it is not user friendly.  Again, I ask if you're aware of a way to inject values into the classpath which will have an effect on the groovyscript engeine please tell, as setting the value in idea.vmoptions did not seem to have any effect.  Additionally, if I really can not access or modify the classpath so my classes will be in scope, is there a way to get my project source / classes in scope with some form of dependency injection so my IDE is not coupled to my source.

I'm aware I wrote a book, thanks in advance for your attention.

0
Comment actions Permalink

The IDE classpath is set in a nontrivial way in the starting scripts, e.g. idea.bat/idea.sh. You can append it using IDEA_CLASSPATH environment variable. Then this should be available in groovyScript. But please be aware that it'll increase the whole IDE's memory usage. Besides, our classloader has caching inside, so if the class file appears after IDE is fully loaded, it probably won't find it anyway (having cached a missing result).

You can also attempt to create yet another classloader inside the script that includes only the directories you need, and analyze them via reflection. But please note that for that approach to work, you need to have your class already compiled in that directory, which can be not true if you're changing it right now, especially with auto-compilation enabled.

Alternatively, you could try to work with PSI. You can pass qualifiedClassName() as the script parameter. Then you can get the PSI equivalent of the containing class via "JavaPsiFacade.getInstance(_editor.project).findClass(_1, GlobalSearchScope.allScope(_editor.project))" (import com.intellij.psi; import com.intellij.psi.search; necessary).That will give you a PsiClass instance, where you can iterate over getMethods()/getAllMethods() and do what you want.

BTW you might also want to vote for/comment on IDEA-146698 Rethink edit live template variables dialog.

0
Comment actions Permalink

Peter, thank you for the information on the classpath.  It is disheartening and unfortunate to learn that this feature is so closely tied to the IDE classpath, when its functionality is so closely tied to the active project and file.  Based on your information, I would also conclude it would be ill advised to add my project compilation files to the classpath which would overall result in slower performance for IDEA.

As to attempting to create another classloader within the GroovyScript, I can already attest that I have tried this and have had no success, as it is overall still limited by the scope of the IDE's classpath.  This was verified and tested by mvn install executed externally to IDEA, with the class files occurring directly on the classpath, as well as the jar files themselves.

Your statement about auto-compilation is a little perplexing, are you suggesting that auto-compilation does not compile the class every time it is saved?

As for your use of the PsiFacade while within the script, this might be an interesting stopgap to creating a weighty, and over-scoped plugin.  I’m assuming to utilize this, I would need to include the plugin SDK to the classpath for IDE startup, as it certainly isn’t finding my import * currently.

Alright, as I suspected it does work when I include all the jars for plugin development on the path to start up IntelliJ.  The Psi API is almost an equivalent replacement for the reflections api when it comes to working within IntelliJ and doesn’t require class files.  I have been able to get the full script working calling the appropriate getters and setters.  I will now expand to the additional goal of a full deep copy of all elements, and I will probably extend the Copy Constructor plugin to include this additional logic as well.  I started down the path of enhancing it anyway, but ran out of time with the holidays.


Thanks for your assistance Peter.  While this isn’t optimal, I’m not sure I entirely disagree with you that more imperative tasks like reflection might not belong within Live Templates.

0
Comment actions Permalink

No, the classes are not necessarily compiled every time they're saved. It depends on the compiler settings, presence of run/debug configurations and even when it happens, it can take much time and shouldn't be relied upon.

The IDE contains plugin SDK already, so there's no need to add anything to its classpath. What do you mean by saying it doesn't find the import? Which import is it? What do you see instead?

0
Comment actions Permalink

The following imports were not resolved within the groovy script until I included the required classes on my PATH variable explicitly in my .profile (OSX).

import com.intellij.openapi.editor.Editor
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope

I got around this by adding the additional imports from the plugin SDK configuration to the project SDK explicitly to my PATH.  It has added some undesired performance drops, but I am able to use the PSI classes from the groovyscript.  The exception being thrown was the classloader was unable to find import class.  I don't remember the exact wording, but since it was resolved by adding the PSI classes to the PATH, I assume it was not a misleading exception.  The big problem I have is bloating the system PATH with contents which should never be in there, the other potential fix would be to add it to my ~/Library/Preferences/IntelliJIdea14/idea.vmoptions file to not bloat the system path.  I will probably migrate this eventually, but I was spending way to much time on what should have been a quick and easy productivity tool.

That is interesting to know about the project class files, I'll keep that in mind.

0
Comment actions Permalink

That's strange. I've tried a live template with a groovyScript("import com.intellij.openapi.editor.Editor; Editor ed = _editor; 2") variable and it works correctly for me. Does it work for you without IDE classpath modifications?

Editor is a very core class and it must be present in the IDE, otherwise there will be no editors.

0
Comment actions Permalink

_editor is passed to the script by default, so I am sure it is available.  The other imports I am not so sure about, without the modifications to system or IDE classpath, I was not able to execute the following.

PsiClass psiClass = JavaPsiFacade.getInstance(editor.project).findClass(s1, GlobalSearchScope.allScope
(editor.project));
PsiMethod[] methods = psiClass.getAllMethods();

PsiType classType = JavaPsiFacade.getInstance(editor.project).getElementFactory().createType(psiClass);

It is also possible that their is still some issues present in the version I'm running in regards to Java8, as I am currently running the special 14.xxx build to support Java8.

I shouldn't have to do it, but I did and it fixed the problem.  In regards to other more pressing tasks, this has little consequence and isn't worth the time to find out the exact cause.

0
Comment actions Permalink

Tried the following (single-line) variable value:

groovyScript("import com.intellij.psi.*; import com.intellij.psi.search.*; PsiClass psiClass = JavaPsiFacade.getInstance(_editor.project).findClass(_1, GlobalSearchScope.allScope(_editor.project)); PsiMethod[] methods = psiClass.getAllMethods(); PsiType classType = JavaPsiFacade.getInstance(_editor.project).getElementFactory().createType(psiClass); classType", qualifiedClassName())

It works for me. Does it work for you without IDE classpath modifications? If not (and if you want to spend more time on this at all), could you please attach idea.log (Help | Show log) after it fails?

0

Please sign in to leave a comment.