Create callback

I try to create a method to make a callback, but that’s don’t work. Consol return the error message. I think my problem is from: i pass this and this don’t read my sketch i suppose ?

void setup() {
  select_callback("my_method",this);
}

import java.lang.reflect.Method;
void select_callback(String callbackMethod, Object callbackObject) {
  try {
    Class<?> callbackClass = callbackObject.getClass();
    Method selectMethod =
    callbackClass.getMethod(callbackMethod, new Class[] { File.class });
  } catch(NoSuchMethodException nsme) {
    System.err.println(callbackMethod + "() could not be found");
  }
  
}


void my_method() {
  println("truc");
}
1 Like

It’s attempting to find a method of name “my_method” from class PApplet that has 1 parameter of datatype File. :file_cabinet:

However, PApplet::my_method() is parameterless! :roll_eyes:

what is it? I don’t underwant what you mean by parameterless ? You mean there is possibility, if yes how do that ?

Should be

void my_method(File file) {
 println("truc");
}
1 Like

PApplet::my_method() means an instance method named my_method() of datatype PApplet.

Parameters are variables declared inside a function’s () parens which receive passed arguments.

A parameterless function is simply a function w/ an empty () parens.

en.Wiktionary.org/wiki/parameterless

@quark @GoToLoop thks for your lights !
Et voilà the result:

void setup() {
}

int count = 0;
void draw() {
  count++;
  select_callback("my_method",this,"truc", count);
}

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
void select_callback(String callbackMethod, Object callbackObject, String s, int i) {
  try {
    Class<?> callbackClass = callbackObject.getClass();
    Method selectMethod =
    callbackClass.getMethod(callbackMethod, 
                            new Class[] { String.class, int.class });
                            
    selectMethod.invoke(callbackObject, s,i);
  } catch (IllegalAccessException iae) {
      System.err.println(callbackMethod + "() must be public");

  } catch (InvocationTargetException ite) {
      ite.printStackTrace();

  } catch (NoSuchMethodException nsme) {
      System.err.println(callbackMethod + "() could not be found");
    }
}

void my_method(String stuff, int value) {
  println(stuff,value);
}

Method::getMethod()'s 2nd parameter is variadic. So you don’t need to instantiate an array for it:
callbackClass.getMethod(callbackMethod, String.class, int.class);

And no need to 3 catch () blocks. This 1 deals w/ all of them:
} catch (final ReflectiveOperationException e) {

Docs.Oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ReflectiveOperationException.html

Thx but I don’t understand what you mean… may be if you rewrite my code is more understandable. Because for my my code work nice, And don’t see where I instantiate an array, plus I don’ understand too why my secend argument is variadic or not comparable… sorry for my missing knowledge !!!

GoToLoop is providing alternative code to do the same job, whether you use it or not is up to you. There is little to choose between them.

Personally I would stick with the style you are using, there are some benefits to instantiating a Class array to hold the parameter types and separating the catch clauses gives you the option to treat the exceptions differently.

Yes it works but it has one serious flaw, the draw() method is executed 60 times a second but you only need to find the method once.

In the example sketch below I have modified your code so that the method is found in setup and then invoked in draw. It also shows how to find methods without parameters.

import java.lang.reflect.*;

Method method_1 = null;
Method method_2 = null;

void setup() {
  // Find a callback with 2 parameters
  method_1 = select_callback("my_method_1", this, new Class[] {String.class, int.class} );
  // Find a callback with no parameters
  method_2 = select_callback("my_method_2", this, new Class[0]);
}

int count = 0;

void draw() {
  count++;
  try {
    method_1.invoke(this, "truc", count);
  } 
  catch (IllegalAccessException iae) {
    System.err.println(method_1.getName() + "() must be public");
  }
  catch (InvocationTargetException ite) {
    System.err.println("The method " + method_1.getName() + "() threw an exception");
    ite.printStackTrace();
  }

  try {
    method_2.invoke(this);
  } 
  catch (IllegalAccessException iae) {
    System.err.println(method_1.getName() + "() must be public");
  }
  catch (InvocationTargetException ite) {
    System.err.println("The method " + method_1.getName() + "() threw an exception");
    ite.printStackTrace();
  }
}

/**
 Find a method matching the parameters
 @parameter callbackMethod the name of the callback method
 @parameter callbackObject the object resposible for executing the calback
 @parameter params an array of classes of the parametrs for the call back method
 */
Method select_callback(String callbackMethod, Object callbackObject, Class... params) {
  Method callback = null;
  try {
    Class<?> callbackClass = callbackObject.getClass();
    callback = callbackClass.getMethod(callbackMethod, params );
  } 
  catch (NoSuchMethodException nsme) {
    System.err.println(callbackMethod + "() could not be found");
  }  
  return callback;
}

void my_method_1(String stuff, int value) {
  println(stuff, value);
}

void my_method_2() {
  println("============================");
}
2 Likes

Oh, I was doing almost the same too: :disappointed:

/**
 * Method Reflective Invocation (v3.0)
 * Stanlepunk (2019/Apr/03)
 * Mod GoToLoop
 * https://Discourse.Processing.org/t/create-callback/9831/10
 */

import java.lang.reflect.Method;
final Method myMethod = getStrIntMethod("myMethod", this);

void setup() {
  frameRate(1);
}

void draw() {
  background((color) random(#000000));
  invokeStrIntMethod(myMethod, this, "Frames:", frameCount);
}

static final void myMethod(final String stuff, final int num) {
  println(stuff, num);
}

static final Method getStrIntMethod(final String name, final Object instance) {
  final Class<?> c = instance.getClass();

  try {
    return c.getMethod(name, String.class, int.class);
  } 
  catch (final NoSuchMethodException e) {
    try {
      final Method m = c.getDeclaredMethod(name, String.class, int.class);
      m.setAccessible(true);
      return m;
    }   
    catch (final NoSuchMethodException ex) {
      ex.printStackTrace();
      return null;
    }
  }
}

static final void invokeStrIntMethod(
  final Method funct, final Object instance, final String txt, final int val) {
  try {
    funct.invoke(instance, txt, val);
  } 
  catch (final ReflectiveOperationException e) {
    e.printStackTrace();
  }
}
1 Like

Which 1s? Unless we’re also storing the array to use it later, there’s ZERO benefit! :roll_eyes:

Should we create an Object array for Method::invoke() as well? :face_with_hand_over_mouth:

method_1.invoke(this, "truc", count); :arrow_right:
method_1.invoke(this, new Object[] { "truc", count });

Both Class::getMethod() & Method::invoke() got variadic args.

But you’re advocating an array for: Class::getMethod(): :thinking:
method_1 = select_callback("my_method_1", this, new Class[] {String.class, int.class} );

And not for: Method::invoke()? :crazy_face:
method_1.invoke(this, "truc", count);

Yea, for the ultra rare cases indeed. But it’s none of our cases here! :flushed:

For such cases PApplet class already got a method called method(): :angel:
Processing.GitHub.io/processing-javadocs/core/processing/core/PApplet.html#method-java.lang.String-

@quark @GoToLoop nice no I4ve three ways to make the same thing. I’ll study your algo to understand well how that’s work. thanks a lot.

The good point here : there is only one instantiating and after no need to rebuild, juste use. But the problem you must right the in code the method name. With mine the name can be pass with a String and it’s what i need. So I think try to rewrite your code to use in this way.

thx

I’ve a feeling for there is a same issue than @quark code it’s necessary to write a method before this one exist, or like as usual i’m weong ?

You can call getStrIntMethod() from anywhere, as long as you have access to the “myMethod”'s object instance reference. :sunglasses:

BtW, here’s a more powerful generic version, which can find & invoke any method name w/ any number of parameters & types: :partying_face:

/**
 * Method Reflective Invocation (v4.0)
 * Stanlepunk (2019/Apr/03)
 * Mod GoToLoop
 * https://Discourse.Processing.org/t/create-callback/9831/16
 */

import java.lang.reflect.Method;
final Method myMethod = getMethod("myMethod", this, String.class, int.class);

void setup() {
  frameRate(1);
}

void draw() {
  background((color) random(#000000));
  invokeMethod(myMethod, this, "Frames:", frameCount);
}

static final void myMethod(final String stuff, final int num) {
  println(stuff, num);
}

@SafeVarargs static final Method getMethod(
  final String name, final Object instance, final Class... classes) {
  final Class<?> c = instance.getClass();

  try {
    return c.getMethod(name, classes);
  } 
  catch (final NoSuchMethodException e) {
    try {
      final Method m = c.getDeclaredMethod(name, classes);
      m.setAccessible(true);
      return m;
    }   
    catch (final NoSuchMethodException ex) {
      ex.printStackTrace();
      return null;
    }
  }
}

@SafeVarargs static final Object invokeMethod(
  final Method funct, final Object instance, final Object... args) {
  try {
    return funct.invoke(instance, args);
  } 
  catch (final ReflectiveOperationException e) {
    throw new RuntimeException(e);
  }
}
1 Like

Whatthe utility of @SafeVarargs

Docs.Oracle.com/en/java/javase/11/docs/api/java.base/java/lang/SafeVarargs.html

Et voilà, a method manager, to create method, add in a list of method and to call it with String name and check if this one already existe. I don’t if it’s really optimizing but that’s work :slight_smile:

/**
* Manage creator method
*/
void setup() {
  frameRate(1);
  create_method("my_method",this, String.class, int.class);
  create_method("my_method",this, String.class, int.class); // print: create_method(): this method my_method with those classes organisation already exist
  create_method("my_method",this, String.class, int.class, float.class);
  println("method size:",method_index.size());
  for(Method_Manager mm : method_index) {
    println(mm.get_name());
    printArray(mm.get_index());
  }
}

void draw() {
  surface.setTitle(Integer.toString((int)frameRate));
  background((int)random(#000000));
  method("my_method",this,"Frames:", frameCount);
  method("my_method",this,"Frames:", frameCount, 3.4);
  //method("my_method",this,"Frames:", frameCount);
}

void my_method(final String stuff, final int num) {
  println(stuff, num);
}








/**
* Method manager to call method if this one already exist
* v 0.0.1
* 2019-2019
*/
void method(String name, PApplet pa, Object... args) {
  Method method = method_exist(name, args);
  if(method != null) {
    invoke_method(method, pa, args);
  } else {
    println("method(): no method exist for this name:",name,"or this order of arguments:");
    for(int i = 0 ; i < args.length ; i++) {
      println("[",i,"]",args[i].getClass().getName());
    } 
  }
}

Method method_exist(String name, Object... args) {
  Method method = null;
  if(method_index != null && method_index.size() > 0) {
    for(Method_Manager mm : method_index) {
      if(mm.get_name().equals(name)) {
        boolean same_is = true;
        if(args.length == mm.get_index().length) {
          for(int i = 0 ; i < args.length; i++) {
            String arg_name = translate_class_to_type_name_if_necessary(args[i]);
            if(!arg_name.equals(mm.get_index()[i])) {
              same_is = false;
              break;
            }
          }
        } else {
          same_is = false;
        }       
        if(same_is) {
          method = mm.get_method();
        }
      }
    }
  }
  return method;
}

String translate_class_to_type_name_if_necessary(Object arg) {
  String name = arg.getClass().getName();
  if(name.equals("java.lang.Byte")) {
    name = "byte";
  } else if(name.equals("java.lang.Short")) {
    name = "short";
  } else if(name.equals("java.lang.Integer")) {
    name = "int";
  } else if(name.equals("java.lang.Long")) {
    name = "long";
  } else if(name.equals("java.lang.Float")) {
    name = "float";
  } else if(name.equals("java.lang.Double")) {
    name = "double";
  } else if(name.equals("java.lang.Boolean")) {
    name = "boolean";
  } else if(name.equals("java.lang.Character")) {
    name = "char";
  }
  return name;
}

void create_method(String name, PApplet pa, Class... classes) {
  if(method_index == null) {
    method_index = new ArrayList<Method_Manager>();
  } 
  init_method(name,pa,classes);
}

ArrayList<Method_Manager> method_index ;
void init_method(String name, PApplet pa, Class... classes) { 
  // check if method already exist
  boolean create_class_is = true; 
  for(Method_Manager mm : method_index) {
    if(mm.get_name().equals(name)) {
      if(mm.get_index().length == classes.length) {
        int count_same_classes = 0;
        for(int i = 0 ; i < classes.length ; i++) {
          if(mm.get_index()[i].equals(classes[i].getCanonicalName())) {
            count_same_classes++;
          }
        }
        if(count_same_classes == classes.length) {
          create_class_is = false;
          break;
        }
      }
    }
  }
  // instantiate if necessary
  if(create_class_is) {
    Method method = get_method(name,pa,classes);
    Method_Manager method_manager = new Method_Manager(method,name,classes);
    method_index.add(method_manager);
  } else {
    println("create_method(): this method",name,"with those classes organisation already exist");
  }
}


/**
* Method manger
*/
import java.lang.reflect.Method;
class Method_Manager {
  Method method;
  String name;
  String [] index;
  Method_Manager(Method method, String name, Class... classes) {
    index = new String[classes.length];
    for(int i = 0 ; i < index.length ; i++) {
      index[i] = classes[i].getName();
    }
    this.method = method;
    this.name = name;
  }

  String [] get_index() {
    return index;
  }


  String get_name() {
    return name;
  }

  Method get_method() {
    return method;
  }
}


/**
 * refactoring of Method Reflective Invocation (v4.0)
 * Stanlepunk (2019/Apr/03)
 * Mod GoToLoop
 * https://Discourse.Processing.org/t/create-callback/9831/16
 */
static final Method get_method(String name, Object instance, Class... classes) {
  final Class<?> c = instance.getClass();
  try {
    return c.getMethod(name, classes);
  } 
  catch (final NoSuchMethodException e) {
    try {
      final Method m = c.getDeclaredMethod(name, classes);
      m.setAccessible(true);
      return m;
    }   
    catch (final NoSuchMethodException ex) {
      ex.printStackTrace();
      return null;
    }
  }
}

static final Object invoke_method(Method funct, Object instance, Object... args) {
  try {
    return funct.invoke(instance, args);
  } 
  catch (final ReflectiveOperationException e) {
    throw new RuntimeException(e);
  }
}

If you need to know whether 2 Method objects represent the same actual method, just call equals() on 1 of the objects in order to compare them. :bulb:

You can also call getParameterTypes() to get an array of classes of the Method’s parameter types: :smile_cat:

Docs.Oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/Executable.html#getParameterTypes()

// https://Discourse.Processing.org/t/create-callback/9831/20
// GoToLoop (2019/Apr/05)

import java.lang.reflect.Method;

final Method[] methods = {
  getMethod("myMethod", this, String.class, int.class), 
  getMethod("myMethod", this, String.class, int.class), 
  getMethod("myMethod", this, String.class, int.class, float.class)
};

void setup() {
  println(methods[0] == methods[1]); // we can't use the compare == operator!
  println(methods[0].equals(methods[1])); // true
  println(methods[0].equals(methods[2])); // false

  for (final Method m : methods) {
    println();
    printArray(m.getParameterTypes());
  }

  exit();
}

static final void myMethod(final String stuff, final int num) {
  println(stuff, num);
}

static final void myMethod(final String stuff, final int num, float frac) {
  println(stuff, num);
}

@SafeVarargs static final Method getMethod(
  final String name, final Object instance, final Class... classes) {
  final Class<?> c = instance.getClass();

  try {
    return c.getMethod(name, classes);
  } 
  catch (final NoSuchMethodException e) {
    try {
      final Method m = c.getDeclaredMethod(name, classes);
      m.setAccessible(true);
      return m;
    }   
    catch (final NoSuchMethodException ex) {
      ex.printStackTrace();
      return null;
    }
  }
}