Skip to content

Commit

Permalink
Support functions written as Groovy scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
graemerocher committed Nov 2, 2017
1 parent ac1915a commit 632e8be
Show file tree
Hide file tree
Showing 14 changed files with 353 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,21 @@ protected void registerDefaultConverters() {
}
});

// String -> Number
addConverter(CharSequence.class, Number.class, (CharSequence object, Class<Number> targetType, ConversionContext context) -> {
// String -> Float
addConverter(CharSequence.class, Float.class, (CharSequence object, Class<Float> targetType, ConversionContext context) -> {
try {
Integer converted = Integer.valueOf(object.toString());
Float converted = Float.valueOf(object.toString());
return Optional.of(converted);
} catch (NumberFormatException e) {
context.reject(object, e);
return Optional.empty();
}
});

// String -> Double
addConverter(CharSequence.class, Double.class, (CharSequence object, Class<Double> targetType, ConversionContext context) -> {
try {
Double converted = Double.valueOf(object.toString());
return Optional.of(converted);
} catch (NumberFormatException e) {
context.reject(object, e);
Expand Down Expand Up @@ -281,6 +292,7 @@ protected void registerDefaultConverters() {
}
});


// String -> Boolean
addConverter(CharSequence.class, Boolean.class, (CharSequence object, Class<Boolean> targetType, ConversionContext context) -> {
String booleanString = object.toString().toLowerCase(Locale.ENGLISH);
Expand Down
24 changes: 24 additions & 0 deletions examples/simple-groovy-lambda/src/main/groovy/example/Book.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2017 original 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 example

/**
* @author Graeme Rocher
* @since 1.0
*/
class Book {
String title
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2017 original 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 example

import groovy.transform.CompileStatic

import javax.inject.Singleton

/**
* @author Graeme Rocher
* @since 1.0
*/
@Singleton
@CompileStatic
class BookService {


Book toUpperCase(Book book) {
String title = book.title
if(title != null) {
book.title = book.title.toUpperCase()
}
return book
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2017 original 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 example

/**
* @author Graeme Rocher
* @since 1.0
*/

BookService bookService

Book toUpperCase(Book book) {
bookService.toUpperCase(book)
}
5 changes: 5 additions & 0 deletions function-groovy/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dependencies {
compile project(":inject-groovy")
compile project(":function")
runtime project(":configurations/jackson")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright 2017 original 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.particleframework.function.groovy

import groovy.transform.CompileStatic
import groovy.transform.Field
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.expr.DeclarationExpression
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.ast.stmt.ExpressionStatement
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.FieldASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.transform.sc.transformers.StaticCompilationTransformer
import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor
import org.particleframework.ast.groovy.InjectTransform
import org.particleframework.ast.groovy.annotation.AnnotationStereoTypeFinder
import org.particleframework.ast.groovy.utils.AstMessageUtils
import org.particleframework.ast.groovy.utils.AstUtils
import org.particleframework.context.ApplicationContext
import org.particleframework.function.executor.FunctionInitializer

import javax.inject.Inject
import java.lang.reflect.Modifier

import static org.codehaus.groovy.ast.tools.GeneralUtils.*

/**
* Transforms a Groovy script into a function
*
* @author Graeme Rocher
* @since 1.0
*/
@CompileStatic
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
class FunctionTransform implements ASTTransformation{
public static final ClassNode FIELD_TYPE = ClassHelper.make(Field)
AnnotationStereoTypeFinder stereoTypeFinder = new AnnotationStereoTypeFinder();
@Override
void visit(ASTNode[] nodes, SourceUnit source) {

def uri = source.getSource().getURI()
if(uri != null) {
def file = uri.toString()
if(!file.contains("src/main/groovy") || file.endsWith("logback.groovy") || file.endsWith("application.groovy") || file ==~ (/\S+\/application-\S+.groovy/)) {
return
}
}
for(node in source.getAST().classes) {
if(node.isScript()) {
node.setSuperClass(ClassHelper.makeCached(FunctionInitializer))
MethodNode functionMethod = node.methods.find() { method -> !method.isAbstract() && !method.isStatic() && method.isPublic() && method.name != 'run' }
if(functionMethod == null) {
AstMessageUtils.error(source, node, "Function must have at least one public method")
}
else {


MethodNode runMethod = node.getMethod("run", AstUtils.ZERO_PARAMETERS)
node.removeMethod(runMethod)
MethodNode mainMethod = node.getMethod("main", new Parameter(ClassHelper.make(([] as String[]).class), "args"))
Parameter argParam = mainMethod.getParameters()[0]
def thisInstance = varX('$this')
def functionCall = callX(thisInstance, functionMethod.getName(), args(callX(varX("it"), "get", args(classX(functionMethod.parameters[0].type.plainNodeReference)))))
def closureExpression = closureX(stmt(functionCall))
mainMethod.variableScope.putDeclaredVariable(thisInstance)
closureExpression.setVariableScope(mainMethod.variableScope)
mainMethod.setCode(
block(
declS(thisInstance, ctorX(node)),
stmt(callX(thisInstance, "run", args(varX(argParam), closureExpression)))
)
)
new StaticCompilationTransformer(source, new StaticTypeCheckingVisitor(source, node)).visitMethod(mainMethod)
def code = runMethod.getCode()
if(code instanceof BlockStatement) {
BlockStatement bs = (BlockStatement)code
for(st in bs.statements) {
if(st instanceof ExpressionStatement) {
ExpressionStatement es = (ExpressionStatement)st
Expression exp = es.expression
if(exp instanceof DeclarationExpression) {
DeclarationExpression de = (DeclarationExpression)exp
def initial = de.getVariableExpression().getInitialExpression()
if ( initial == null) {
de.addAnnotation(new AnnotationNode(ClassHelper.make(Inject)))
new FieldASTTransformation().visit([new AnnotationNode(FIELD_TYPE), de] as ASTNode[], source)
}
}
}
}
}

node.addMethod(new MethodNode(
"injectThis",
Modifier.PROTECTED,
ClassHelper.VOID_TYPE,
params(param(ClassHelper.make(ApplicationContext),"ctx")),
null,
block()
))

ConstructorNode constructorNode = new ConstructorNode(Modifier.PUBLIC, block(
stmt(
callX(varX("applicationContext"), "inject", varX("this"))
)
))
constructorNode.addAnnotation(new AnnotationNode(ClassHelper.make(Inject)))
node.declaredConstructors.clear()
node.addConstructor(constructorNode)

new InjectTransform().visit(nodes, source)
}

}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2017 original 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.
*/
/**
* <p>Support classes that simplify writing standalone functions as Groovy scripts</p>
*
* @author Graeme Rocher
* @since 1.0
*/
package org.particleframework.function.groovy;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.particleframework.function.groovy.FunctionTransform
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2017 original 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.particleframework.function.groovy

import spock.lang.Specification

import java.nio.charset.StandardCharsets

/**
* @author Graeme Rocher
* @since 1.0
*/
class FunctionTransformSpec extends Specification{

void "run function"() {
expect:
new RoundFunction().round(1.6f) == 2

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2017 original 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.particleframework.function.groovy

import javax.inject.Singleton

/**
* @author Graeme Rocher
* @since 1.0
*/
@Singleton
class MathService {
int round(float value) {
Math.round(value)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.particleframework.function.groovy

MathService mathService

int round(float value) {
mathService.round(value) // go
}
Loading

0 comments on commit 632e8be

Please sign in to comment.