Extract inner-classes from GroovyCompiler

Extract and refactor inner-classes from the GroovyCompiler to improve
code readability.
This commit is contained in:
Phillip Webb 2013-10-29 00:02:04 -07:00
parent 8d186945e7
commit a6a1929274
4 changed files with 279 additions and 189 deletions

View File

@ -0,0 +1,73 @@
/*
* Copyright 2012-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 org.springframework.boot.cli.compiler;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
/**
* {@link ASTTransformation} to apply
* {@link CompilerAutoConfiguration#applyDependencies(DependencyCustomizer) dependency
* auto-configuration}.
*
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
*/
class DependencyAutoConfigurationTransformation implements ASTTransformation {
private final GroovyClassLoader loader;
private final ArtifactCoordinatesResolver coordinatesResolver;
private final Iterable<CompilerAutoConfiguration> compilerAutoConfigurations;
DependencyAutoConfigurationTransformation(GroovyClassLoader loader,
ArtifactCoordinatesResolver coordinatesResolver,
Iterable<CompilerAutoConfiguration> compilerAutoConfigurations) {
this.loader = loader;
this.coordinatesResolver = coordinatesResolver;
this.compilerAutoConfigurations = compilerAutoConfigurations;
}
@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
for (ASTNode astNode : nodes) {
if (astNode instanceof ModuleNode) {
visitModule((ModuleNode) astNode);
}
}
}
private void visitModule(ModuleNode module) {
DependencyCustomizer dependencies = new DependencyCustomizer(this.loader, module,
this.coordinatesResolver);
for (ClassNode classNode : module.getClasses()) {
for (CompilerAutoConfiguration autoConfiguration : this.compilerAutoConfigurations) {
if (autoConfiguration.matches(classNode)) {
autoConfiguration.applyDependencies(dependencies);
}
}
}
}
}

View File

@ -41,17 +41,17 @@ public class DependencyCustomizer {
private final ClassNode classNode;
private final ArtifactCoordinatesResolver artifactCoordinatesResolver;
private final ArtifactCoordinatesResolver coordinatesResolver;
/**
* Create a new {@link DependencyCustomizer} instance.
* @param loader
*/
public DependencyCustomizer(GroovyClassLoader loader, ModuleNode moduleNode,
ArtifactCoordinatesResolver artifactCoordinatesResolver) {
ArtifactCoordinatesResolver coordinatesResolver) {
this.loader = loader;
this.classNode = moduleNode.getClasses().get(0);
this.artifactCoordinatesResolver = artifactCoordinatesResolver;
this.coordinatesResolver = coordinatesResolver;
}
/**
@ -61,7 +61,7 @@ public class DependencyCustomizer {
protected DependencyCustomizer(DependencyCustomizer parent) {
this.loader = parent.loader;
this.classNode = parent.classNode;
this.artifactCoordinatesResolver = parent.artifactCoordinatesResolver;
this.coordinatesResolver = parent.coordinatesResolver;
}
public String getVersion(String artifactId) {
@ -70,7 +70,7 @@ public class DependencyCustomizer {
}
public String getVersion(String artifactId, String defaultVersion) {
String version = this.artifactCoordinatesResolver.getVersion(artifactId);
String version = this.coordinatesResolver.getVersion(artifactId);
if (version == null) {
version = defaultVersion;
}
@ -184,8 +184,8 @@ public class DependencyCustomizer {
* @return this {@link DependencyCustomizer} for continued use
*/
public DependencyCustomizer add(String module) {
return this.add(this.artifactCoordinatesResolver.getGroupId(module), module,
this.artifactCoordinatesResolver.getVersion(module), true);
return this.add(this.coordinatesResolver.getGroupId(module), module,
this.coordinatesResolver.getVersion(module), true);
}
/**
@ -198,8 +198,8 @@ public class DependencyCustomizer {
* @return this {@link DependencyCustomizer} for continued use
*/
public DependencyCustomizer add(String module, boolean transitive) {
return this.add(this.artifactCoordinatesResolver.getGroupId(module), module,
this.artifactCoordinatesResolver.getVersion(module), transitive);
return this.add(this.coordinatesResolver.getGroupId(module), module,
this.coordinatesResolver.getVersion(module), transitive);
}
private DependencyCustomizer add(String group, String module, String version,

View File

@ -17,7 +17,6 @@
package org.springframework.boot.cli.compiler;
import groovy.grape.GrapeEngine;
import groovy.lang.Grab;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyClassLoader.ClassCollector;
@ -34,14 +33,7 @@ import java.util.List;
import java.util.ServiceLoader;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilationUnit;
@ -66,6 +58,7 @@ import org.codehaus.groovy.transform.ASTTransformationVisitor;
*
* <li>Generated class files can also be loaded using
* {@link ClassLoader#getResource(String)}</li>
*
* <ul>
*
* @author Phillip Webb
@ -77,32 +70,45 @@ public class GroovyCompiler {
private static final ClassLoader AETHER_CLASS_LOADER = new URLClassLoader(
new URL[] { GroovyCompiler.class.getResource("/internal/") });
private GroovyCompilerConfiguration configuration;
private final GroovyCompilerConfiguration configuration;
private ExtendedGroovyClassLoader loader;
private final ExtendedGroovyClassLoader loader;
private ArtifactCoordinatesResolver artifactCoordinatesResolver;
private final ArtifactCoordinatesResolver coordinatesResolver;
private final ASTTransformation dependencyCustomizerTransformation = new DependencyCustomizerAstTransformation();
private final ServiceLoader<CompilerAutoConfiguration> compilerAutoConfigurations;
private final ASTTransformation dependencyCoordinatesTransformation = new DefaultDependencyCoordinatesAstTransformation();
private final List<ASTTransformation> transformations;
/**
* Create a new {@link GroovyCompiler} instance.
* @param configuration the compiler configuration
*/
@SuppressWarnings("unchecked")
public GroovyCompiler(final GroovyCompilerConfiguration configuration) {
this.configuration = configuration;
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
this.loader = new ExtendedGroovyClassLoader(getClass().getClassLoader(),
compilerConfiguration);
new CompilerConfiguration());
if (configuration.getClasspath().length() > 0) {
this.loader.addClasspath(configuration.getClasspath());
}
this.artifactCoordinatesResolver = new PropertiesArtifactCoordinatesResolver(
this.loader);
this.coordinatesResolver = new PropertiesArtifactCoordinatesResolver(this.loader);
installGrapeEngine();
this.loader.getConfiguration().addCompilationCustomizers(
new CompilerAutoConfigureCustomizer());
this.compilerAutoConfigurations = ServiceLoader.load(
CompilerAutoConfiguration.class, GroovyCompiler.class.getClassLoader());
this.transformations = new ArrayList<ASTTransformation>();
this.transformations.add(new DependencyAutoConfigurationTransformation(this.loader,
this.coordinatesResolver, this.compilerAutoConfigurations));
if (this.configuration.isGuessDependencies()) {
this.transformations.add(new ResolveDependencyCoordinatesTransformation(
this.coordinatesResolver));
}
}
@SuppressWarnings("unchecked")
private void installGrapeEngine() {
try {
Class<GrapeEngine> grapeEngineClass = (Class<GrapeEngine>) AETHER_CLASS_LOADER
.loadClass("org.springframework.boot.cli.compiler.AetherGrapeEngine");
@ -110,16 +116,13 @@ public class GroovyCompiler {
GroovyClassLoader.class, String.class, String.class, String.class);
GrapeEngine grapeEngine = constructor.newInstance(this.loader,
"org.springframework.boot", "spring-boot-starter-parent",
this.artifactCoordinatesResolver.getVersion("spring-boot"));
this.coordinatesResolver.getVersion("spring-boot"));
new GrapeEngineInstaller(grapeEngine).install();
}
catch (Exception ex) {
throw new IllegalStateException("Failed to install custom GrapeEngine", ex);
}
compilerConfiguration
.addCompilationCustomizers(new CompilerAutoConfigureCustomizer());
}
public void addCompilationCustomizers(CompilationCustomizer... customizers) {
@ -157,12 +160,12 @@ public class GroovyCompiler {
this.loader.clearCache();
List<Class<?>> classes = new ArrayList<Class<?>>();
CompilerConfiguration compilerConfiguration = this.loader.getConfiguration();
CompilerConfiguration configuration = this.loader.getConfiguration();
final CompilationUnit compilationUnit = new CompilationUnit(
compilerConfiguration, null, this.loader);
SourceUnit sourceUnit = new SourceUnit(file[0], compilerConfiguration,
this.loader, compilationUnit.getErrorCollector());
CompilationUnit compilationUnit = new CompilationUnit(configuration, null,
this.loader);
SourceUnit sourceUnit = new SourceUnit(file[0], configuration, this.loader,
compilationUnit.getErrorCollector());
ClassCollector collector = this.loader.createCollector(compilationUnit,
sourceUnit);
compilationUnit.setClassgenCallback(collector);
@ -189,18 +192,23 @@ public class GroovyCompiler {
}
return classes.toArray(new Class<?>[classes.size()]);
}
@SuppressWarnings("rawtypes")
private void addAstTransformations(final CompilationUnit compilationUnit) {
private void addAstTransformations(CompilationUnit compilationUnit) {
LinkedList[] phaseOperations = getPhaseOperations(compilationUnit);
processConversionOperations(phaseOperations[Phases.CONVERSION]);
}
@SuppressWarnings("rawtypes")
private LinkedList[] getPhaseOperations(final CompilationUnit compilationUnit) {
try {
Field field = CompilationUnit.class.getDeclaredField("phaseOperations");
field.setAccessible(true);
LinkedList[] phaseOperations = (LinkedList[]) field.get(compilationUnit);
processConversionOperations(phaseOperations[Phases.CONVERSION]);
return phaseOperations;
}
catch (Exception npe) {
catch (Exception ex) {
throw new IllegalStateException(
"Phase operations not available from compilation unit");
}
@ -208,24 +216,26 @@ public class GroovyCompiler {
@SuppressWarnings({ "rawtypes", "unchecked" })
private void processConversionOperations(LinkedList conversionOperations) {
for (int i = 0; i < conversionOperations.size(); i++) {
Object operation = conversionOperations.get(i);
int index = getIndexOfASTTransformationVisitor(conversionOperations);
conversionOperations.add(index, new CompilationUnit.SourceUnitOperation() {
@Override
public void call(SourceUnit source) throws CompilationFailedException {
ASTNode[] nodes = new ASTNode[] { source.getAST() };
for (ASTTransformation transformation : GroovyCompiler.this.transformations) {
transformation.visit(nodes, source);
}
}
});
}
if (operation.getClass().getName()
private int getIndexOfASTTransformationVisitor(LinkedList<?> conversionOperations) {
for (int index = 0; index < conversionOperations.size(); index++) {
if (conversionOperations.get(index).getClass().getName()
.startsWith(ASTTransformationVisitor.class.getName())) {
conversionOperations.add(i, new CompilationUnit.SourceUnitOperation() {
@Override
public void call(SourceUnit source) throws CompilationFailedException {
ASTNode[] astNodes = new ASTNode[] { source.getAST() };
GroovyCompiler.this.dependencyCustomizerTransformation.visit(
astNodes, source);
GroovyCompiler.this.dependencyCoordinatesTransformation.visit(
astNodes, source);
}
});
break;
return index;
}
}
return conversionOperations.size();
}
/**
@ -243,12 +253,8 @@ public class GroovyCompiler {
ImportCustomizer importCustomizer = new ImportCustomizer();
ServiceLoader<CompilerAutoConfiguration> customizers = ServiceLoader.load(
CompilerAutoConfiguration.class,
GroovyCompiler.class.getClassLoader());
// Additional auto configuration
for (CompilerAutoConfiguration autoConfiguration : customizers) {
for (CompilerAutoConfiguration autoConfiguration : GroovyCompiler.this.compilerAutoConfigurations) {
if (autoConfiguration.matches(classNode)) {
if (GroovyCompiler.this.configuration.isGuessImports()) {
autoConfiguration.applyImports(importCustomizer);
@ -270,134 +276,4 @@ public class GroovyCompiler {
}
}
private class DependencyCustomizerAstTransformation implements ASTTransformation {
@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
ServiceLoader<CompilerAutoConfiguration> customizers = ServiceLoader.load(
CompilerAutoConfiguration.class,
GroovyCompiler.class.getClassLoader());
for (ASTNode astNode : nodes) {
if (astNode instanceof ModuleNode) {
ModuleNode module = (ModuleNode) astNode;
DependencyCustomizer dependencyCustomizer = new DependencyCustomizer(
GroovyCompiler.this.loader, module,
GroovyCompiler.this.artifactCoordinatesResolver);
ClassNode firstClass = null;
for (ClassNode classNode : module.getClasses()) {
if (firstClass == null) {
firstClass = classNode;
}
for (CompilerAutoConfiguration autoConfiguration : customizers) {
if (autoConfiguration.matches(classNode)) {
if (GroovyCompiler.this.configuration
.isGuessDependencies()) {
autoConfiguration
.applyDependencies(dependencyCustomizer);
}
}
}
}
}
}
}
}
private class DefaultDependencyCoordinatesAstTransformation implements
ASTTransformation {
@Override
public void visit(ASTNode[] nodes, final SourceUnit source) {
for (ASTNode node : nodes) {
if (node instanceof ModuleNode) {
ModuleNode module = (ModuleNode) node;
for (ImportNode importNode : module.getImports()) {
visitAnnotatedNode(importNode);
}
for (ClassNode classNode : module.getClasses()) {
visitAnnotatedNode(classNode);
classNode.visitContents(new ClassCodeVisitorSupport() {
@Override
protected SourceUnit getSourceUnit() {
return source;
}
@Override
public void visitAnnotations(AnnotatedNode node) {
visitAnnotatedNode(node);
}
});
}
}
}
}
private void visitAnnotatedNode(AnnotatedNode annotatedNode) {
for (AnnotationNode annotationNode : annotatedNode.getAnnotations()) {
if (isGrabAnnotation(annotationNode)) {
transformGrabAnnotation(annotationNode);
}
}
}
private boolean isGrabAnnotation(AnnotationNode annotationNode) {
String annotationClassName = annotationNode.getClassNode().getName();
return annotationClassName.equals(Grab.class.getName())
|| annotationClassName.equals(Grab.class.getSimpleName());
}
private void transformGrabAnnotation(AnnotationNode grabAnnotation) {
grabAnnotation.setMember("initClass", new ConstantExpression(false));
Expression valueExpression = grabAnnotation.getMember("value");
String value = null;
if (valueExpression instanceof ConstantExpression) {
ConstantExpression constantExpression = (ConstantExpression) valueExpression;
Object valueObject = constantExpression.getValue();
if (valueObject instanceof String) {
value = (String) valueObject;
if (isConvenienceForm(value)) {
return;
}
}
}
applyGroupAndVersion(grabAnnotation, value);
}
private boolean isConvenienceForm(String value) {
return value.contains(":") || value.contains("#");
}
private void applyGroupAndVersion(AnnotationNode grabAnnotation, String module) {
if (module != null) {
grabAnnotation.setMember("module", new ConstantExpression(module));
}
else {
module = (String) ((ConstantExpression) grabAnnotation.getMembers().get(
"module")).getValue();
}
if (grabAnnotation.getMember("group") == null) {
ConstantExpression groupIdExpression = new ConstantExpression(
GroovyCompiler.this.artifactCoordinatesResolver
.getGroupId(module));
grabAnnotation.setMember("group", groupIdExpression);
}
if (grabAnnotation.getMember("version") == null) {
ConstantExpression versionExpression = new ConstantExpression(
GroovyCompiler.this.artifactCoordinatesResolver
.getVersion(module));
grabAnnotation.setMember("version", versionExpression);
}
}
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright 2012-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 org.springframework.boot.cli.compiler;
import groovy.lang.Grab;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
/**
* {@link ASTTransformation} to resolve {@link Grab} artifact coordinates.
* @author Andy Wilkinson
* @author Phillip Webb
*/
class ResolveDependencyCoordinatesTransformation implements ASTTransformation {
private static final Set<String> GRAB_ANNOTATION_NAMES = Collections
.unmodifiableSet(new HashSet<String>(Arrays.asList(Grab.class.getName(),
Grab.class.getSimpleName())));
private final ArtifactCoordinatesResolver coordinatesResolver;
ResolveDependencyCoordinatesTransformation(
ArtifactCoordinatesResolver coordinatesResolver) {
this.coordinatesResolver = coordinatesResolver;
}
@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
ClassVisitor classVisitor = new ClassVisitor(source);
for (ASTNode node : nodes) {
if (node instanceof ModuleNode) {
ModuleNode module = (ModuleNode) node;
for (ImportNode importNode : module.getImports()) {
visitAnnotatedNode(importNode);
}
for (ClassNode classNode : module.getClasses()) {
visitAnnotatedNode(classNode);
classNode.visitContents(classVisitor);
}
}
}
}
private void visitAnnotatedNode(AnnotatedNode annotatedNode) {
for (AnnotationNode annotationNode : annotatedNode.getAnnotations()) {
if (GRAB_ANNOTATION_NAMES.contains(annotationNode.getClassNode().getName())) {
transformGrabAnnotation(annotationNode);
}
}
}
private void transformGrabAnnotation(AnnotationNode grabAnnotation) {
grabAnnotation.setMember("initClass", new ConstantExpression(false));
String value = getValue(grabAnnotation);
if (value != null && !isConvenienceForm(value)) {
applyGroupAndVersion(grabAnnotation, value);
}
}
private String getValue(AnnotationNode annotation) {
Expression expression = annotation.getMember("value");
if (expression instanceof ConstantExpression) {
Object value = ((ConstantExpression) expression).getValue();
return (value instanceof String ? (String) value : null);
}
return null;
}
private boolean isConvenienceForm(String value) {
return value.contains(":") || value.contains("#");
}
private void applyGroupAndVersion(AnnotationNode annotation, String module) {
if (module != null) {
setMember(annotation, "module", module);
}
else {
Expression expression = annotation.getMembers().get("module");
module = (String) ((ConstantExpression) expression).getValue();
}
if (annotation.getMember("group") == null) {
setMember(annotation, "group", this.coordinatesResolver.getGroupId(module));
}
if (annotation.getMember("version") == null) {
setMember(annotation, "version", this.coordinatesResolver.getVersion(module));
}
}
private void setMember(AnnotationNode annotation, String name, String value) {
ConstantExpression expression = new ConstantExpression(value);
annotation.setMember(name, expression);
}
private class ClassVisitor extends ClassCodeVisitorSupport {
private final SourceUnit source;
public ClassVisitor(SourceUnit source) {
this.source = source;
}
@Override
protected SourceUnit getSourceUnit() {
return this.source;
}
@Override
public void visitAnnotations(AnnotatedNode node) {
visitAnnotatedNode(node);
}
}
}