@ -15,16 +15,40 @@
* /
* /
package org.springframework.bootstrap.cli.command ;
package org.springframework.bootstrap.cli.command ;
import groovy.lang.Mixin ;
import java.util.ArrayList ;
import java.util.List ;
import joptsimple.OptionParser ;
import joptsimple.OptionSet ;
import joptsimple.OptionSpecBuilder ;
import org.codehaus.groovy.ast.AnnotationNode ;
import org.codehaus.groovy.ast.ClassHelper ;
import org.codehaus.groovy.ast.ClassNode ;
import org.codehaus.groovy.ast.ClassNode ;
import org.codehaus.groovy.ast.MethodNode ;
import org.codehaus.groovy.ast.Parameter ;
import org.codehaus.groovy.ast.expr.ArgumentListExpression ;
import org.codehaus.groovy.ast.expr.ClassExpression ;
import org.codehaus.groovy.ast.expr.ClosureExpression ;
import org.codehaus.groovy.ast.expr.Expression ;
import org.codehaus.groovy.ast.expr.MethodCallExpression ;
import org.codehaus.groovy.ast.stmt.BlockStatement ;
import org.codehaus.groovy.ast.stmt.ExpressionStatement ;
import org.codehaus.groovy.ast.stmt.Statement ;
import org.codehaus.groovy.classgen.GeneratorContext ;
import org.codehaus.groovy.classgen.GeneratorContext ;
import org.codehaus.groovy.control.CompilationFailedException ;
import org.codehaus.groovy.control.CompilationFailedException ;
import org.codehaus.groovy.control.CompilePhase ;
import org.codehaus.groovy.control.CompilePhase ;
import org.codehaus.groovy.control.SourceUnit ;
import org.codehaus.groovy.control.SourceUnit ;
import org.codehaus.groovy.control.customizers.CompilationCustomizer ;
import org.codehaus.groovy.control.customizers.CompilationCustomizer ;
import org.codehaus.groovy.control.customizers.ImportCustomizer ;
import org.codehaus.groovy.control.customizers.ImportCustomizer ;
import org.objectweb.asm.Opcodes ;
import org.springframework.bootstrap.cli.Command ;
import org.springframework.bootstrap.cli.Command ;
/ * *
/ * *
* Customizer for the compilation of CLI commands .
*
* @author Dave Syer
* @author Dave Syer
*
*
* /
* /
@ -37,10 +61,22 @@ public class ScriptCompilationCustomizer extends CompilationCustomizer {
@Override
@Override
public void call ( SourceUnit source , GeneratorContext context , ClassNode classNode )
public void call ( SourceUnit source , GeneratorContext context , ClassNode classNode )
throws CompilationFailedException {
throws CompilationFailedException {
// AnnotationNode mixin = new AnnotationNode(ClassHelper.make(Mixin.class));
addOptionHandlerMixin ( classNode ) ;
// mixin.addMember("value",
overrideOptionsMethod ( source , classNode ) ;
// new ClassExpression(ClassHelper.make(OptionHandler.class)));
addImports ( source , context , classNode ) ;
// classNode.addAnnotation(mixin);
}
/ * *
* Add imports to the class node to make writing simple commands easier . No need to
* import { @ link OptionParser } , { @link OptionSet } , { @link Command } or
* { @link OptionHandler } .
*
* @param source the source node
* @param context the current context
* @param classNode the class node to manipulate
* /
private void addImports ( SourceUnit source , GeneratorContext context ,
ClassNode classNode ) {
ImportCustomizer importCustomizer = new ImportCustomizer ( ) ;
ImportCustomizer importCustomizer = new ImportCustomizer ( ) ;
importCustomizer . addImports ( "joptsimple.OptionParser" , "joptsimple.OptionSet" ,
importCustomizer . addImports ( "joptsimple.OptionParser" , "joptsimple.OptionSet" ,
OptionParsingCommand . class . getCanonicalName ( ) ,
OptionParsingCommand . class . getCanonicalName ( ) ,
@ -48,4 +84,73 @@ public class ScriptCompilationCustomizer extends CompilationCustomizer {
importCustomizer . call ( source , context , classNode ) ;
importCustomizer . call ( source , context , classNode ) ;
}
}
/ * *
* If the script defines a block in this form :
*
* < pre >
* options {
* option "foo" , "My Foo option"
* option "bar" , "Bar has a value" withOptionalArg ( ) ofType Integer
* }
* < / pre >
*
* Then the block is taken and used to override the { @link OptionHandler # options ( ) }
* method . In the example "option" is a call to
* { @link OptionHandler # option ( String , String ) } , and hence returns an
* { @link OptionSpecBuilder } . Makes a nice readable DSL for adding options .
*
* @param source the source node
* @param classNode the class node to manipulate
* /
private void overrideOptionsMethod ( SourceUnit source , ClassNode classNode ) {
BlockStatement block = source . getAST ( ) . getStatementBlock ( ) ;
List < Statement > statements = block . getStatements ( ) ;
for ( Statement statement : new ArrayList < Statement > ( statements ) ) {
if ( statement instanceof ExpressionStatement ) {
ExpressionStatement expr = ( ExpressionStatement ) statement ;
Expression expression = expr . getExpression ( ) ;
if ( expression instanceof MethodCallExpression ) {
MethodCallExpression method = ( MethodCallExpression ) expression ;
if ( method . getMethod ( ) . getText ( ) . equals ( "options" ) ) {
expression = method . getArguments ( ) ;
if ( expression instanceof ArgumentListExpression ) {
ArgumentListExpression arguments = ( ArgumentListExpression ) expression ;
expression = arguments . getExpression ( 0 ) ;
if ( expression instanceof ClosureExpression ) {
ClosureExpression closure = ( ClosureExpression ) expression ;
classNode . addMethod ( new MethodNode ( "options" ,
Opcodes . ACC_PROTECTED , ClassHelper . VOID_TYPE ,
new Parameter [ 0 ] , new ClassNode [ 0 ] , closure
. getCode ( ) ) ) ;
statements . remove ( statement ) ;
}
}
}
}
}
}
}
/ * *
* Add { @link OptionHandler } as a mixin to the class node if it doesn ' t already
* declare it as a super class .
*
* @param classNode the class node to manipulate
* /
private void addOptionHandlerMixin ( ClassNode classNode ) {
// If we are not an OptionHandler then add that class as a mixin
if ( ! classNode . isDerivedFrom ( ClassHelper . make ( OptionHandler . class ) )
& & ! classNode . isDerivedFrom ( ClassHelper . make ( "OptionHandler" ) ) ) {
AnnotationNode mixin = new AnnotationNode ( ClassHelper . make ( Mixin . class ) ) ;
mixin . addMember ( "value" ,
new ClassExpression ( ClassHelper . make ( OptionHandler . class ) ) ) ;
classNode . addAnnotation ( mixin ) ;
}
}
}
}