javacpp icon indicating copy to clipboard operation
javacpp copied to clipboard

First attempt at Java side automatic downcast feature

Open nyholku opened this issue 2 years ago • 7 comments

Hi,

this is my first sketch for a Java side automatic downcast feature.

To use it do as follow:

The base type of the class hierarchy is annotated with @Downcast and the name of the downcast function in the target class.

For example:

infoMap.put(new Info("opencascade::handle").skip().annotations("@MySmartPtr", "@Downcast").downCaster("TKernel.downcast"));

And the downcast function is injected into the target as follows:

String downcaster = "static Standard_Transient downcast(Standard_Transient obj) {...";
infoMap.put(new Info("@occ.TKernel").javaText(downcaster));

Since in the case of multi toolkit library it usefull to inject some code to all toolkits (for example to register the downcastable classe) I also added:

infoMap.put(new Info("@").javaText("java code here to register classes"));

How it works:

Any function (say foo) that returns a type that is tagged with @Downcast is renamed and tagged with '_ _' (for example foo_ _). In addition a new pure Java function with the original name and same signature is created . This new function calls the renamed function and passes the returned value to the downcast function and returns the value returned from the downcast function. The renamed function is also annotated with @Renamed

For example:

public native @MySmartPtr @Downcast @Renamed Example_Base foobar__(@MySmartPtr  Example_Base p);

public @MySmartPtr @Downcast  Example_Base foobar(@MySmartPtr  Example_Base p){return (Example_Base)TKernel.downcast( foobar__(p));};

nyholku avatar Jan 31 '22 18:01 nyholku

We shouldn't need to modify the Parser in any way for this. Could you explain what doesn't work when we directly annotate a Java method in a base class somewhere?

saudet avatar Jan 31 '22 23:01 saudet

Well I did not consider that we should not modify the Parser.

I did try several places/ways to do this and this was the first one that worked.

Since AFAIU parser is responsible producing the Java code (JNI native side) that corresponds to the C++ code I felt and found that this is the easiest place to do this.

There are three things that need to happen:

  1. Rename the native call that returns the object to be downcast (I want to keep the original name for the function that returns the downcasted value).

  2. Duplicate that function declaration keeping the original name, make it not-native and add downcasting Java code in the body.

  3. Inject target level Java code to implement the actual mechanism for the downcasting that the code in step 2 above calls.

nyholku avatar Feb 01 '22 07:02 nyholku

In my opinion, it's a lot simpler to just call your method from JNI, somewhere around Pointer.init()...

saudet avatar Feb 01 '22 11:02 saudet

Sorry, I'm not following you. Can you elaborate a bit?

nyholku avatar Feb 01 '22 16:02 nyholku

Well, basically what you did in pull #542, but have the Generator "write" that JNI code instead of forcing the user to do it manually. Even if we're using some JNI to call Java methods, which is a bit slow, it can still be pretty fast when caching everything, and possibly merging the logic with Pointer.init(). As long as the user doesn't have to touch JNI, we can optimize things in the background with JavaCPP if/when anyone cares, but still have an JNI-less user experience, also leaving the window open to use tech like Panama Foreign in the future.

saudet avatar Feb 01 '22 23:02 saudet

Thanks.

I see what you mean, I think.

So the 'API' for the auto downcast feature would be something like :

package org.bytedeco.javacpp.annotation;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.PARAMETER })
public @interface Downcaster {
	String typenamefun() default "";
}
@Properties(value = @Platform(compiler = "cpp11",
    include = { //
          "fun_to_get_type_name.hxx", //

...

infoMap.put(new Info("opencascade::handle").skip().annotations("@Downcaster(typenamefun=\"fun_to_get_type_name\")", "@MySmartPtr"));

Hmm, I kind of like the economy of expression in this solution.

There is one thing that needs to be solved, ie in a multi toolkit situation the Java classes to be dynamically instantiated are spread across number of target classes and thus the name alone is enough to instantiate the class.

I guess we can generate code to populate at runtime a global cache of classes or constructors, indexed by class name.

nyholku avatar Feb 02 '22 08:02 nyholku

There is one thing that needs to be solved, ie in a multi toolkit situation the Java classes to be dynamically instantiated are spread across number of target classes and thus the name alone is enough to instantiate the class.

I guess we can generate code to populate at runtime a global cache of classes or constructors, indexed by class name.

Well, we don't need to know their names. We just need to be able to associate any given C++ object with a Java class, which is something we can do with, as I mentioned previously, std::type_index: https://en.cppreference.com/w/cpp/types/type_index

saudet avatar Feb 03 '22 08:02 saudet