Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First attempt at Java side automatic downcast feature #548

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

nyholku
Copy link

@nyholku nyholku commented Jan 31, 2022

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));};

@saudet
Copy link
Member

saudet commented Jan 31, 2022

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?

@nyholku
Copy link
Author

nyholku commented Feb 1, 2022

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.

@saudet
Copy link
Member

saudet commented Feb 1, 2022

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

@nyholku
Copy link
Author

nyholku commented Feb 1, 2022

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

@saudet
Copy link
Member

saudet commented Feb 1, 2022

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.

@nyholku
Copy link
Author

nyholku commented Feb 2, 2022

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.

@saudet
Copy link
Member

saudet commented Feb 3, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants