Search code examples
groovy

Why does this test script have static type-checking errors?


I have the following sample code

Utility.myLogger

package Utility;
import org.apache.log4j.Logger;

class myLogger {
    static Logger log = Logger.getLogger("com.mycompany")
    Boolean logOn = true;
    String TAG = this.class.toString();

    def warn(String message) {
        if (logOn) {
            log.warn("$TAG: $message");
        }
    }

    def error(String message) {
        log.error("$TAG: $message")
    }
}

Test Code

import Utility.myLogger;

class Parent {
    myLogger l;
    Closure warn;
    Closure error;

    Parent() {
        this.l = new myLogger();
        this.warn = l.&warn;
        this.error = l.&error;
        l.TAG = this.class.toString()
    }
}

Parent p = new Parent();
p.error("Testing")

class Child extends Parent {
    Child() {
        super();
    }
}

Child c = new Child();
c.error("Testing Child")

When I run this, I get two log messages, which is exactly what I expect:

2024-07-18T15:40:14,807 ERROR [mycompany]: class Parent: Testing
2024-07-18T15:40:14,816 ERROR [mycompany]: class Child: Testing Child

The call to p.error and c.error both have static type checking errors that "error(String)" is not found. The script runs correctly and I can continue to work without fixing this, but I would really like to understand why the compiler isn't resolving the call to the error function.


Solution

  • Those closures are dynamic methods, and so if you are using @CompileStatic or @TypeChecking you will get errors from the usage of those methods. A more type safe way of doing what you want with less code would be to use @Delegate annotation like so:

    class Parent {
        @Delegate MyLogger logger
    
        Parent() {
            logger = new MyLogger()
        }
    }
    

    Now any method on MyLogger be available on Parent through the @Delegate annotation. No closures, or method refs needed. It will be compiled that way so it's much faster too.

    Furthermore, with Log4j you can configure it to output the class or name of the logger without the need to use the $TAG method. If you want to add some contextual information to your log statements you also have Thread Context feature, Flow Tracing, Markers, etc. You could skip the MyLogger class all together, simplify your code, and get more powerful logging by adopting one of these features.