org.grails.cli.compiler.GroovyCompiler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grace-shell Show documentation
Show all versions of grace-shell Show documentation
Grace Framework : Grace Shell
/*
* Copyright 2012-2023 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
*
* https://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 org.grails.cli.compiler;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import groovy.grape.GrapeEngine;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyClassLoader.ClassCollector;
import groovy.lang.GroovyCodeSource;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.Phases;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.ASTTransformationVisitor;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.ClassUtils;
import org.grails.cli.compiler.dependencies.GrailsDependenciesDependencyManagement;
import org.grails.cli.compiler.grape.DependencyResolutionContext;
import org.grails.cli.compiler.grape.GrapeEngineInstaller;
import org.grails.cli.compiler.grape.MavenResolverGrapeEngineFactory;
import org.grails.cli.util.ResourceUtils;
/**
* Compiler for Groovy sources. Primarily a simple Facade for
* {@link GroovyClassLoader#parseClass(GroovyCodeSource)} with the following additional
* features:
*
* - {@link CompilerAutoConfiguration} strategies will be read from
* {@code META-INF/services/org.grails.cli.compiler.CompilerAutoConfiguration}
* (per the standard java {@link ServiceLoader} contract) and applied during compilation
*
*
* - Multiple classes can be returned if the Groovy source defines more than one Class
*
*
* - Generated class files can also be loaded using
* {@link ClassLoader#getResource(String)}
*
*
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
* @author Michael Yan
* @since 2022.1.0
*/
public class GroovyCompiler {
private final GroovyCompilerConfiguration configuration;
private final ExtendedGroovyClassLoader loader;
private final Iterable compilerAutoConfigurations;
private final List transformations;
/**
* Create a new {@link GroovyCompiler} instance.
*
* @param configuration the compiler configuration
*/
public GroovyCompiler(GroovyCompilerConfiguration configuration) {
this.configuration = configuration;
this.loader = createLoader(configuration);
DependencyResolutionContext resolutionContext = new DependencyResolutionContext();
resolutionContext.addDependencyManagement(new GrailsDependenciesDependencyManagement());
GrapeEngine grapeEngine = MavenResolverGrapeEngineFactory.create(this.loader,
configuration.getRepositoryConfiguration(), resolutionContext, configuration.isQuiet());
GrapeEngineInstaller.install(grapeEngine);
this.loader.getConfiguration().addCompilationCustomizers(new CompilerAutoConfigureCustomizer());
if (configuration.isAutoconfigure()) {
this.compilerAutoConfigurations = ServiceLoader.load(CompilerAutoConfiguration.class);
}
else {
this.compilerAutoConfigurations = Collections.emptySet();
}
this.transformations = new ArrayList<>();
this.transformations.add(new DependencyManagementBomTransformation(resolutionContext));
this.transformations.add(new DependencyAutoConfigurationTransformation(this.loader, resolutionContext,
this.compilerAutoConfigurations));
this.transformations.add(new GroovyBeansTransformation());
if (this.configuration.isGuessDependencies()) {
this.transformations.add(new ResolveDependencyCoordinatesTransformation(resolutionContext));
}
for (ASTTransformation transformation : ServiceLoader.load(SpringBootAstTransformation.class)) {
this.transformations.add(transformation);
}
this.transformations.sort(AnnotationAwareOrderComparator.INSTANCE);
}
/**
* Return a mutable list of the {@link ASTTransformation}s to be applied during
* {@link #compile(String...)}.
*
* @return the AST transformations to apply
*/
public List getAstTransformations() {
return this.transformations;
}
public ExtendedGroovyClassLoader getLoader() {
return this.loader;
}
private ExtendedGroovyClassLoader createLoader(GroovyCompilerConfiguration configuration) {
ExtendedGroovyClassLoader loader = new ExtendedGroovyClassLoader(configuration.getScope());
for (URL url : getExistingUrls()) {
loader.addURL(url);
}
for (String classpath : configuration.getClasspath()) {
loader.addClasspath(classpath);
}
return loader;
}
private URL[] getExistingUrls() {
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
if (tccl instanceof ExtendedGroovyClassLoader) {
return ((ExtendedGroovyClassLoader) tccl).getURLs();
}
else {
return new URL[0];
}
}
public void addCompilationCustomizers(CompilationCustomizer... customizers) {
this.loader.getConfiguration().addCompilationCustomizers(customizers);
}
/**
* Compile the specified Groovy sources, applying any
* {@link CompilerAutoConfiguration}s. All classes defined in the sources will be
* returned from this method.
*
* @param sources the sources to compile
* @return compiled classes
* @throws CompilationFailedException in case of compilation failures
* @throws IOException in case of I/O errors
* @throws CompilationFailedException in case of compilation errors
*/
public Class>[] compile(String... sources) throws CompilationFailedException, IOException {
this.loader.clearCache();
List> classes = new ArrayList<>();
CompilerConfiguration configuration = this.loader.getConfiguration();
CompilationUnit compilationUnit = new CompilationUnit(configuration, null, this.loader);
ClassCollector collector = this.loader.createCollector(compilationUnit, null);
compilationUnit.setClassgenCallback(collector);
for (String source : sources) {
List paths = ResourceUtils.getUrls(source, this.loader);
for (String path : paths) {
compilationUnit.addSource(new URL(path));
}
}
addAstTransformations(compilationUnit);
compilationUnit.compile(Phases.CLASS_GENERATION);
for (Object loadedClass : collector.getLoadedClasses()) {
classes.add((Class>) loadedClass);
}
ClassNode mainClassNode = MainClass.get(compilationUnit);
Class> mainClass = null;
for (Class> loadedClass : classes) {
if (mainClassNode.getName().equals(loadedClass.getName())) {
mainClass = loadedClass;
}
}
if (mainClass != null) {
classes.remove(mainClass);
classes.add(0, mainClass);
}
return ClassUtils.toClassArray(classes);
}
@SuppressWarnings("rawtypes")
private void addAstTransformations(CompilationUnit compilationUnit) {
Deque[] phaseOperations = getPhaseOperations(compilationUnit);
processConversionOperations((LinkedList) phaseOperations[Phases.CONVERSION]);
}
@SuppressWarnings("rawtypes")
private Deque[] getPhaseOperations(CompilationUnit compilationUnit) {
try {
Field field = CompilationUnit.class.getDeclaredField("phaseOperations");
field.setAccessible(true);
return (Deque[]) field.get(compilationUnit);
}
catch (Exception ex) {
throw new IllegalStateException("Phase operations not available from compilation unit");
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void processConversionOperations(LinkedList conversionOperations) {
int index = getIndexOfASTTransformationVisitor(conversionOperations);
conversionOperations.add(index, new CompilationUnit.ISourceUnitOperation() {
@Override
public void call(SourceUnit source) throws CompilationFailedException {
ASTNode[] nodes = new ASTNode[] { source.getAST() };
for (ASTTransformation transformation : GroovyCompiler.this.transformations) {
transformation.visit(nodes, source);
}
}
});
}
private int getIndexOfASTTransformationVisitor(List> conversionOperations) {
for (int index = 0; index < conversionOperations.size(); index++) {
if (conversionOperations.get(index)
.getClass()
.getName()
.startsWith(ASTTransformationVisitor.class.getName())) {
return index;
}
}
return conversionOperations.size();
}
/**
* {@link CompilationCustomizer} to call {@link CompilerAutoConfiguration}s.
*/
private class CompilerAutoConfigureCustomizer extends CompilationCustomizer {
CompilerAutoConfigureCustomizer() {
super(CompilePhase.CONVERSION);
}
@Override
public void call(SourceUnit source, GeneratorContext context, ClassNode classNode)
throws CompilationFailedException {
ImportCustomizer importCustomizer = new SmartImportCustomizer(source);
List classNodes = source.getAST().getClasses();
ClassNode mainClassNode = MainClass.get(classNodes);
// Additional auto configuration
for (CompilerAutoConfiguration autoConfiguration : GroovyCompiler.this.compilerAutoConfigurations) {
if (classNodes.stream().anyMatch(autoConfiguration::matches)) {
if (GroovyCompiler.this.configuration.isGuessImports()) {
autoConfiguration.applyImports(importCustomizer);
importCustomizer.call(source, context, classNode);
}
if (classNode.equals(mainClassNode)) {
autoConfiguration.applyToMainClass(GroovyCompiler.this.loader,
GroovyCompiler.this.configuration, context, source, classNode);
}
autoConfiguration.apply(GroovyCompiler.this.loader, GroovyCompiler.this.configuration, context,
source, classNode);
}
}
importCustomizer.call(source, context, classNode);
}
}
private static class MainClass {
static ClassNode get(CompilationUnit source) {
return get(source.getAST().getClasses());
}
static ClassNode get(List classes) {
for (ClassNode node : classes) {
if (AstUtils.hasAtLeastOneAnnotation(node, "Enable*AutoConfiguration")) {
return null; // No need to enhance this
}
if (AstUtils.hasAtLeastOneAnnotation(node, "*Controller", "Configuration", "Component", "*Service",
"Repository", "Enable*")) {
return node;
}
}
return classes.isEmpty() ? null : classes.get(0);
}
}
}