/* * Copyright 2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ratpack.groovy.script.internal; import groovy.lang.GroovyClassLoader; import groovy.lang.Script; import groovy.transform.CompileStatic; import groovy.transform.InheritConstructors; import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.classgen.GeneratorContext; import org.codehaus.groovy.classgen.Verifier; import org.codehaus.groovy.control.*; import org.codehaus.groovy.control.customizers.CompilationCustomizer; import org.codehaus.groovy.runtime.DefaultGroovyMethods; import java.nio.file.Path; import java.security.CodeSource; public class ScriptEngine<T extends Script> { private static final ClassNode LINE_NUMBER_CLASS_NODE = new ClassNode(LineNumber.class); private final boolean staticCompile; private final Class<T> scriptBaseClass; private ClassLoader parentLoader; public ScriptEngine(ClassLoader parentLoader, boolean staticCompile, Class<T> scriptBaseClass) { this.parentLoader = parentLoader; this.staticCompile = staticCompile; this.scriptBaseClass = scriptBaseClass; } public T create(String scriptName, Path scriptPath, String scriptText, Object... scriptConstructionArgs) throws IllegalAccessException, InstantiationException { Class<T> scriptClass = compile(scriptName, scriptPath, scriptText); return DefaultGroovyMethods.newInstance(scriptClass, scriptConstructionArgs); } @SuppressWarnings("unchecked") public Class<T> compile(String scriptName, String scriptText) throws IllegalAccessException, InstantiationException { return createClassLoader(null).parseClass(scriptText, scriptName); } @SuppressWarnings("unchecked") public Class<T> compile(String scriptName, Path scriptPath, String scriptText) throws IllegalAccessException, InstantiationException { return createClassLoader(scriptPath).parseClass(scriptText, scriptName); } private GroovyClassLoader createClassLoader(final Path scriptPath) { final CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); if (!scriptBaseClass.equals(Script.class)) { compilerConfiguration.setScriptBaseClass(scriptBaseClass.getName()); } compilerConfiguration.addCompilationCustomizers(new CompilationCustomizer(CompilePhase.CONVERSION) { @Override public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException { if (staticCompile) { classNode.addAnnotation(new AnnotationNode(new ClassNode(CompileStatic.class))); } classNode.addAnnotation(new AnnotationNode(new ClassNode(InheritConstructors.class))); if (scriptPath != null) { AnnotationNode scriptPathAnnotation = new AnnotationNode(new ClassNode(ScriptPath.class)); scriptPathAnnotation.addMember("value", new ConstantExpression(scriptPath.toUri().toString())); classNode.addAnnotation(scriptPathAnnotation); } } }); return new GroovyClassLoader(parentLoader, compilerConfiguration) { @Override protected CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource source) { return new CompilationUnit(config, source, this) { { verifier = new Verifier() { @Override public void visitClass(ClassNode node) { if (node.implementsInterface(ClassHelper.GENERATED_CLOSURE_Type)) { AnnotationNode lineNumberAnnotation = new AnnotationNode(LINE_NUMBER_CLASS_NODE); lineNumberAnnotation.addMember("value", new ConstantExpression(node.getLineNumber(), true)); node.addAnnotation(lineNumberAnnotation); } super.visitClass(node); } }; } }; } }; } }