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

Java Callable Wrappers via custom attributes #833

Open
jonpryor opened this issue May 6, 2021 · 2 comments
Open

Java Callable Wrappers via custom attributes #833

jonpryor opened this issue May 6, 2021 · 2 comments
Labels
callable-wrappers Issues with Java Callable Wrappers enhancement Proposed change to current functionality

Comments

@jonpryor
Copy link
Member

jonpryor commented May 6, 2021

Java Callable Wrappers are (currently) generated by parsing an IL Type Definition for a Java.Lang.Object subclass (though see also Issue #540), and contain Java source which "mirror" the managed type, "forwarding" the base class, implemented interfaces, overridden methods, and [Export] methods.

It may be useful if the Java generation process could be extended via custom attributes, overriding the defaults.

Add the following two custom attributes:

namespace Java.Interop.Generation {

    [AttributeUsage (AttributeTargets.Class, AllowMultiple=false)]
    public class JcwExtendsAttribute : Attribute {
        public JcwExtendsAttribute(string javaTypeName);
        public string JavaTypeName {get;}
    }

    [AttributeUsage (AttributeTargets.Class, AllowMultiple=true)]
    public class JcwImplementsAttribute : Attribute {
        public JcwImplementsAttribute(string javaTypeName);
        public string JavaTypeName {get;}
    }
}

and update https://github.com/xamarin/java.interop/tree/main/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers so that if the above attributes are present, they override the default behavior. This would allow:

[JcwExtends("some.random.Type")]
[Register("mypackage.MyType")]
[JcwImplements("some.random.Interface")]
[JcwImplements("another.random.Interface<Integer>")]
class MyExample : Java.Lang.Object, ISomeOtherInterface {
    [Export] public void forSomeRandomInterface() {}
    [Export] public void forAnotherRandomInterface(Integer v) {}
}

which would generate the Java Callable Wrapper:

package mypackage; // via JcwNameAttribute

class MyType // JcwNameAttribute
  extends some.random.Type // JcwExtends
  implements mono.android.IGCUserPeer
    , some.random.Interface // JcwImplements
    , another.random.Interface<Integer> // JcwImplements
{
    public MyType() {…}
    public void forSomeRandomInterface() {…}
    public void forAnotherRandomInterface(java.lang.Integer v) {…}
}
@jonpryor jonpryor added enhancement Proposed change to current functionality callable-wrappers Issues with Java Callable Wrappers labels May 6, 2021
@dellis1972
Copy link
Contributor

dellis1972 commented May 7, 2021

This is an interesting idea. One of the scenarios I've been hitting is as follows. We have the following Java interface which we want to bind.

public interface SplitInstallStateUpdatedListener 
implements StateUpdatedListener<SplitInstallSessionState> {
   onStateUpdate(SplitInstallSessionState state)
}

In order to bind this what tends to happen is we change the StateUpdatedListener<SplitInstallSessionState> to a managed type of Java.Lang.Object because ... you know ... generics. (see here) So we end up with this in the generated C# code

    [JavaTypeParameters(new[] { "StateT" })]
    [Register("com/google/android/play/core/listener/StateUpdatedListener", "", "Xamarin.Google.Android.Play.Core.Listener.IStateUpdatedListenerInvoker")]
    public interface IStateUpdatedListener : IJavaObject, IDisposable, IJavaPeerable
    {
        [Register("onStateUpdate", "(Ljava/lang/Object;)V", "GetOnStateUpdate_Ljava_lang_Object_Handler:Xamarin.Google.Android.Play.Core.Listener.IStateUpdatedListenerInvoker, Xamarin.Google.Android.Play.Core")]
        void OnStateUpdate(Java.Lang.Object p0);
    }

So we then try to "implement" this interface in an app

    public class Listener : Java.Lang.Object, ISplitInstallStateUpdatedListener
    {
        public void OnStateUpdate(Object p0)
        {
        }
    }

The problem starts when we JCW gets generated from the C# code. It will produce the following

package crc64cbac1eb14a70596e;


public class Listener
	extends java.lang.Object
	implements
		mono.android.IGCUserPeer,
		com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener,
		com.google.android.play.core.listener.StateUpdatedListener
{
/** @hide */
	public static final String __md_methods;
	static {
		__md_methods = 
			"n_onStateUpdate:(Ljava/lang/Object;)V:GetOnStateUpdate_Ljava_lang_Object_Handler:Xamarin.Google.Android.Play.Core.Listener.IStateUpdatedListenerInvoker, Xamarin.Google.Android.Play.Core\n" +
			"";
		mono.android.Runtime.register ("DynamicAssetsExample.Listener, DynamicAssetsExample", Listener.class, __md_methods);
	}


	public Listener ()
	{
		super ();
		if (getClass () == Listener.class)
			mono.android.TypeManager.Activate ("DynamicAssetsExample.Listener, DynamicAssetsExample", "", this, new java.lang.Object[] {  });
	}


	public void onStateUpdate (java.lang.Object p0)
	{
		n_onStateUpdate (p0);
	}

	private native void n_onStateUpdate (java.lang.Object p0);

	private java.util.ArrayList refList;
	public void monodroidAddReference (java.lang.Object obj)
	{
		if (refList == null)
			refList = new java.util.ArrayList ();
		refList.add (obj);
	}

	public void monodroidClearReferences ()
	{
		if (refList != null)
			refList.clear ();
	}
}

and then the following error.

error: Listener is not abstract and does not override abstract method onStateUpdate(SplitInstallSessionState) in StateUpdatedListener 

So part of this will work I think. The JcwImplements should fix the problem were we are just spitting out com.google.android.play.core.listener.StateUpdatedListener instead of com.google.android.play.core.listener.StateUpdatedListener<SplitInstallSessionState>. But then I guess we also need a way to update the onStateUpdate method to use SplitInstallSessionState instead of java.lang.Object as its parameter.

public void onStateUpdate (java.lang.Object p0)

So perhaps in addition to this we need an additional attribute which can be placed either on Methods or Parameters to change the JCW type?

@jonpryor
Copy link
Member Author

@dellis1972 wrote:

So perhaps in addition to this we need an additional attribute which can be placed either on Methods or Parameters to change the JCW type?

We have the ExportAttribute custom attribute, which could be used to control the JNI signature. (Not currently, but it could be extended to do so.)

However, I don't think it'll solve your problem. Your problem is that, eventually, you'll have a Java API which accepts StateUpdatedListener<SplitInstallSessionState> as a parameter:

public class UsesListener {
    public void method(StateUpdatedListener<SplitInstallSessionState> listener);
}

Someone will want to call UsesListener.method(), providing their own type:

UsesListener.Method(new MyListener());

at which point they would also need to do "all this" as part of their MyListener implementation. It's not a scalable solution.

:-(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
callable-wrappers Issues with Java Callable Wrappers enhancement Proposed change to current functionality
Projects
None yet
Development

No branches or pull requests

2 participants