[Active Annotation] How to copy / move fields and methods between classes with updated field type reference within copied / moved method body?

362 views
Skip to first unread message

torsten.j...@gmail.com

unread,
Apr 23, 2013, 10:21:15 AM4/23/13
to xtend...@googlegroups.com
I'm working on an active annotation which provides a kind of delegation mechanism. Here a new "extension" class is created which holds a copy of all fields and methods from the annotated class.

Here's the source code of the corresponding annotation processor:

class SupportChainOverridingProcessor extends AbstractClassProcessor {

val String extensionClassNameSuffix = 'Extension'


override doRegisterGlobals(ClassDeclaration annotatedClass, RegisterGlobalsContext context) {

context.registerClass(annotatedClass.extensionClassName)

}

override doTransform(MutableClassDeclaration annotatedClass, extension TransformationContext context) {

if (validate(annotatedClass, context)) {

val extensionClass = createExtensionClass(annotatedClass, context)

transformAnnotatedClass(annotatedClass, extensionClass, context)

}

}


private def getExtensionClassName(ClassDeclaration annotatedClass) {

annotatedClass.qualifiedName + extensionClassNameSuffix

}


private def boolean validate(MutableClassDeclaration annotatedClass, extension TransformationContext context) {


// Check if annotated class does extend another class

if (annotatedClass.extendedClass?.name != 'java.lang.Object') {

annotatedClass.addError('Annotated class must not extend a class')

return false

}

true

}


private def createExtensionClass(MutableClassDeclaration annotatedClass, extension TransformationContext context) {

val extensionClass = findClass(annotatedClass.extensionClassName)


// Extend from annotated class

extensionClass.extendedClass = annotatedClass.newTypeReference


// Add default constructor which initializes the chaining with null 

extensionClass.addConstructor[

body = ['''super(null);''']

]


// Copy all annotations from annotated class (excluding our own annotation)

annotatedClass.annotations.filter[

annotationTypeDeclaration.simpleName != 'org.sculptor.generator.chain.SupportChainOverriding'].forEach[

extensionClass.addAnnotation(annotationTypeDeclaration)]


// Copy all fields from annotated class

annotatedClass.declaredFields.forEach[sourceField |

extensionClass.addField(sourceField.simpleName) [targetField |

targetField.final = sourceField.final

targetField.static = sourceField.static

targetField.transient = sourceField.transient 

targetField.volatile = sourceField.volatile 

targetField.initializer = sourceField.initializer 

targetField.type = sourceField.type 

targetField.docComment = sourceField.docComment 

targetField.visibility = sourceField.visibility 

sourceField.annotations.forEach[targetField.addAnnotation(annotationTypeDeclaration)]

]


// Copy all methods from annotated class

annotatedClass.declaredMethods.forEach[sourceMethod |

extensionClass.addMethod(sourceMethod.simpleName) [targetMethod |

targetMethod.returnType = sourceMethod.returnType 

targetMethod.static = sourceMethod.static

targetMethod.final = sourceMethod.final

targetMethod.strictFloatingPoint = sourceMethod.strictFloatingPoint 

targetMethod.native = sourceMethod.native 

targetMethod.abstract = sourceMethod.abstract

targetMethod.synchronized = sourceMethod.synchronized

targetMethod.^default = sourceMethod.^default

targetMethod.varArgs = sourceMethod.varArgs

targetMethod.body = sourceMethod.body   // FIXME How to relocate the field type references to the extension class? 

targetMethod.exceptions = sourceMethod.exceptions

sourceMethod.parameters.forEach[targetMethod.addParameter(simpleName, type)]

// FIXME How to copy type parameters?

targetMethod.docComment = sourceMethod.docComment 

targetMethod.visibility = sourceMethod.visibility 

sourceMethod.annotations.forEach [targetMethod.addAnnotation(annotationTypeDeclaration)]

]

]

extensionClass 

}


private def transformAnnotatedClass(MutableClassDeclaration annotatedClass, MutableClassDeclaration extensionClass,

extension TransformationContext context) {


// Extend from chain link class referencing the generated extension class

val extensionClassRef = extensionClass.newTypeReference

annotatedClass.extendedClass = typeof(ChainLink).newTypeReference(extensionClassRef)


// add constructor for chaining

annotatedClass.addConstructor [

addParameter("next", extensionClassRef)

body = ['''super(next);''']

]


// remove all fields and methods

annotatedClass.declaredFields.forEach[remove]

annotatedClass.declaredMethods.forEach[remove]


// add methods delegating to the given extension class' public non-final methods  

extensionClass.declaredMethods.filter [

visibility == Visibility::PUBLIC && !final && !static

].forEach [ extensionMethod |

annotatedClass.addMethod(extensionMethod.simpleName) [ delegateMethod |

delegateMethod.returnType = extensionMethod.returnType

delegateMethod.^default = extensionMethod.^default

delegateMethod.varArgs = extensionMethod.varArgs

delegateMethod.exceptions = extensionMethod.exceptions

extensionMethod.parameters.forEach[delegateMethod.addParameter(simpleName, type)]

delegateMethod.docComment = extensionMethod.docComment

delegateMethod.body = [

'''

return getNext().«extensionMethod.simpleName»(«FOR p : extensionMethod.parameters SEPARATOR ", "»«p.simpleName»«ENDFOR»);

'''

]

]

]

}

}



Now I'm wondering how to copy a methods body. By using the original method body all field type references are pointing to the type of the original class.
How can the field type references be redirected to the target class?

And how are a methods type parameters are copied?

/Torsten

Sven Efftinge

unread,
Apr 24, 2013, 1:23:06 AM4/24/13
to xtend...@googlegroups.com

On Apr 23, 2013, at 4:21 PM, torsten.j...@gmail.com wrote:
> Now I'm wondering how to copy a methods body. By using the original method body all field type references are pointing to the type of the original class.
> How can the field type references be redirected to the target class?

That should just work as you did it.
What exactly do you mean by "all field type references are pointing to the type of the original class"?

>
> And how are a methods type parameters are copied?

See https://bugs.eclipse.org/bugs/show_bug.cgi?id=404107

Sven

torsten.j...@gmail.com

unread,
Apr 24, 2013, 5:43:05 AM4/24/13
to xtend...@googlegroups.com
What exactly do you mean by "all field type references are pointing to the type of the original class"? 

In the generated java code of the copied methods the (also copied) fields are not referenced via "this.field" but via "<reference to old type>.field". But in "<old type>" these fields are not available anymore and these fields have been privat.

Compiling this kind of code with CompilationTestHelper results in the following stacktrace:

java.lang.RuntimeException: org.eclipse.emf.common.util.WrappedException: java.lang.NullPointerException: type may not be null
at org.eclipse.xtext.xbase.compiler.CompilationTestHelper.compile(CompilationTestHelper.java:232)
at org.eclipse.xtext.xbase.compiler.CompilationTestHelper.compile(CompilationTestHelper.java:119)
at org.eclipse.xtext.xbase.compiler.CompilationTestHelper.assertCompilesTo(CompilationTestHelper.java:78)
at org.eclipse.xtend.core.compiler.batch.XtendCompilerTester.assertCompilesTo(XtendCompilerTester.java:51)
at org.sculptor.generator.chain.SupportChainOverridingTest.testGeneratedCode1(SupportChainOverridingTest.java:114)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.eclipse.emf.common.util.WrappedException: java.lang.NullPointerException: type may not be null
at org.eclipse.xtext.util.OnChangeEvictingCache.execWithoutCacheClear(OnChangeEvictingCache.java:129)
at org.eclipse.xtext.xbase.typesystem.internal.CachingBatchTypeResolver.resolveTypes(CachingBatchTypeResolver.java:58)
at org.eclipse.xtext.xbase.resource.BatchLinkingService.resolveBatched(BatchLinkingService.java:45)
at org.eclipse.xtext.xbase.resource.BatchLinkableResource.resolveLazyCrossReferences(BatchLinkableResource.java:141)
at org.eclipse.xtext.EcoreUtil2.resolveLazyCrossReferences(EcoreUtil2.java:510)
at org.eclipse.xtext.validation.ResourceValidatorImpl.resolveProxies(ResourceValidatorImpl.java:154)
at org.eclipse.xtext.validation.ResourceValidatorImpl.validate(ResourceValidatorImpl.java:67)
at org.eclipse.xtext.xbase.compiler.CompilationTestHelper.compile(CompilationTestHelper.java:140)
... 27 more
Caused by: java.lang.NullPointerException: type may not be null
at org.eclipse.xtext.xbase.typesystem.references.ParameterizedTypeReference.<init>(ParameterizedTypeReference.java:50)
at org.eclipse.xtext.xbase.typesystem.internal.ResolvedFeature.applyToComputationState(ResolvedFeature.java:91)
at org.eclipse.xtext.xbase.typesystem.computation.XbaseTypeComputer._computeTypes(XbaseTypeComputer.java:854)
at org.eclipse.xtext.xbase.typesystem.computation.XbaseTypeComputer.computeTypes(XbaseTypeComputer.java:106)
at org.eclipse.xtext.xbase.annotations.typesystem.XbaseWithAnnotationsTypeComputer.computeTypes(XbaseWithAnnotationsTypeComputer.java:50)
at org.eclipse.xtend.core.typesystem.XtendTypeComputer.computeTypes(XtendTypeComputer.java:57)
at org.eclipse.xtext.xbase.typesystem.internal.AbstractTypeComputationState.doComputeTypes(AbstractTypeComputationState.java:118)
at org.eclipse.xtext.xbase.typesystem.internal.ExpressionTypeComputationState.doComputeTypes(ExpressionTypeComputationState.java:63)
at org.eclipse.xtext.xbase.typesystem.internal.AbstractTypeComputationState.computeTypes(AbstractTypeComputationState.java:107)
at org.eclipse.xtext.xbase.typesystem.computation.XbaseTypeComputer._computeTypes(XbaseTypeComputer.java:310)
at org.eclipse.xtext.xbase.typesystem.computation.XbaseTypeComputer.computeTypes(XbaseTypeComputer.java:114)
at org.eclipse.xtext.xbase.annotations.typesystem.XbaseWithAnnotationsTypeComputer.computeTypes(XbaseWithAnnotationsTypeComputer.java:50)
at org.eclipse.xtend.core.typesystem.XtendTypeComputer.computeTypes(XtendTypeComputer.java:57)
at org.eclipse.xtext.xbase.typesystem.internal.AbstractTypeComputationState.doComputeTypes(AbstractTypeComputationState.java:118)
at org.eclipse.xtext.xbase.typesystem.internal.AbstractTypeComputationState.computeTypes(AbstractTypeComputationState.java:107)
at org.eclipse.xtext.xbase.typesystem.internal.AbstractRootTypeComputationState.computeTypes(AbstractRootTypeComputationState.java:36)
at org.eclipse.xtext.xbase.typesystem.internal.LogicalContainerAwareReentrantTypeResolver._computeTypes(LogicalContainerAwareReentrantTypeResolver.java:511)
at org.eclipse.xtend.core.typesystem.DispatchAndExtensionAwareReentrantTypeResolver._computeTypes(DispatchAndExtensionAwareReentrantTypeResolver.java:476)
at org.eclipse.xtext.xbase.typesystem.internal.LogicalContainerAwareReentrantTypeResolver.computeTypes(LogicalContainerAwareReentrantTypeResolver.java:421)
at org.eclipse.xtext.xbase.typesystem.internal.LogicalContainerAwareReentrantTypeResolver.computeMemberTypes(LogicalContainerAwareReentrantTypeResolver.java:582)
at org.eclipse.xtext.xbase.typesystem.internal.LogicalContainerAwareReentrantTypeResolver._computeTypes(LogicalContainerAwareReentrantTypeResolver.java:571)
at org.eclipse.xtext.xbase.typesystem.internal.LogicalContainerAwareReentrantTypeResolver.computeTypes(LogicalContainerAwareReentrantTypeResolver.java:415)
at org.eclipse.xtext.xbase.typesystem.internal.LogicalContainerAwareReentrantTypeResolver.computeTypes(LogicalContainerAwareReentrantTypeResolver.java:410)
at org.eclipse.xtend.core.typesystem.DispatchAndExtensionAwareReentrantTypeResolver.computeTypes(DispatchAndExtensionAwareReentrantTypeResolver.java:283)
at org.eclipse.xtext.xbase.typesystem.internal.DefaultReentrantTypeResolver.resolve(DefaultReentrantTypeResolver.java:160)
at org.eclipse.xtext.xbase.typesystem.internal.DefaultReentrantTypeResolver.reentrantResolve(DefaultReentrantTypeResolver.java:147)
at org.eclipse.xtext.xbase.typesystem.internal.TypeResolutionStateAdapter.reentrantResolve(TypeResolutionStateAdapter.java:82)
at org.eclipse.xtext.xbase.typesystem.internal.CompoundReentrantTypeResolver.reentrantResolve(CompoundReentrantTypeResolver.java:63)
at org.eclipse.xtext.xbase.typesystem.internal.CachingBatchTypeResolver$LazyResolvedTypes.delegate(CachingBatchTypeResolver.java:114)
at org.eclipse.xtext.xbase.typesystem.internal.CachingBatchTypeResolver$2.process(CachingBatchTypeResolver.java:61)
at org.eclipse.xtext.xbase.typesystem.internal.CachingBatchTypeResolver$2.process(CachingBatchTypeResolver.java:1)
at org.eclipse.xtext.util.concurrent.IUnitOfWork$Void.exec(IUnitOfWork.java:36)
at org.eclipse.xtext.util.OnChangeEvictingCache.execWithoutCacheClear(OnChangeEvictingCache.java:127)
... 34 more


I'll try to get access to the generated java code.

/Torsten

torsten.j...@gmail.com

unread,
Apr 24, 2013, 5:18:33 PM4/24/13
to xtend...@googlegroups.com
It seems that the exception "java.lang.NullPointerException: type may not be null" from the aforementioned stacktrace is caused by deleting the fields from the original class.

To explain "all field type references are pointing to the type of the original class" let's take the following sample code (here the "original class" is "Foo" and the copied one is "FooExtension"):

package acme

@org.sculptor.generator.chain.CopyingSupportChainOverriding

class Foo {

var String field = 'foo'

def method() {

field = 'bar'

}

}


If we compile this code with the aforementioned annotation processor (without deleting the fields in "original class") we get the following generated java code:

File 1 : acme/Foo.java

package acme;

import acme.FooExtension;
import org.sculptor.generator.chain.CopyingSupportChainOverriding;
import org.sculptor.generator.util.ChainLink;

@CopyingSupportChainOverriding
@SuppressWarnings("all")
public class Foo extends ChainLink<FooExtension> {
  private String field;
  
  public Foo(final FooExtension next) {
    super(next);
  }
  
  public String method() {
    return getNext().method();
    
  }
}

File 2 : acme/FooExtension.java

package acme;

import acme.Foo;
import org.sculptor.generator.chain.CopyingSupportChainOverriding;

@CopyingSupportChainOverriding
@SuppressWarnings("all")
public class FooExtension extends Foo {
  public FooExtension() {
    super(null);
  }
  
  private String field = "foo";
  
  public String method() {
    String _field = super.field = "bar";
    return _field;
  }
}


Here the field "field" is referenced via "super.field" (despite of this field being declared as privat) instead of referencing the local field "field".


By changing the annotation processor to not let the generated class inherit from the "original class" the following code is generated:

File 1 : acme/Foo.java

package acme;

import acme.FooExtension;
import org.sculptor.generator.chain.CopyingSupportChainOverriding;
import org.sculptor.generator.util.ChainLink;

@CopyingSupportChainOverriding
@SuppressWarnings("all")
public class Foo extends ChainLink<FooExtension> {
  private String field;
  
  public Foo(final FooExtension next) {
    super(next);
  }
  
  public String method() {
    return getNext().method();
    
  }
}

File 2 : acme/FooExtension.java

package acme;

import acme.Foo;
import org.sculptor.generator.chain.CopyingSupportChainOverriding;

@CopyingSupportChainOverriding
@SuppressWarnings("all")
public class FooExtension {
  private String field = "foo";
  
  public String method() {
    Foo _Foo = Foo;
    String _field = _Foo.field = "bar";
    return _field;
  }
}


Now I'm wondering how to change (within a copied method body) the field references from the "original class" to local field references. 

/Torsten

Sven Efftinge

unread,
Apr 25, 2013, 9:47:15 AM4/25/13
to xtend...@googlegroups.com
Could you please open a bugzilla and attach the info below?
We will have a closer look next week.

--
You received this message because you are subscribed to the Google Groups "Xtend Programming Language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to xtend-lang+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

torsten.j...@gmail.com

unread,
Apr 25, 2013, 10:41:34 AM4/25/13
to xtend...@googlegroups.com
Could you please open a bugzilla and attach the info below?

Done -> Bug 406566


Sebastian explains the reason for this in bug 406481 as follows:

This is most likely caused by the return type of operations to be resolved before the names / expressions are properly set. E.g. after method.getReturnType the method's body is linked and therefore the references still point to the old fields. Can you try with a sample file that does not contain inferred return types?


Adding a return type to every method is not an option. But changing the order of statements in the annotation processors copy method code is a valid workaround:

Moving the statement "targetMethod.returnType = sourceMethod.returnType" AFTER "targetMethod.body = sourceMethod.body" does the trick.

/Torsten
Reply all
Reply to author
Forward
0 new messages