How do I set an embedded Groovy scripts classpath? - groovy

I am trying to extend an Eclipse code builder (for generating DTOs from Hibernate VOs) - and it uses Groovy for its template system.
The code it uses to create the groovy Script is a little weird (not what I see in the Groovy docs) but it works, mostly:
GroovyShell shell = new GroovyShell();
script = shell.parse(source);
Then, later:
Binding binding = (bindings == null ? new Binding() : new Binding(bindings));
Script scriptInstance = InvokerHelper.createScript(script.getClass(), binding);
scriptInstance.setProperty("out", out);
scriptInstance.run();
out.flush();
Now, this works just fine, until it hits a reference to an object that is not directly in the project. In the script, it iterates through the properties of the Class that it is processing - when it does this, Groovy looks at all of the methods and when it can't find a Class definition for one of the method parameters, it craps out. In this case, it's dying when it finds any references to Hibernate, but I'm sure it will crap out with a lot more. It doesn't need to do anything to them, but it can't live without knowing what they are apparently.
Script doesn't appear to have a classloader that I can supply any classpath info, so I tried providing it to the GroovyShell - no difference.
What's the proper way to fix this so that the Groovy interpreter can find my projects referenced Jars?

I had this exact problem and solved it by creating my own URLClassLoader, and using reflection to call a protected method to add a new path to the ClassPath
// Specify the path you want to add
URL url = new URL("file://path/to/classes/here");
// Create a new class loader as a child of the default system class loader
ClassLoader loader = new URLClassLoader(System.getClass().getClassLoader());
// Get the AddURL method and call it
Method method = URLClassLoader.class.getDeclaredMethod("addURL",new Class[]{URL.class});
method.setAccessible(true);
method.invoke(loader,new Object[]{ url });
GroovyShell shell = new GroovyShell( loader );

The same as #James can be done without using reflection, loading all jar files from a certain folder:
URLClassLoader classLoader = new URLClassLoader( getExtraJarUrls(), getClass().getClassLoader() );
GroovyShell shell = new GroovyShell( classLoader, binding, compilerConfiguration );
private URL[] getExtraJarUrls() throws MalformedURLException
{
logger.debug( "Loading extra jars from {}", EXTRA_JARS_DIR.getAbsolutePath() );
URL[] result;
File[] files = EXTRA_JARS_DIR.listFiles( new JarFilenameFilter() );
if (files != null)
{
List<URL> urls = new ArrayList<URL>( files.length );
for (File file : files)
{
urls.add( file.toURI().toURL() );
}
result = urls.toArray( new URL[urls.size()] );
}
else
{
result = new URL[0];
}
logger.debug( "Adding URLs to classloader: {}", Arrays.toString( result ) );
return result;
}
private static class JarFilenameFilter implements FilenameFilter
{
public boolean accept( File dir, String name )
{
return name.endsWith( ".jar" );
}
}

I'm having the same problem trying to automate Gant scripts running. The solution I found is:
copy gant-starter.conf (or
groovy-starter.conf if it's just
groovy) from $GROOVY_HOME/conf to your
own dir;
add "load [directory]" or
"load [jar]" there, as described in
javadocs to
org.codehaus.groovy.tools.LoaderConfiguration,
found in Groovy source distribution;
before starting groovy set
groovy.starter.conf.override system
property to the name of that file,
like
-Dgroovy.starter.conf.override=[filename]

Related

Twig_Template::getSource() doesn't work until the template has been cached

I need to load and show a Twig template's source.
The template's getSource() method appears to work by using reflection to find its own class file, and reading the comment block at the end of it (which has the Twig code).
public function getSource()
{
$reflector = new ReflectionClass($this);
$file = $reflector->getFileName();
// ...
}
Unfortunately, that file is only available when the template is loaded from the file cache - before then, the class is defined at runtime and the ReflectionClass will return Environment.php(403) : eval()'d code as the class file.
if (!class_exists($cls, false)) {
$content = $this->compileSource($this->getLoader()->getSource($name), $name);
if ($this->bcWriteCacheFile) {
$this->writeCacheFile($key, $content);
} else {
$this->cache->write($key, $content);
}
eval('?>'.$content);
}
Is there any other way I can get the source from Twig, or is it only possible if I find and read the original .html.twig file directly?
Oops. The answer was right there in the code, of course:
$content = $this->compileSource($this->getLoader()->getSource($name), $name);
All I needed was to replace $environment->loadTemplate($name)->getSource() with $environment->getLoader()->getSource($name).

Groovy - Type check AST generated code

