Skip to content

Instantly share code, notes, and snippets.

@xnickmx
Created June 11, 2013 18:28
Show Gist options
  • Save xnickmx/5759436 to your computer and use it in GitHub Desktop.
Save xnickmx/5759436 to your computer and use it in GitHub Desktop.
ParamsChecker annotation, ASTTransformation and example usage.
package com.faceture.paramschecker
import org.codehaus.groovy.transform.GroovyASTTransformationClass
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
@Retention (RetentionPolicy.SOURCE)
@Target ([ElementType.TYPE])
@GroovyASTTransformationClass ("com.faceture.paramschecker.ParamsNotNullNotEmptyTransformation")
@interface ParamsNotNullNotEmpty {}
package com.faceture.paramschecker
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.Parameter
import org.codehaus.groovy.ast.builder.AstBuilder
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.ast.stmt.Statement
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
class ParamsNotNullNotEmptyTransformation implements ASTTransformation {
private final AstBuilder astBuilder = new AstBuilder()
void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
// per the documentation: http://groovy.codehaus.org/gapi/org/codehaus/groovy/transform/ASTTransformation.html
// index 0 is the annotation node
// index 1 is the class/method/etc node
AnnotationNode annotationNode = (AnnotationNode)astNodes[0]
ClassNode classNode = (ClassNode)astNodes[1]
// get the methods in this class
List<MethodNode> methodNodes = classNode.getMethods()
// get just the public methods
List<MethodNode> publicMethods = methodNodes.findAll{ MethodNode methodNode ->
methodNode.isPublic() && !methodNode.isAbstract()
}
// add an error check for each param on each method
publicMethods.each { MethodNode methodNode ->
// get the parameters to this method
Parameter[] parameters = methodNode.getParameters()
// get the existing code from the method
BlockStatement methodCode = (BlockStatement)methodNode.getCode()
List<Statement> methodStatements = methodCode.getStatements()
// for each parameter...
parameters.eachWithIndex { Parameter parameter, int index ->
// get the paramschecker check code
BlockStatement preconditionCheck = getPreconditionCheckStatement(parameter)
// add the paramschecker check code in order to the method
methodStatements.add(index, preconditionCheck)
}
}
}
private BlockStatement getPreconditionCheckStatement(Parameter parameter) {
String paramName = parameter.getName()
String source = """
if (null == ${paramName}) {
throw new IllegalArgumentException("$paramName is null.")
}
else if ((${paramName} instanceof java.util.Collection || ${paramName} instanceof java.lang.String) && ${paramName}.isEmpty()) {
throw new IllegalArgumentException("$paramName is empty.")
}
}
"""
List<ASTNode> astNodes = astBuilder.buildFromString(CompilePhase.SEMANTIC_ANALYSIS, false, source)
BlockStatement blockStatement = (BlockStatement)astNodes[0]
return blockStatement
}
}
package com.faceture.paramschecker.test
import com.faceture.paramschecker.ParamsNotNullNotEmpty
import groovy.transform.CompileStatic
/**
* Params are checked and uses static compilation
*/
@ParamsNotNullNotEmpty
@CompileStatic
class ExampleCheckedAndCompileStatic {
void testMethod(String myString, Float myFloat, List myList) {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment