Search code examples
javaandroidannotationsxtend

How to get full return type forwarding in Xtend's Active Annotations?


I'm trying out Xtend's Active Annotations by writing a simple "Logged" annotation for tracing when methods have been called. Basically I want to write this in Xtend:

@Logged
override onCreate()
{
   sampleFuncCall()
}

and get something like this in Java:

@Override void onCreate()
{
   Log.d("TAG", "onCreate started");
   sampleFuncCall();
   Log.d("TAG", "onCreate ended");
}

Here's my first attempt:

@Active(LoggedAnnotationProcessor)
@Target(ElementType.METHOD)
annotation Logged {
}

class LoggedAnnotationProcessor extends AbstractMethodProcessor
{
    override doTransform(MutableMethodDeclaration method, extension TransformationContext context) 
    {
        val prefix = "wrapped_"
        method.declaringType.addMethod(prefix + method.simpleName) [ m |
            m.static = method.static
            m.visibility = Visibility.PRIVATE
            m.docComment = method.docComment            
            m.exceptions = method.exceptions
            method.parameters.forEach[ p | m.addParameter(p.simpleName, p.type) ]
            m.body = method.body
            m.primarySourceElement = method
            m.returnType = method.returnType
        ]
        val voidMethod = method.returnType === null || method.returnType.void
        method.body = [ ''' 
                try {
                    android.util.Log.d("TAG", "«method.simpleName» start");
                    «IF (!voidMethod) method.returnType» ret = «ENDIF»
                    «prefix + method.simpleName»(«method.parameters.map[simpleName].join(", ")»);
                    android.util.Log.d("TAG", "«method.simpleName» end");
                    «IF (!voidMethod)»return ret;«ENDIF»
                }
                catch(RuntimeException e) {
                    android.util.Log.d("TAG", "«method.simpleName» ended with exception " 
                        + e.getClass().getSimpleName() + "\n" + e.getMessage());
                    throw e;                
                }
            ''']
    }
}

(Note that I couldn't find a way to modify method.body and have to create a new private method with the "wrapped_" prefix. I guess this is bad for performance, so if you know how to modify the method's body directly, please share).

Here I get into problems when working with methods returning void. If the method's return type wasn't declared explicitly, I get the following error:

Cannot call method 'isVoid' on a inferred type reference before the compilation phase. Check isInferred() before calling any methods.

Ok, I can add the check of method.returnType.inferred, but what to do with it - seems that at this stage we still don't know if it going to be void or not, but knowing that is crucial for forwarding the method's return value.

Please tell me what is the proper way to write such an annotation, thanks!


Solution

  • maybe you should defer the calc. to the closure

    method.body = [
        val voidMethod = method.returnType === null || method.returnType.void
    
             ''' 
                try {
                    android.util.Log.d("TAG", "«method.simpleName» start");
                    «IF (!voidMethod)»«method.returnType» ret = «ENDIF»
                    «prefix + method.simpleName»(«method.parameters.map[simpleName].join(", ")»);
                    android.util.Log.d("TAG", "«method.simpleName» end");
                    «IF (!voidMethod)»return ret;«ENDIF»
                }
                catch(RuntimeException e) {
                    android.util.Log.d("TAG", "«method.simpleName» ended with exception " 
                        + e.getClass().getSimpleName() + "\n" + e.getMessage());
                    throw e;                
                }
            ''']