I have a Groovy application that can be custimized by a small Groovy DSL I wrote. On startup, the application loads several Groovy scripts, applies some AST transformations and finally executes whatever was specified in the scripts.
One of the AST transformations inserts a couple of lines of code into certain methods. That works fine and I can see the different behavior during runtime. However, sometimes the generated code is not correct. Although I load the scripts with the TypeChecked customizer in place, my generated code is never checked for soundness.
To show my problem, I constructed an extreme example. I have the following script:
int test = 10
println test // prints 10 when executed without AST
I load this script and insert a new line of code between the declaration of test and println:
public void visitBlockStatement(BlockStatement block) {
def assignment = (new AstBuilder().buildFromSpec {
binary {
variable "test"
token "="
constant 15
}
}).first()
def newStmt = new ExpressionStatement(assignment)
newStmt.setSourcePosition(block.statements[1])
block.statements.add(2, newStmt)
super.visitBlockStatement(block)
}
After applying this AST, the script prints 15. When I use AstNodeToScriptVisitor to print the Groovy code of the resulting script, I can see the new assignment added to the code.
However, if I change the value of the assignment to a String value:
// ...
def assignment = (new AstBuilder().buildFromSpec {
binary {
variable "test"
token "="
constant "some value"
}
}).first()
// ...
I get a GroovyCastExcpetion at runtime. Although the resulting script looks like this:
int test = 10
test = "some value" // no compile error but a GroovyCastException at runtime here. WHY?
println test
no error is raised by TypeChecked. I read in this mailing list, that you need to set the source position for generated code to be checked, but I'm doing that an it still doesn't work. Can anyone provide some feedback of what I am doing wrong? Thank you very much!
Update
I call the AST by attaching it to the GroovyShell like this:
def config = new CompilerConfiguration()
config.addCompilationCustomizers(
new ASTTransformationCustomizer(TypeChecked)
)
config.addCompilationCustomizers(
new ASTTransformationCustomizer(new AddAssignmentAST())
)
def shell = new GroovyShell(config)
shell.evaluate(new File("./path/to/file.groovy"))
The class for the AST itself looks like this:
#GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
class AddAssignmentAST implements ASTTransformation {
#Override
public void visit(ASTNode[] nodes, SourceUnit source) {
def transformer = new AddAssignmentTransformer()
source.getAST().getStatementBlock().visit(transformer)
}
private class AddAssignmentTransformer extends CodeVisitorSupport {
#Override
public void visitBlockStatement(BlockStatement block) {
// already posted above
}
}
}
Since my Groovy script only consists of one block (for this small example) the visitBlockStatement method is called exactly once, adds the assignment (which I can verify since the output changes) but does not ever throw a compile-time error.

Revit Api Load Command - Auto Reload

