Skip to content

Instantly share code, notes, and snippets.

@aaronzirbes
Last active December 18, 2015 03:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aaronzirbes/5718923 to your computer and use it in GitHub Desktop.
Save aaronzirbes/5718923 to your computer and use it in GitHub Desktop.
Trying to write an AST transformer to make it easier to add a @GrailsEnum annotation
class GrailsEnumType {
String name
}
@interface GrailsEnumHolder {
String value() default 'default value'
}
@interface Parameter {
String name() default 'default name'
String value() default 'default value'
}
@interface Type {
String type() default 'default'
Parameter[] parameters()
}
import org.codehaus.groovy.transform.AnnotationCollectorTransform
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.AnnotatedNode
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.ListExpression
import org.codehaus.groovy.ast.expr.AnnotationConstantExpression
import org.codehaus.groovy.ast.expr.ConstantExpression
import org.codehaus.groovy.control.SourceUnit
import org.hibernate.annotations.Parameter
import org.hibernate.annotations.Type
/**
* This builds a hibernate annotation to map the custom Grails style enums
* using a custom hibernate mapper via the Type annotation.
*
* Desired resultant annotation:
* <pre>
* import com.bloomhealthco.radiant.member.qualifyingevent.CoverageChangeEventType
*
* class SomeEntity {
* @Type(
* type = 'com.bloomhealthco.radiant.service.hibernate.GrailsEnumType',
* parameters = [
* @Parameter(
* name = 'enumClass',
* value = 'com.bloomhealthco.radiant.member.qualifyingevent.CoverageChangeEventType' ) ] )
* CoverageChangeEventType coverageChangeEventType
* }
* </pre>
*
* using the template in GrailsEnum:
* <pre>
* @Type( parameters = [ @Parameter(type) ] )
* </pre>
*/
class GrailsEnumTypeProcessor extends AnnotationCollectorTransform {
List<AnnotationNode> visit(AnnotationNode collector,
AnnotationNode usage,
AnnotatedNode annotated,
SourceUnit src) {
// Get the class name of the field being annotated
String fieldType = annotated.type.name
// Build the new Nodes
AnnotationNode typeAnnotation = new AnnotationNode(ClassHelper.make(Type))
AnnotationNode parameterAnnotation = new AnnotationNode(ClassHelper.make(Parameter))
ListExpression parameters = new ListExpression(
[ new AnnotationConstantExpression(parameterAnnotation) ]
)
// Set the attributes
typeAnnotation.addMember('type', getConstantValue(GrailsEnumType.name))
typeAnnotation.addMember('parameters', parameters)
parameterAnnotation.addMember('name', getConstantValue('enumClass'))
parameterAnnotation.addMember('value', getConstantValue(fieldType))
return [ typeAnnotation ]
}
private Expression getConstantValue(String value) {
return new ConstantExpression(value)
}
}
new GroovyShell(this.class.classLoader).evaluate '''
import groovy.transform.AnnotationCollector
import org.joda.time.LocalDate
@Type(parameters = [ @Parameter ])
@AnnotationCollector(processor = 'GrailsEnumTypeProcessor')
@interface GrailsEnum {}
class GrailsEnumType {
String name
}
enum SomeType {
ONE('One'),
ANOTHER('Another')
final String id
SomeType(String id) {
this.id = id
}
String toString() { id }
public static SomeType fromString(String value) {
SomeType type = values().find {it.id == value}
if (!type && (value != 'null')) {
String errorMessage = "No matching '${SomeType.name}' found for '$value'"
throw new IllegalArgumentException(errorMessage)
}
type
}
}
class Thing {
String wat
@GrailsEnum
LocalDate today
@GrailsEnum
SomeType type
}
println new Thing()
'''
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment