So I'm writing an AST Transformation that adds a field with the #Delegate annotation to a class
#SimpleAST
class PersonBuilder{
}
Should produce
class PersonBuilder{
#Delegate
Person target = new Person
}
My Interface:
#Retention(RetentionPolicy.RUNTIME)
#Target([ElementType.TYPE])
#GroovyASTTransformationClass("poc.SimpleASTTransformation")
public #interface SimpleAST {
/**
* A class for which builder methods should be created. It will be an error to leave
* this attribute with its default value for some strategies.
*/
Class value()
}
My transformation:
#CompileStatic
#GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
class SimpleASTTransformation implements ASTTransformation {
#Override
void visit(ASTNode[] astNodes, SourceUnit source) {
ClassNode classNode = (ClassNode) astNodes[1]
if (!astNodes) return
if (!astNodes[0]) return
if (!astNodes[1]) return
if (!(astNodes[0] instanceof AnnotationNode)) return
if (!(astNodes[1] instanceof ClassNode)) return
println "Running AST Transformation for ${classNode.getNameWithoutPackage()}..."
AnnotationNode annotationNode = (AnnotationNode) astNodes[0]
ClassExpression classExpression = (ClassExpression) annotationNode.getMember("value")
String packageName = classNode.getPackageName()
String builderClassNameWithoutPackage = classNode.getNameWithoutPackage()
String originalClassNameWithPackage = classExpression.getText()
originalClassNameWithPackage = Validate.checkOriginalClassName(originalClassNameWithPackage)
Class<?> originalClass = Class.forName(originalClassNameWithPackage)
ClassNode originalClassNode = new ClassNode(originalClass)
String originalClassNameWithoutPackage = originalClassNode.getNameWithoutPackage()
println "Generating methods for $originalClassNameWithoutPackage..."
generateTargetField(classNode,originalClass)
println "Transformation applied!"
}
static void generateTargetField(ClassNode classNode, Class originalClass){
ClassNode originalClassNode = new ClassNode(originalClass)
ConstructorCallExpression constructorCallExpression = new ConstructorCallExpression(originalClassNode,new ArgumentListExpression())
FieldNode fieldNode = new FieldNode("target",
2,
originalClassNode,
classNode,
constructorCallExpression)
ArrayList<AnnotationNode> annotationNodes = new ArrayList<>()
annotationNodes.add(new AnnotationNode(new ClassNode(Delegate)))
fieldNode.addAnnotations(annotationNodes)
classNode.addField(fieldNode)
}
}
When I check the byte code there is no getter/setter methods for the fields inside the Class Person (I used #Delegate on the target field)
However, if I Just add the field manually and compile the code I get getter and setters for the fields in class Person.
Person has 2 fields: firstName, lastName both Strings.
Related
ok - tried looking /reading and not sure i have an answer to this.
I have a Utility class which wraps a static ConcurrentLinkedQueue internally.
The utility class itself adds some static methods - i dont expect to call new to create an instance of the Utility.
I want to intercept the getProperty calls the utility class - and implement these internally in the class definition
I can achieve this by adding the following to the utility classes metaclass, before i use it
UnitOfMeasure.metaClass.static.propertyMissing = {name -> println "accessed prop called $name"}
println UnitOfMeasure.'Each'
however what i want to do is declare the interception in the class definition itself. i tried this in the class definition - but it never seems to get called
static def propertyMissing (receiver, String propName) {
println "prop $propName, saught"
}
i also tried
static def getProperty (String prop) { println "accessed $prop"}
but this isnt called either.
So other than adding to metaClass in my code/script before i use, how can declare the in the utility class that want to capture property accesses
the actual class i have looks like this at present
class UnitOfMeasure {
static ConcurrentLinkedQueue UoMList = new ConcurrentLinkedQueue(["Each", "Per Month", "Days", "Months", "Years", "Hours", "Minutes", "Seconds" ])
String uom
UnitOfMeasure () {
if (!UoMList.contains(this) )
UoMList << this
}
static list () {
UoMList.toArray()
}
static getAt (index) {
def value = null
if (index in 0..(UoMList.size() -1))
value = UoMList[index]
else if (index instanceof String) {
Closure matchClosure = {it.toUpperCase().contains(index.toUpperCase())}
def position = UoMList.findIndexOf (matchClosure)
if (position != -1)
value = UoMList[position]
}
value
}
static def propertyMissing (receiver, String propName) {
println "prop $propName, saught"
}
//expects either a String or your own closure, with String will do case insensitive find
static find (match) {
Closure matchClosure
if (match instanceof Closure)
matchClosure = match
if (match instanceof String) {
matchClosure = {it.toUpperCase().contains(match.toUpperCase())}
}
def inlist = UoMList.find (matchClosure)
}
static findWithIndex (match) {
Closure matchClosure
if (match instanceof Closure)
matchClosure = match
else if (match instanceof String) {
matchClosure = {it.toUpperCase().contains(match.toUpperCase())}
}
def position = UoMList.findIndexOf (matchClosure)
position != -1 ? [UoMList[position], position] : ["Not In List", -1]
}
}
i'd appreciate the secret of doing this for a static utility class rather than instance level property interception, and doing it in class declaration - not by adding to metaClass before i make the calls.
just so you can see the actual class, and script that calls - i've attached these below
my script thats calling the class looks like this
println UnitOfMeasure.list()
def (uom, position) = UnitOfMeasure.findWithIndex ("Day")
println "$uom at postition $position"
// works UnitOfMeasure.metaClass.static.propertyMissing = {name -> println "accessed prop called $name"}
println UnitOfMeasure[4]
println UnitOfMeasure.'Per'
which errors like this
[Each, Per Month, Days, Months, Years, Hours, Minutes, Seconds]
Days at postition 2
Years
Caught: groovy.lang.MissingPropertyException: No such property: Per for class: com.softwood.portfolio.UnitOfMeasure
Possible solutions: uom
groovy.lang.MissingPropertyException: No such property: Per for class: com.softwood.portfolio.UnitOfMeasure
Possible solutions: uom
at com.softwood.scripts.UoMTest.run(UoMTest.groovy:12)
Static version of propertyMissing method is called $static_propertyMissing:
static def $static_propertyMissing(String name) {
// do something
}
This method gets invoked by MetaClassImpl at line 1002:
protected static final String STATIC_METHOD_MISSING = "$static_methodMissing";
protected static final String STATIC_PROPERTY_MISSING = "$static_propertyMissing";
// ...
protected Object invokeStaticMissingProperty(Object instance, String propertyName, Object optionalValue, boolean isGetter) {
MetaClass mc = instance instanceof Class ? registry.getMetaClass((Class) instance) : this;
if (isGetter) {
MetaMethod propertyMissing = mc.getMetaMethod(STATIC_PROPERTY_MISSING, GETTER_MISSING_ARGS);
if (propertyMissing != null) {
return propertyMissing.invoke(instance, new Object[]{propertyName});
}
} else {
// .....
}
// ....
}
Example:
class Hello {
static def $static_propertyMissing(String name) {
println "Hello, $name!"
}
}
Hello.World
Output:
Hello, World!
I'm tearing my hair out - I'm sure I'm missing something small, but I can't see it for the life of me! Any hints really appreciated. Essentially on the line root.name = 'root'; it's giving me the following errors:
Undefined Class 'root.name' on the 'root.name' section
Expected a Class Member on the '=' sign.
import 'package:uuid/uuid.dart';
class AppLogic {
Item root = new Item();
root.name = 'root';
List<Item> allItems;
void createNewItem(Item parent){
allItems.add(new Item());
}
}
class Item {
DateTime created = DateTime.now();
String name;
String ID = new Uuid().v1();
Item parentID;
List<String> childrenIDs;
Item.fromID(this.ID);
Item.fromName(this.name);
Item();
}
This code is not allowed outside a constructor or method
root.name = 'root';
Only variable declaration (with initialization), method declaration, and constructors are allowed in a class' body.
You could do it like
class AppLogic {
Item root = new Item()..name = 'root';
List<Item> allItems;
void createNewItem(Item parent){
allItems.add(new Item());
}
}
I wrote my own AST Transformation which should generate getter and setter methods (here creating getter method). But they don't work and can't understand reason.
create annotation with property
#Retention(RetentionPolicy.SOURCE)
#Target([ElementType.FIELD])
#GroovyASTTransformationClass(['ua.home.gag.ast.GetterAndSetterASTTransformation'])
public #interface GetterAndSetter {
}
my code of AST transformation which should create getter method for annotated field
#GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
class GetterAndSetterASTTransformation implements ASTTransformation {
#Override
void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
if (!checkNodes(astNodes)) return
List fields = astNodes.findAll { it instanceof FieldNode }
fields.each {
MethodNode getter = getterMethod(ClassHelper.make(it));
it.declaringClass.addMethod(getter);
}
}
static def checkNodes(ASTNode[] nodes) {
nodes &&
nodes[0] &&
nodes[1] &&
nodes[0] instanceof AnnotationNode &&
nodes[0].classNode?.name == GetterAndSetter.class.name &&
nodes[1] instanceof ClassNode
}
private MethodNode getterMethod(FieldNode fieldNode) {
return new MethodNode(
"getMy" + fieldNode.name.capitalize(),
Modifier.PUBLIC,
new ClassNode(fieldNode.type),
new Parameter[0],
new ClassNode[0],
new BlockStatement(
[new ReturnStatement(
new VariableExpression(fieldNode.name)
)],
new VariableScope())
)
}
}
Annotation check
import ua.home.gag.ast.GetterAndSetter
class Example {
#GetterAndSetter
int counter = 5;
static void main(String[] args) {
println new Example().getMyCounter();
}
}
In which place I did mistake?
The result of running :
Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: ua.home.gag.usage.Example.getMyCounter() is applicable for argument types: () values: []
Possible solutions: getCounter(), setCounter(int)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:56)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:51)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:112)
at ua.home.gag.usage.Example.main(Example.groovy:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
PS repo https://bitbucket.org/maxrdev/ast-gands
I think that the problem is in your checkNodes method. This expression nodes[1] instanceof ClassNode evaluates to false because nodes[1] is instanceof FieldNode.
You also don't have to filter and iterate over the fields, because this transformation will be applied to all fields annotated with #GetterAndSetter. That's why you have to only focus on a single case for annotated field. Usually all you have to do is:
#GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
class GetterAndSetterASTTransformation implements ASTTransformation {
#Override
void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
AnnotationNode parent = (AnnotationNode) astNodes[0]
FieldNode node = (FieldNode) astNodes[1]
if (!(parent instanceof AnnotationNode) && !(node instanceof FieldNode)) {
throw new RuntimeException("Internal error: wrong types: ${node.class} / ${parent.class}");
}
Statement statement = new BlockStatement([
new ReturnStatement(new VariableExpression(node))
], new VariableScope())
MethodNode methodNode = new MethodNode("getMy${node.name.capitalize()}",
Modifier.PUBLIC,
node.type,
new Parameter[0],
new ClassNode[0],
statement
)
node.declaringClass.addMethod(methodNode)
}
}
Then below code will work:
class Example {
#GetterAndSetter
int counter = 5;
#GetterAndSetter
String lorem = 'asdasd'
#Deprecated
#GetterAndSetter
BigDecimal ipsum = BigDecimal.ONE
static void main(String[] args) {
Example example = new Example()
println example.getMyCounter()
println example.getMyLorem()
println example.getMyIpsum()
}
}
And the result is:
/usr/lib/jvm/java-1.8.0/bin/java -Didea.launcher.port=7541 -Didea.launcher.bin.path=/opt/idea-IU-129.1525/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-1.8.0/jre/lib/resources.jar:/usr/lib/jvm/java-1.8.0/jre/lib/jsse.jar:/usr/lib/jvm/java-1.8.0/jre/lib/jce.jar:/usr/lib/jvm/java-1.8.0/jre/lib/charsets.jar:/usr/lib/jvm/java-1.8.0/jre/lib/management-agent.jar:/usr/lib/jvm/java-1.8.0/jre/lib/rt.jar:/usr/lib/jvm/java-1.8.0/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-1.8.0/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-1.8.0/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-1.8.0/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-1.8.0/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-1.8.0/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-1.8.0/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-1.8.0/jre/lib/ext/sunjce_provider.jar:/home/wololock/workspace/idea/ast-gands/target/classes:/home/wololock/.m2/repository/org/codehaus/groovy/groovy-all/2.3.7/groovy-all-2.3.7.jar:/opt/idea-IU-129.1525/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain ua.home.gag.usage.Example
5
asdasd
1
Process finished with exit code 0
You can find more examples in e.g. grails-core repository - go to https://github.com/grails/grails-core/, type t and search for ASTTransformation
I am new to Stack Overflow. I have created a Groovy class loader's object in which I have loaded all the classes required by my script. I have task of serializing and deserializing an object created of one of the class that is loaded in class loader. Problem is while deserializing I am not able to cast the object in the class as class in loaded in class loader. I don't know how to cast an object in a class that is loaded in a class loader. Can some one help me in this. The ????? in below snippet is a class that is loaded in class loader but how shall I achieve this.
Object o = null
new ByteArrayInputStream(bytes).withObjectInputStream(getClass().classLoader){ gin ->
o = (?????)gin.readObject() }
Thanks in advance!!!
I managed to solve your problem. Let me show you a working example
Some Employee class, that I use
public class Employee implements java.io.Serializable
{
public String name;
public String address;
}
Then the Main class
class Main {
public static void main(String[] args) {
Employee e1 = new Employee()
e1.name = 'John'
e1.address = 'Main Street'
byte[] bytes = []
ByteArrayOutputStream stream = new ByteArrayOutputStream()
ObjectOutputStream out = new ObjectOutputStream(stream)
out.writeObject(e1)
bytes = stream.toByteArray()
out.close()
stream.close()
Object o = null
new ByteArrayInputStream(bytes).withObjectInputStream(Main.getClassLoader()){ gin ->
o = gin.readObject()
}
print o instanceof Employee
println 'Deserialized Employee...'
println 'Name: ' + o.name
println 'Address: ' + o.address
}
}
Instead of doing getClass().classLoader, which was throwing a java.lang.ClassNotFoundException, I am doing Main.getClassLoader(). This classloader is able to find my Employee class.
Moreover, you don't really need to cast the object, it is groovy, its dynamic, so you get the name and address fields at runtime.
But, you can always check the type of the object and then cast it:
print o instanceof Employee
this will return true
I have the following java class and have placed an XmlJavaAdapter annotation on the payerPartyReference variable. I want the adapter PartyReferenceAdapter to be used for unmarshalling ONLY this variable, not any other variables which have the same type of PartyReference, whether in this class or some other class. How can I do this? Thanks for your help!
public class InitialPayment extends PaymentBase
{
// Want PartyReferenceAdapter to be used here
#XmlJavaTypeAdapter(PartyReferenceAdapter.class)
protected PartyReference payerPartyReference;
//
// Dont want PartyReferenceAdapter to be used here
protected PartyReference receiverPartyReference;
//
protected AccountReference receiverAccountReference;
#XmlSchemaType(name = "date")
protected XMLGregorianCalendar adjustablePaymentDate;
#XmlSchemaType(name = "date")
protected XMLGregorianCalendar adjustedPaymentDate;
protected Money paymentAmount;
}
My Adapter is defined as follows:
public class PartyReferenceAdapter
extends XmlAdapter < Object, PartyReference > {
public PartyReference unmarshal(Object obj) throws Exception {
Element element = null;
if (obj instanceof Element) {
element = (Element)obj;
String reference_id = element.getAttribute("href");
PartyReference pr = new PartyReference();
pr.setHref(reference_id);
return pr;
}
public Object marshal(PartyReference arg0) throws Exception {
return null;
}
}
Field/Property Level
If you set #XmlJavaTypeAdapter on a field/property it will only be used for that property.
http://bdoughan.blogspot.com/2010/07/xmladapter-jaxbs-secret-weapon.html
Type Level
If you set #XmlJavaTypeAdapter on a type, then it will used for all references to that type.
http://bdoughan.blogspot.com/2010/12/jaxb-and-immutable-objects.html
Package Level
If you set #XmlJavaTypeAdapter on a package, then it will be used for all references to that type within that package:
http://bdoughan.blogspot.com/2011/05/jaxb-and-joda-time-dates-and-times.html