I'm working with the revit api, and one of its problems is that it locks the .dll once the command's run. You have to exit revit before the command can be rebuilt, very time consuming.
After some research, I came across this post on GitHub, that streams the command .dll into memory, thus hiding it from Revit. Letting you rebuild the VS project as much as you like.
The AutoReload Class impliments the revit IExteneralCommand Class which is the link into the Revit Program.
But the AutoReload class hides the actual source DLL from revit. So revit can't lock the DLL and lets one rebuilt the source file.
Only problem is I cant figure out how to implement it, and have revit execute the command. I guess my C# general knowledge is still too limited.
I created an entry in the RevitAddin.addin manifest that points to the AutoReload Method command, but nothing happens.
I've tried to follow all the comments in the posted code, but nothing seems to work; and no luck finding a contact for the developer.
Found at: https://gist.github.com/6084730.git
using System;
namespace Mine
{
// helper class
public class PluginData
{
public DateTime _creation_time;
public Autodesk.Revit.UI.IExternalCommand _instance;
public PluginData(Autodesk.Revit.UI.IExternalCommand instance)
{
_instance = instance;
}
}
//
// Base class for auto-reloading external commands that reside in other dll's
// (that Revit never knows about, and therefore cannot lock)
//
public class AutoReload : Autodesk.Revit.UI.IExternalCommand
{
// keep a static dictionary of loaded modules (so the data persists between calls to Execute)
static System.Collections.Generic.Dictionary<string, PluginData> _dictionary;
String _path; // to the dll
String _class_full_name;
public AutoReload(String path, String class_full_name)
{
if (_dictionary == null)
{
_dictionary = new System.Collections.Generic.Dictionary<string, PluginData>();
}
if (!_dictionary.ContainsKey(class_full_name))
{
PluginData data = new PluginData(null);
_dictionary.Add(class_full_name, data);
}
_path = path;
_class_full_name = class_full_name;
}
public Autodesk.Revit.UI.Result Execute(
Autodesk.Revit.UI.ExternalCommandData commandData,
ref string message,
Autodesk.Revit.DB.ElementSet elements)
{
PluginData data = _dictionary[_class_full_name];
DateTime creation_time = new System.IO.FileInfo(_path).LastWriteTime;
if (creation_time.CompareTo(data._creation_time) > 0)
{
// dll file has been modified, or this is the first time we execute this command.
data._creation_time = creation_time;
byte[] assembly_bytes = System.IO.File.ReadAllBytes(_path);
System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(assembly_bytes);
foreach (Type type in assembly.GetTypes())
{
if (type.IsClass && type.FullName == _class_full_name)
{
data._instance = Activator.CreateInstance(type) as Autodesk.Revit.UI.IExternalCommand;
break;
}
}
}
// now actually call the command
return data._instance.Execute(commandData, ref message, elements);
}
}
//
// Derive a class from AutoReload for every auto-reloadable command. Hardcode the path
// to the dll and the full name of the IExternalCommand class in the constructor of the base class.
//
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
[Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
public class AutoReloadExample : AutoReload
{
public AutoReloadExample()
: base("C:\\revit2014plugins\\ExampleCommand.dll", "Mine.ExampleCommand")
{
}
}
}
There is an easier approach: Add-in Manager
Go to Revit Developer Center and download the Revit SDK, unzip/install it, the check at \Revit 2016 SDK\Add-In Manager folder. With this tool you can load/reload DLLs without having to modify your code.
There is also some additional information at this blog post.
this is how you can use the above code:
Create a new VS class project; name it anything (eg. AutoLoad)
Copy&Paste the above code in-between the namespace region
reference revitapi.dll & revitapiui.dll
Scroll down to AutoReloadExample class and replace the path to point
your dll
Replace "Mine.ExampleCommand" with your plugins namespace.mainclass
Build the solution
Create an .addin manifest to point this new loader (eg.
AutoLoad.dll)
your .addin should include "FullClassName" AutoLoad.AutoReloadExample
This method uses reflection to create an instance of your plugin and prevent Revit to lock your dll file! You can add more of your commands just by adding new classes like AutoReloadExample and point them with seperate .addin files.
Cheers

Scope of Groovy's metaClass?

I have an application which can run scripts to automate certain tasks. I'd like to use meta programming in these scripts to optimize code size and readability. So instead of:
try {
def res = factory.allocate();
... do something with res ...
} finally {
res.close()
}
I'd like to
Factory.metaClass.withResource = { c ->
try {
def res = factory.allocate();
c(res)
} finally {
res.close()
}
}
so the script writers can write:
factory.withResource { res ->
... do something with res ...
}
(and I could do proper error handling, etc).
Now I wonder when and how I can/should implement this. I could attach the manipulation of the meta class in a header which I prepend to every script but I'm worried what would happen if two users ran the script at the same time (concurrent access to the meta class).
What is the scope of the meta class? The compiler? The script environment? The Java VM? The classloader which loaded Groovy?
My reasoning is that if Groovy meta classes have VM scope, then I could run a setup script once during startup.
Metaclasses exist per classloader [citation needed]:
File /tmp/Qwop.groovy:
class Qwop { }
File /tmp/Loader.groovy:
Qwop.metaClass.bar = { }
qwop1 = new Qwop()
assert qwop1.respondsTo('bar')
loader = new GroovyClassLoader()
clazz = loader.parseClass new File("/tmp/Qwop.groovy")
clazz.metaClass.brap = { 'oh my' }
qwop = clazz.newInstance()
assert !qwop.respondsTo('bar')
assert qwop1.respondsTo('bar')
assert qwop.brap() == "oh my"
assert !qwop1.respondsTo('brap')
// here be custom classloaders
new GroovyShell(loader).evaluate new File('/tmp/QwopTest.groovy')
And a script to test the scoped metaclass (/tmp/QwopTest.groovy):
assert !new Qwop().respondsTo('bar')
assert new Qwop().respondsTo('brap')
Execution:
$ groovy Loaders.groovy
$
If you have a set of well defined classes you could apply metaprogramming on top of the classes loaded by your classloader, as per the brap method added.
Another option for this sort of thing which is better for a lot of scenarios is to use an extension module.
package demo
class FactoryExtension {
static withResource(Factory instance, Closure c) {
def res
try {
res = instance.allocate()
c(res)
} finally {
res?.close()
}
}
}
Compile that and put it in a jar file which contains a file at META-INF/services/org.codehaus.groovy.runtime.ExtensionModule that contains something like this...
moduleName=factory-extension-module
moduleVersion=1.0
extensionClasses=demo.FactoryExtension
Then in order for someone to use your extension they just need to put that jar file on their CLASSPATH. With all of that in place, a user could do something like this...
factoryInstance.withResource { res ->
... do something with res ...
}
More information on extension modules is available at http://docs.groovy-lang.org/docs/groovy-2.3.6/html/documentation/#_extension_modules.

Parsing classes with GroovyShell

I have a groovy script that need to run a method inside a class inside an external groovy script.
I know how to run a method within an external groovy script:
new GroovyShell().parse( new File( 'foo.groovy' ) ).with {
method()
}
But what if the method is inside a class? I tried this but it gave me an error.
new GroovyShell().parse( new File( 'foo.groovy' ) ).with {
theclass.method()
}
You can use Java reflection to create new instance of a Class that is located in another script:
File sourceFile = new File("D:\\anoutherScript.groovy")
//here you have to update your classloader with external script
getClass().getClassLoader().addURL(sourceFile.toURI().toURL())
GroovyObject obj = Class.forName("ClassInAnotherObject").newInstance()
obj.doSth()
Script in your external file would be like that:
class ClassInAnotherObject{
def doSth(){
}
}
but there could be more classes in script file, also some more instructions and method call. Just like normal groovy script.

Resources