manifold-systems / manifold

Manifold is a Java compiler plugin, its features include Metaprogramming, Properties, Extension Methods, Operator Overloading, Templates, a Preprocessor, and more.
http://manifold.systems/
Apache License 2.0
2.43k stars 125 forks source link

"this" on static extension methods #348

Closed joseaio closed 2 years ago

joseaio commented 2 years ago

Already known that static method in Java don't have a "this" context. But this is not true with other OO languages ("this" on class method referenced the called Class).

I like a static logging method warn() declared on Object, for use on any class

If "static extension methods" allows an optional (for retrocompatibility) first parameter with the called class, as this one:

package <your-project>.extensions.java.lang.Object;

@Extension
public class MyObject {

  private static final String jdk_logger_packages = "jdk.logger.packages";
  protected static final Map<Class, Logger> logs = new HashMap<>();

  static {
    // Ignore logger wrappers, as this, when writes class and method on log traces
    System.setProperty(jdk_logger_packages, MyObject.class.getPackageName());
  }

  private static Logger log(Class clazz) {
    if (!logs.containsKey(clazz)) {
      logs.put(clazz, Logger.getLogger(clazz.getName()));
    }
    return logs.get(clazz);
  }

  @Extension
  protected static void warn(@This Class clazz, String msg) {
    log(clazz).warning(msg);
  }

Now, I can write logs, on any class, calling to static method: warn("message")

note: this solution centralize logging framework dependency on single class (I don't like imports of logging framework on all classes)

joseaio commented 2 years ago

This is not a solution (I prefer "this" on class/static methods), but a possible workaround (I don't known if cause a poor performance):

Please note the use of StackWalker on warn() method to determine the caller.

package <your-project>.extensions.java.lang.Object;

@Extension
public class MyObject {

  // Create walker constant to reduce initialization time
  private static final StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);

  private static final String jdk_logger_packages = "jdk.logger.packages";
  protected static final Map<Class, Logger> logs = new HashMap<>();

  static {
    // Ignore logger wrappers, as this, when writes class and method on log traces
    System.setProperty(jdk_logger_packages, MyObject.class.getPackageName());
  }

  private static Logger log(Class<?> clazz) {
    if (!logs.containsKey(clazz)) {
      logs.put(clazz, Logger.getLogger(clazz.getName()));
    }
    return logs.get(clazz);
  }

  @Extension
  public static void warn(String msg) {
    // Use walker to determine the caller class, to get its Logger
    log(walker.getCallerClass()).warning(msg);
  }
}

I changed warn() method to public because protected extension methods causes error on Manifold (see issue 349) FIXED on Manifold version >= 2022.1.8

rsmckinney commented 2 years ago

See "Smart" static methods with @ThisClass. Paired with @Self I think you can accomplish what you are looking for with static methods here.

joseaio commented 2 years ago

Great example and another use case: generic factory with static method (on superclass or interface). In the past, I also needed this...