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

Desktop JDK-11 Java.Base Binding #858

Open
jonpryor opened this issue Jul 17, 2021 · 0 comments
Open

Desktop JDK-11 Java.Base Binding #858

jonpryor opened this issue Jul 17, 2021 · 0 comments
Assignees
Labels
enhancement Proposed change to current functionality

Comments

@jonpryor
Copy link
Member

jonpryor commented Jul 17, 2021

Begin binding Desktop JVM libraries.

We should try to see if we can use a 1 "module" per assembly binding strategy.

We should look into updating class-parse to natively support the jmod file format. Lacking that, we can get class-parse to read all types in a Java module via e.g.

cd path/to/jdk-11
mkdir x
./bin/jmod extract jmods/java.base.jmod --dir x
mono path/to/class-parse.exe @classes.txt > api.xml

For me, with JDK 11, class-parse is able to find 5609 classes and 553 interfaces.

class-parse also emits 305 warnings:

class-parse: Unable to read file 'x/classes/module-info.class': Unknown constant type 0x00000013.

class-parse needs to actually support Java modules.

Plus:

class-parse: method com/sun/java/util/jar/pack/Attribute$Layout.compareTo(Ljava/lang/Object;)I: Local variable type descriptor mismatch! Got 'Ljava/lang/Object;'; expected 'Lcom/sun/java/util/jar/pack/Attribute$Layout;'.
…
class-parse: method java/util/concurrent/ConcurrentHashMap$EntrySetView.add(Ljava/lang/Object;)Z: Local variable type descriptor mismatch! Got 'Ljava/lang/Object;'; expected 'Ljava/util/concurrent/ConcurrentHashMap$EntrySetView;'
…

Regardless, we can emit an api.xml for the java.base.jmod module. However, to make a binding, we need generator support:

mono path/to/generator.exe -o bindings --codegen-target JavaInterop1 api.xml

This emits 35MB of C# bindings (yay!)

The problem is that the bindings are Xamarin.Android-style, and not what I had wanted for desktop use. For example, Java.Lang.Object:

namespace Java.Lang {

	// Metadata.xml XPath class reference: path="/api/package[@name='java.lang']/class[@name='Object']"
	[global::Android.Runtime.Register ("java/lang/Object", DoNotGenerateAcw=true)]
	public partial class Object {
		static readonly JniPeerMembers _members = new JniPeerMembers ("java/lang/Object", typeof (Object));

		// Metadata.xml XPath constructor reference: path="/api/package[@name='java.lang']/class[@name='Object']/constructor[@name='Object' and count(parameter)=0]"
		[Register (".ctor", "()V", "")]
		public unsafe Object () : this (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)

This calls the Xamarin.Android-style (IntPtr, JniHandleOwnership) constructor. Java.Interop doesn't have an Android.Runtime.JniHandleOwnership enum; that's (currently) specific to Xamarin.Android. Instead, the desired Java.Interop approach is to instead have/use a (ref JniObjectReference, JniObjectReferenceOptions) constructor, as used by TestType:

https://github.com/xamarin/java.interop/blob/855ecfa3a85ce74f40a1cc47e67b70aedd697dc3/tests/Java.Interop-Tests/Java.Interop/TestType.cs#L47-L51

Next:

		public unsafe Java.Lang.Class Class {
			// Metadata.xml XPath method reference: path="/api/package[@name='java.lang']/class[@name='Object']/method[@name='getClass' and count(parameter)=0]"
			[Register ("getClass", "()Ljava/lang/Class;", "")]
			get {
				const string __id = "getClass.()Ljava/lang/Class;";
				try {
					var __rm = _members.InstanceMethods.InvokeNonvirtualObjectMethod (__id, this, null);
					return global::Java.Lang.Object.GetObject<Java.Lang.Class> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
				} finally {
				}
			}
		}

Java.Interop doesn't currently have a Java.Lang.Object type, though that would be created/implicit to binding java.base.jmod. Instead, I would prefer investigating: https://github.com/xamarin/java.interop/blob/main/Documentation/Architecture.md#proposed-javainterop-architecture; that is, instead of Object.GetObject<T>(), use JniEnvironment.Runtime.ValueManager.GetValue<T>().

Then there's marshal methods:

		static Delegate cb_clone;
#pragma warning disable 0169
		static Delegate GetCloneHandler ()
		{
			if (cb_clone == null)
				cb_clone = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_L) n_Clone);
			return cb_clone;
		}

I would prefer that JNINativeWrapper not exist on Desktop.


Creating an api.xml is the easy part (but could certainly be made easier!). The hard part is figuring out what we want for a "nice" Desktop ABI, one that doesn't have "Android-Isms" everywhere. (For example, JniHandleOwnership is Android.Runtime.JniHandleOwnership; a Desktop JVM binding assembly shouldn't have any types in an Android.* namespace!)

@jpobst jpobst added the enhancement Proposed change to current functionality label Aug 11, 2021
jonpryor added a commit to jonpryor/java.interop that referenced this issue Oct 13, 2021
Context: dotnet#858
Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720
Context: https://openjdk.java.net/projects/jigsaw/
Context: xamarin/monodroid@c9e5cbd

JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar`
with a set of `.jmod` files.

Thus, as of JDK 9, there is no `.jar` file to try to parse with
`class-parse`, only `.jmod` files!

A `.jmod` file, in turn, is still a ZIP container, much like `.jar`
files, but:

 1. With a different internal directory structure, and
 2. With a custom file header.

The result of (2) is that while `unzip -l` can show the contents of
a `.jmod` file -- with a warning -- `System.IO.Compression.ZipArchive`
cannot process the file:

	% mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod
	class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.
	<api
	  api-source="class-parse" />

Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod`
files by *copying* the file to a new file, trimming off the 4 byte
{ 0x4a, 0x4d, 0x01, 0x00 } header.  Copying to a new file is
necessary because I couldn't find an easy way to avoid it; I tried
using a "wrapper" `System.IO.Stream` which would skip the firsta
4 bytes, that but that didn't work as desired.

Once able to read a `.jmod` file, *warnings* appeared, a'la

	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	  Local variables array has 2 entries ('LocalVariableTableAttribute(
	      LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0),
	      LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1),
	      LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2),
	      LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))'
	  ); descriptor has 3 entries!
	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries!

This was a variation on the "JDK 8?" block that previously didn't
have much detail, in part because it didn't have a repro.  Now we
have a repro, based on [JDK code][0] which contains a class
declaration within a method declaration

	public List<String> func (StringBuilder value) {
	    class MyStringList extends ArrayList<String> {
	        public MyStringList(String a, int b) {
	        }
	        public String get(int index) {
	            return value.toString();
	        }
	    }
	    return new MyStringList("a", 0xb);
	}

The deal is that `func()` contains a `MyStringList` class, which in
turn contains a constructor with two parameters.  *However*, as far
as Java bytecode is concerned, the constructor contains *4* local
variables with StartPC==0, which is what we use to suggest parameters.
The `this` parameter is skipped because it's a non-static inner class,
and `this$0` was skipped because previously not-fully-understood
"JDK 8?" condition.  Now we know what it is: it's the 'outer `this`"
of the inner class.  `a` and `b` are the actually declared params.

However, then we look at the descriptor value, which has 4 entries:

 1. `Lcom/xamarin/JavaType;`: declaring/outer class
 2. `Ljava/lang/String;`: parameter `a`
 3. `I`: parameter `b`
 4. `Ljava/lang/StringBuilder`: Closure variable from containing
    method.

(1) is skipped, as it's a non-`static` inner class, leaving 3
parameters which are inferred from the Descriptor.

What causes the warning is the 4th `StringBuilder` parameter.
This comes from declaring scope, from `func(StringBuilder value)`.

Update the comparison logic so that we skip "closure variables".
Unfortunately, we still *infer* that a parameter comes from a closure
by seeing if there is a declared field with the same type which:

 1. Has a `val$` prefix (*heavy sigh*), and
 2. Has the `.Final` and `.Synthetic` flags.

This (and other) fixes allows us to parse
`JavaType$1MyStringList.class` without emitting warnings:

	% mono bin/Debug/class-parse.exe 'tests/Xamarin.Android.Tools.Bytecode-Tests/obj/Debug/classes/com/xamarin/JavaType$1MyStringList.class' >/dev/null
	# no warnings

Unfortunately, while this is a *start*, processing `java.base.jmod`
results in 28 warnings:

	% mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null 2>e.txt && wc -l e.txt
	      28 e.txt

Processing Android API-31 results in no warnings:

	% mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null 2>e.txt
	% wc -l e.txt
	       0 e.txt

[0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor added a commit to jonpryor/java.interop that referenced this issue Oct 14, 2021
Context: dotnet#858
Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720
Context: https://openjdk.java.net/projects/jigsaw/
Context: xamarin/monodroid@c9e5cbd

JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar`
with a set of `.jmod` files.

Thus, as of JDK 9, there is no `.jar` file to try to parse with
`class-parse`, only `.jmod` files!

A `.jmod` file, in turn, is still a ZIP container, much like `.jar`
files, but:

 1. With a different internal directory structure, and
 2. With a custom file header.

The result of (2) is that while `unzip -l` can show the contents of
a `.jmod` file -- with a warning -- `System.IO.Compression.ZipArchive`
cannot process the file:

	% mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod
	class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.
	<api
	  api-source="class-parse" />

Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod`
files by using `PartialStream` (73096d9) to skip the first 4 bytes.

Once able to read a `.jmod` file, *warnings* appeared, a'la

	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	  Local variables array has 2 entries ('LocalVariableTableAttribute(
	      LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0),
	      LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1),
	      LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2),
	      LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))'
	  ); descriptor has 3 entries!
	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries!

This was a variation on the "JDK 8?" block that previously didn't
have much detail, in part because it didn't have a repro.  Now we
have a repro, based on [JDK code][0] which contains a class
declaration within a method declaration

	public List<String> staticActionWithGenerics(…) {
	    class MyStringList extends ArrayList<String> {
	        public MyStringList(String a, int b) {
	        }
	        public String get(int index) {
	            return value.toString();
	        }
	    }
	}

The deal is that `staticActionWithGenerics()` contains a `MyStringList`
class, which in turn contains a constructor with two parameters.
*However*, as far as Java bytecode is concerned, the constructor
contains *4* local variables with StartPC==0, which is what we use to
infer parameter names.

Refactor, cleanup, and otherwise rewrite huge swaths of `Methods.cs`
to get to a "happy medium" of:

  * No warnings from our unit tests, ensured by updating
    `ClassFileFixture` to have a `[SetUp]` method which sets the
	`Log.OnLog` field to a delegate which may call `Assert.Fail()`
	when invoked.  (This asserts for all non-Kotlin messages.)

  * No warnings when processing `java.base.jmod`:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null
        # no error messages

  * No warnings when processing Android API-31:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null
        # no error messages

Aside: closures are *weird* and finicky.
Consider the following Java code:

	class ClosureDemo {
	    public void m(String a) {
	        class Example {
	            public Example(int x) {
	                System.out.println (a);
	            }
	        }
	    }
	}

It looks like the JNI signature for the `Example` constructor might
be `(I)V`, but isn't.  It is instead:

	(LClosureDemo;ILjava/lang/String;)V

Breaking that down:

  * `LClosureDemo;`: `Example` is an inner class, and thus has an
    implicit reference to the containing type.  OK, easy to forget.

  * `I`: the `int x` parameter.  Expected.

  * `Ljava/lang/String`: the `String a` parameter from the enclosing
    scope!  This is the closure parameter.

This does make sense.  The problem is that it's *selective*: only
variables used within `Example` become extra parameters.
If the `Example` constructor is updated to remove the
`System.out.println(a)` statement, then `a` is no longer used, and
is no longer present as a constructor parameter.

The only way I found to "reasonably" determine if a constructor
parameter was a closure parameter was by checking all fields in the
class with names starting with `val$`, and then comparing the types
of those fields to types within the enclosing method's descriptor.
I can't think of a way to avoid using `val$`. :-()

[0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor added a commit to jonpryor/java.interop that referenced this issue Oct 14, 2021
Context: dotnet#858
Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720
Context: https://openjdk.java.net/projects/jigsaw/
Context: xamarin/monodroid@c9e5cbd

JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar`
with a set of `.jmod` files.

Thus, as of JDK 9, there is no `.jar` file to try to parse with
`class-parse`, only `.jmod` files!

A `.jmod` file, in turn, is still a ZIP container, much like `.jar`
files, but:

 1. With a different internal directory structure, and
 2. With a custom file header.

The result of (2) is that while `unzip -l` can show the contents of
a `.jmod` file -- with a warning -- `System.IO.Compression.ZipArchive`
cannot process the file:

	% mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod
	class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.
	<api
	  api-source="class-parse" />

Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod`
files by using `PartialStream` (73096d9) to skip the first 4 bytes.

Once able to read a `.jmod` file, *warnings* appeared, a'la

	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	  Local variables array has 2 entries ('LocalVariableTableAttribute(
	      LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0),
	      LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1),
	      LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2),
	      LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))'
	  ); descriptor has 3 entries!
	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries!

This was a variation on the "JDK 8?" block that previously didn't
have much detail, in part because it didn't have a repro.  Now we
have a repro, based on [JDK code][0] which contains a class
declaration within a method declaration

	public List<String> staticActionWithGenerics(…) {
	    class MyStringList extends ArrayList<String> {
	        public MyStringList(String a, int b) {
	        }
	        public String get(int index) {
	            return value.toString();
	        }
	    }
	}

The deal is that `staticActionWithGenerics()` contains a `MyStringList`
class, which in turn contains a constructor with two parameters.
*However*, as far as Java bytecode is concerned, the constructor
contains *4* local variables with StartPC==0, which is what we use to
infer parameter names.

Refactor, cleanup, and otherwise rewrite huge swaths of `Methods.cs`
to get to a "happy medium" of:

  * No warnings from our unit tests, ensured by updating
    `ClassFileFixture` to have a `[SetUp]` method which sets the
    `Log.OnLog` field to a delegate which may call `Assert.Fail()`
    when invoked.  (This asserts for all non-Kotlin messages.)

  * No warnings when processing `java.base.jmod`:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null
        # no error messages

  * No warnings when processing Android API-31:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null
        # no error messages

Aside: closures are *weird* and finicky.
Consider the following Java code:

	class ClosureDemo {
	    public void m(String a) {
	        class Example {
	            public Example(int x) {
	                System.out.println (a);
	            }
	        }
	    }
	}

It looks like the JNI signature for the `Example` constructor might
be `(I)V`, but isn't.  It is instead:

	(LClosureDemo;ILjava/lang/String;)V

Breaking that down:

  * `LClosureDemo;`: `Example` is an inner class, and thus has an
    implicit reference to the containing type.  OK, easy to forget.

  * `I`: the `int x` parameter.  Expected.

  * `Ljava/lang/String`: the `String a` parameter from the enclosing
    scope!  This is the closure parameter.

This does make sense.  The problem is that it's *selective*: only
variables used within `Example` become extra parameters.
If the `Example` constructor is updated to remove the
`System.out.println(a)` statement, then `a` is no longer used, and
is no longer present as a constructor parameter.

The only way I found to "reasonably" determine if a constructor
parameter was a closure parameter was by checking all fields in the
class with names starting with `val$`, and then comparing the types
of those fields to types within the enclosing method's descriptor.
I can't think of a way to avoid using `val$`. :-(

[0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor added a commit to jonpryor/java.interop that referenced this issue Oct 14, 2021
Context: dotnet#858
Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720
Context: https://openjdk.java.net/projects/jigsaw/
Context: xamarin/monodroid@c9e5cbd

JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar`
with a set of `.jmod` files.

Thus, as of JDK 9, there is no `.jar` file to try to parse with
`class-parse`, only `.jmod` files!

A `.jmod` file, in turn, is still a ZIP container, much like `.jar`
files, but:

 1. With a different internal directory structure, and
 2. With a custom file header.

The result of (2) is that while `unzip -l` can show and extract the
contents of a `.jmod` file -- with a warning --
`System.IO.Compression.ZipArchive` cannot process the file:

	% mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod
	class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.
	<api
	  api-source="class-parse" />

Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod`
files by using `PartialStream` (73096d9) to skip the first 4 bytes.

Once able to read a `.jmod` file, *warnings* appeared, a'la

	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	  Local variables array has 2 entries ('LocalVariableTableAttribute(
	      LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0),
	      LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1),
	      LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2),
	      LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))'
	  ); descriptor has 3 entries!
	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries!

This was a variation on the "JDK 8?" block that previously didn't
have much detail, in part because it didn't have a repro.  Now we
have a repro, based on [JDK code][0] which contains a class
declaration within a method declaration

	public List<String> staticActionWithGenerics(…) {
	    class MyStringList extends ArrayList<String> {
	        public MyStringList(String a, int b) {
	        }
	        public String get(int index) {
	            return unboundedList.toString() + value1.toString();
	        }
	    }
	}

The deal is that `staticActionWithGenerics()` contains a `MyStringList`
class, which in turn contains a constructor with two parameters.
*However*, as far as Java bytecode is concerned, the constructor
contains *3* local variables with StartPC==0, which is what we use to
infer parameter names.

Refactor, cleanup, and otherwise modify huge swaths of `Methods.cs`
to get to a "happy medium" of:

  * No warnings from our unit tests, ensured by updating
    `ClassFileFixture` to have a `[SetUp]` method which sets the
    `Log.OnLog` field to a delegate which may call `Assert.Fail()`
    when invoked.  (This asserts for all non-Kotlin messages.)

  * No warnings when processing `java.base.jmod`:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null
        # no error messages

  * No warnings when processing Android API-31:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null
        # no error messages

Aside: closures are *weird* and finicky.
Consider the following Java code:

	class ClosureDemo {
	    public void m(String a) {
	        class Example {
	            public Example(int x) {
	                System.out.println (a);
	            }
	        }
	    }
	}

It looks like the JNI signature for the `Example` constructor might
be `(I)V`, but isn't.  It is instead:

	(LClosureDemo;ILjava/lang/String;)V

Breaking that down:

  * `LClosureDemo;`: `Example` is an inner class, and thus has an
    implicit reference to the containing type.  OK, easy to forget.

  * `I`: the `int x` parameter.  Expected.

  * `Ljava/lang/String`: the `String a` parameter from the enclosing
    scope!  This is the closure parameter.

This does make sense.  The problem is that it's *selective*: only
variables used within `Example` become extra parameters.
If the `Example` constructor is updated to remove the
`System.out.println(a)` statement, then `a` is no longer used, and
is no longer present as a constructor parameter.

The only way I found to "reasonably" determine if a constructor
parameter was a closure parameter was by checking all fields in the
class with names starting with `val$`, and then comparing the types
of those fields to types within the enclosing method's descriptor.
I can't think of a way to avoid using `val$`. :-(

Another aside: closure parameter behavior *differs* between JDK 1.8
and JDK-11: there appears to be a JDK 1.8 `javac` bug in which it
assigns the *wrong* parameter names.  Consider `MyStringList`:
The Java constructor declaration is:

	public static <T, …>
	void staticActionWithGenerics (
	        T value1, …
	        List<?> unboundedList, …)
	{
	    class MyStringList extends ArrayList<String> {
	        public MyStringList(String a, int b) {
	        }
	        // …
	    }
	}

The JNI signature for the `MyStringList` constructor is:

	(Ljava/lang/String;ILjava/util/List;Ljava/lang/Object;)V

which is:

  * `String`: parameter `a`
  * `I`: parameter `b`
  * `List`: closure parameter for `unboundedList`
  * `Object`: closure parameter for `value1`.

If we build with JDK 1.8 `javac -parameters`, the `MethodParameters`
annotation states that the closure parameters are:

	MyStringList(String a, int b, List val$value1, Object val$unboundedList);

which is *wrong*; `unboundedList` is the `List`, `value1` is `Object`!

This was fixed in JDK-11, with the `MethodParameters` annotations
specifying:

	MyStringList(String a, int b, List val$unboundedList, Object val$value1);

This means that the unit tests need to take this into consideration.

Add a new `ConfiguredJdkInfo` class, which contains code similar to
`tests/TestJVM/TestJVM.cs`: it will read `bin/Build*/JdkInfo.props`
to find the path to the JDK found in `make prepare`, and then
determine the JDK version from that directory's `release` file.

[0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor added a commit to jonpryor/java.interop that referenced this issue Oct 14, 2021
Context: dotnet#858
Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720
Context: https://openjdk.java.net/projects/jigsaw/
Context: xamarin/monodroid@c9e5cbd

JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar`
with a set of `.jmod` files.

Thus, as of JDK 9, there is no `.jar` file to try to parse with
`class-parse`, only `.jmod` files!

A `.jmod` file, in turn, is still a ZIP container, much like `.jar`
files, but:

 1. With a different internal directory structure, and
 2. With a custom file header.

The result of (2) is that while `unzip -l` can show and extract the
contents of a `.jmod` file -- with a warning --
`System.IO.Compression.ZipArchive` cannot process the file:

	% mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod
	class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.
	<api
	  api-source="class-parse" />

Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod`
files by using `PartialStream` (73096d9) to skip the first 4 bytes.

Once able to read a `.jmod` file, *warnings* appeared, a'la

	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	  Local variables array has 2 entries ('LocalVariableTableAttribute(
	      LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0),
	      LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1),
	      LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2),
	      LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))'
	  ); descriptor has 3 entries!
	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries!

This was a variation on the "JDK 8?" block that previously didn't
have much detail, in part because it didn't have a repro.  Now we
have a repro, based on [JDK code][0] which contains a class
declaration within a method declaration

	public List<String> staticActionWithGenerics(…) {
	    class MyStringList extends ArrayList<String> {
	        public MyStringList(String a, int b) {
	        }
	        public String get(int index) {
	            return unboundedList.toString() + value1.toString();
	        }
	    }
	}

The deal is that `staticActionWithGenerics()` contains a `MyStringList`
class, which in turn contains a constructor with two parameters.
*However*, as far as Java bytecode is concerned, the constructor
contains *3* local variables with StartPC==0, which is what we use to
infer parameter names.

Refactor, cleanup, and otherwise modify huge swaths of `Methods.cs`
to get to a "happy medium" of:

  * No warnings from our unit tests, ensured by updating
    `ClassFileFixture` to have a `[SetUp]` method which sets the
    `Log.OnLog` field to a delegate which may call `Assert.Fail()`
    when invoked.  (This asserts for all non-Kotlin messages.)

  * No warnings when processing `java.base.jmod`:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null
        # no error messages

  * No warnings when processing Android API-31:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null
        # no error messages

Aside: closures are *weird* and finicky.
Consider the following Java code:

	class ClosureDemo {
	    public void m(String a) {
	        class Example {
	            public Example(int x) {
	                System.out.println (a);
	            }
	        }
	    }
	}

It looks like the JNI signature for the `Example` constructor might
be `(I)V`, but isn't.  It is instead:

	(LClosureDemo;ILjava/lang/String;)V

Breaking that down:

  * `LClosureDemo;`: `Example` is an inner class, and thus has an
    implicit reference to the containing type.  OK, easy to forget.

  * `I`: the `int x` parameter.  Expected.

  * `Ljava/lang/String`: the `String a` parameter from the enclosing
    scope!  This is the closure parameter.

This does make sense.  The problem is that it's *selective*: only
variables used within `Example` become extra parameters.
If the `Example` constructor is updated to remove the
`System.out.println(a)` statement, then `a` is no longer used, and
is no longer present as a constructor parameter.

The only way I found to "reasonably" determine if a constructor
parameter was a closure parameter was by checking all fields in the
class with names starting with `val$`, and then comparing the types
of those fields to types within the enclosing method's descriptor.
I can't think of a way to avoid using `val$`. :-(

Another aside: closure parameter behavior *differs* between JDK 1.8
and JDK-11: there appears to be a JDK 1.8 `javac` bug in which it
assigns the *wrong* parameter names.  Consider `MyStringList`:
The Java constructor declaration is:

	public static <T, …>
	void staticActionWithGenerics (
	        T value1, …
	        List<?> unboundedList, …)
	{
	    class MyStringList extends ArrayList<String> {
	        public MyStringList(String a, int b) {
	        }
	        // …
	    }
	}

The JNI signature for the `MyStringList` constructor is:

	(Ljava/lang/String;ILjava/util/List;Ljava/lang/Object;)V

which is:

  * `String`: parameter `a`
  * `I`: parameter `b`
  * `List`: closure parameter for `unboundedList`
  * `Object`: closure parameter for `value1`.

If we build with JDK 1.8 `javac -parameters`, the `MethodParameters`
annotation states that the closure parameters are:

	MyStringList(String a, int b, List val$value1, Object val$unboundedList);

which is *wrong*; `unboundedList` is the `List`, `value1` is `Object`!

This was fixed in JDK-11, with the `MethodParameters` annotations
specifying:

	MyStringList(String a, int b, List val$unboundedList, Object val$value1);

This means that the unit tests need to take this into consideration.

Add a new `ConfiguredJdkInfo` class, which contains code similar to
`tests/TestJVM/TestJVM.cs`: it will read `bin/Build*/JdkInfo.props`
to find the path to the JDK found in `make prepare`, and then
determine the JDK version from that directory's `release` file.

[0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor added a commit to jonpryor/java.interop that referenced this issue Oct 15, 2021
Context: dotnet#858
Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720
Context: https://openjdk.java.net/projects/jigsaw/
Context: xamarin/monodroid@c9e5cbd

JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar`
with a set of `.jmod` files.

Thus, as of JDK 9, there is no `.jar` file to try to parse with
`class-parse`, only `.jmod` files!

A `.jmod` file, in turn, is still a ZIP container, much like `.jar`
files, but:

 1. With a different internal directory structure, and
 2. With a custom file header.

The result of (2) is that while `unzip -l` can show and extract the
contents of a `.jmod` file -- with a warning --
`System.IO.Compression.ZipArchive` cannot process the file:

	% mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod
	class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.
	<api
	  api-source="class-parse" />

Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod`
files by using `PartialStream` (73096d9) to skip the first 4 bytes.

Once able to read a `.jmod` file, lots of debug messages appeared
while parsing `java.base.jmod`, a'la:

	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	  Local variables array has 2 entries ('LocalVariableTableAttribute(
	      LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0),
	      LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1),
	      LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2),
	      LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))'
	  ); descriptor has 3 entries!
	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries!

This was a variation on the "JDK 8?" block that previously didn't
have much detail, in part because it didn't have a repro.  Now we
have a repro, based on [JDK code][0] which contains a class
declaration within a method declaration

	// Java
	public List<String> staticActionWithGenerics(…) {
	    class MyStringList extends ArrayList<String> {
	        public MyStringList(String a, int b) {
	        }
	        public String get(int index) {
	            return unboundedList.toString() + value1.toString();
	        }
	    }
	}

The deal is that `staticActionWithGenerics()` contains a `MyStringList`
class, which in turn contains a constructor with two parameters.
*However*, as far as Java bytecode is concerned, the constructor
contains *3* local variables with StartPC==0, which is what we use to
infer parameter names.

Refactor, cleanup, and otherwise modify huge swaths of `Methods.cs`
to get to a "happy medium" of:

  * No warnings from our unit tests, ensured by updating
    `ClassFileFixture` to have a `[SetUp]` method which sets the
    `Log.OnLog` field to a delegate which may call `Assert.Fail()`
    when invoked.  This asserts for all messages starting with
    `class-parse: methods`, which are produced by `Methods.cs`.

  * No warnings when processing `java.base.jmod`:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null
        # no error messages

  * No warnings when processing Android API-31:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null
        # no error messages

Additionally, improve `Log.cs` so that there are `M(string)`
overloads for the existing `M(string, params object[])` methods.
This is a "sanity-preserving" change, as "innocuous-looking" code
such as `Log.Debug("{foo}")` will throw `FormatException` when the
`(string, params object[])` overload is used.

Aside: closures are *weird* and finicky.
Consider the following Java code:

	class ClosureDemo {
	    public void m(String a) {
	        class Example {
	            public Example(int x) {
	                System.out.println (a);
	            }
	        }
	    }
	}

It looks like the JNI signature for the `Example` constructor might
be `(I)V`, but isn't.  It is instead:

	(LClosureDemo;ILjava/lang/String;)V

Breaking that down:

  * `LClosureDemo;`: `Example` is an inner class, and thus has an
    implicit reference to the containing type.  OK, easy to forget.

  * `I`: the `int x` parameter.  Expected.

  * `Ljava/lang/String`: the `String a` parameter from the enclosing
    scope!  This is the closure parameter.

This does make sense.  The problem is that it's *selective*: only
variables used within `Example` become extra parameters.
If the `Example` constructor is updated to remove the
`System.out.println(a)` statement, then `a` is no longer used, and
is no longer present as a constructor parameter.

The only way I found to "reasonably" determine if a constructor
parameter was a closure parameter was by checking all fields in the
class with names starting with `val$`, and then comparing the types
of those fields to types within the enclosing method's descriptor.
I can't think of a way to avoid using `val$`. :-(

Another aside: closure parameter behavior *differs* between JDK 1.8
and JDK-11: there appears to be a JDK 1.8 `javac` bug in which it
assigns the *wrong* parameter names.  Consider `MyStringList`:
The Java constructor declaration is:

	public static <T, …>
	void staticActionWithGenerics (
	        T value1, …
	        List<?> unboundedList, …)
	{
	    class MyStringList extends ArrayList<String> {
	        public MyStringList(String a, int b) {
	        }
	        // …
	    }
	}

The JNI signature for the `MyStringList` constructor is:

	(Ljava/lang/String;ILjava/util/List;Ljava/lang/Object;)V

which is:

  * `String`: parameter `a`
  * `I`: parameter `b`
  * `List`: closure parameter for `unboundedList`
  * `Object`: closure parameter for `value1`.

If we build with JDK 1.8 `javac -parameters`, the `MethodParameters`
annotation states that the closure parameters are:

	MyStringList(String a, int b, List val$value1, Object val$unboundedList);

which is *wrong*; `unboundedList` is the `List`, `value1` is `Object`!

This was fixed in JDK-11, with the `MethodParameters` annotations
specifying:

	MyStringList(String a, int b, List val$unboundedList, Object val$value1);

This means that the unit tests need to take this into consideration.

Add a new `ConfiguredJdkInfo` class, which contains code similar to
`tests/TestJVM/TestJVM.cs`: it will read `bin/Build*/JdkInfo.props`
to find the path to the JDK found in `make prepare`, and then
determine the JDK version from that directory's `release` file.

[0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor added a commit that referenced this issue Oct 15, 2021
Context: #858
Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720
Context: https://openjdk.java.net/projects/jigsaw/
Context: xamarin/monodroid@c9e5cbd

JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar`
with a set of `.jmod` files.

Thus, as of JDK 9, there is no `.jar` file to try to parse with
`class-parse`, only `.jmod` files!

A `.jmod` file, in turn, is still a ZIP container, much like `.jar`
files, but:

 1. With a different internal directory structure, and
 2. With a custom file header.

The result of (2) is that while `unzip -l` can show and extract the
contents of a `.jmod` file -- with a warning --
`System.IO.Compression.ZipArchive` cannot process the file:

	% mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod
	class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.
	<api
	  api-source="class-parse" />

Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod`
files by using `PartialStream` (73096d9) to skip the first 4 bytes.

Once able to read a `.jmod` file, lots of debug messages appeared
while parsing `java.base.jmod`, a'la:

	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	  Local variables array has 2 entries ('LocalVariableTableAttribute(
	      LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0),
	      LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1),
	      LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2),
	      LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))'
	  ); descriptor has 3 entries!
	class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
	Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries!

This was a variation on the "JDK 8?" block that previously didn't
have much detail, in part because it didn't have a repro.  Now we
have a repro, based on [JDK code][0] which contains a class
declaration within a method declaration

	// Java
	public List<String> staticActionWithGenerics(…) {
	    class MyStringList extends ArrayList<String> {
	        public MyStringList(String a, int b) {
	        }
	        public String get(int index) {
	            return unboundedList.toString() + value1.toString();
	        }
	    }
	}

The deal is that `staticActionWithGenerics()` contains a `MyStringList`
class, which in turn contains a constructor with two parameters.
*However*, as far as Java bytecode is concerned, the constructor
contains *3* local variables with StartPC==0, which is what we use to
infer parameter names.

Refactor, cleanup, and otherwise modify huge swaths of `Methods.cs`
to get to a "happy medium" of:

  * No warnings from our unit tests, ensured by updating
    `ClassFileFixture` to have a `[SetUp]` method which sets the
    `Log.OnLog` field to a delegate which may call `Assert.Fail()`
    when invoked.  This asserts for all messages starting with
    `class-parse: methods`, which are produced by `Methods.cs`.

  * No warnings when processing `java.base.jmod`:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null
        # no error messages

  * No warnings when processing Android API-31:

        % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null
        # no error messages

Additionally, improve `Log.cs` so that there are `M(string)`
overloads for the existing `M(string, params object[])` methods.
This is a "sanity-preserving" change, as "innocuous-looking" code
such as `Log.Debug("{foo}")` will throw `FormatException` when the
`(string, params object[])` overload is used.

Aside: closures are *weird* and finicky.
Consider the following Java code:

	class ClosureDemo {
	    public void m(String a) {
	        class Example {
	            public Example(int x) {
	                System.out.println (a);
	            }
	        }
	    }
	}

It looks like the JNI signature for the `Example` constructor might
be `(I)V`, but isn't.  It is instead:

	(LClosureDemo;ILjava/lang/String;)V

Breaking that down:

  * `LClosureDemo;`: `Example` is an inner class, and thus has an
    implicit reference to the containing type.  OK, easy to forget.

  * `I`: the `int x` parameter.  Expected.

  * `Ljava/lang/String`: the `String a` parameter from the enclosing
    scope!  This is the closure parameter.

This does make sense.  The problem is that it's *selective*: only
variables used within `Example` become extra parameters.
If the `Example` constructor is updated to remove the
`System.out.println(a)` statement, then `a` is no longer used, and
is no longer present as a constructor parameter.

The only way I found to "reasonably" determine if a constructor
parameter was a closure parameter was by checking all fields in the
class with names starting with `val$`, and then comparing the types
of those fields to types within the enclosing method's descriptor.
I can't think of a way to avoid using `val$`. :-(

Another aside: closure parameter behavior *differs* between JDK 1.8
and JDK-11: there appears to be a JDK 1.8 `javac` bug in which it
assigns the *wrong* parameter names.  Consider `MyStringList`:
The Java constructor declaration is:

	public static <T, …>
	void staticActionWithGenerics (
	        T value1, …
	        List<?> unboundedList, …)
	{
	    class MyStringList extends ArrayList<String> {
	        public MyStringList(String a, int b) {
	        }
	        // …
	    }
	}

The JNI signature for the `MyStringList` constructor is:

	(Ljava/lang/String;ILjava/util/List;Ljava/lang/Object;)V

which is:

  * `String`: parameter `a`
  * `I`: parameter `b`
  * `List`: closure parameter for `unboundedList`
  * `Object`: closure parameter for `value1`.

If we build with JDK 1.8 `javac -parameters`, the `MethodParameters`
annotation states that the closure parameters are:

	MyStringList(String a, int b, List val$value1, Object val$unboundedList);

which is *wrong*; `unboundedList` is the `List`, `value1` is `Object`!

This was fixed in JDK-11, with the `MethodParameters` annotations
specifying:

	MyStringList(String a, int b, List val$unboundedList, Object val$value1);

This means that the unit tests need to take this into consideration.

Add a new `ConfiguredJdkInfo` class, which contains code similar to
`tests/TestJVM/TestJVM.cs`: it will read `bin/Build*/JdkInfo.props`
to find the path to the JDK found in `make prepare`, and then
determine the JDK version from that directory's `release` file.

[0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 6, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `Java.Interop.Java*Array` types.
This should help reduce marshaling logic & overhead, as arrays don't
need to be "deep copied".

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is currently:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 8, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `Java.Interop.Java*Array` types.
This should help reduce marshaling logic & overhead, as arrays don't
need to be "deep copied".  The exception is C# `params` arrays, which
continue to be bound as arrays, and are marshaled via an appropriate
`Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 8, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `Java.Interop.Java*Array` types.
This should help reduce marshaling logic & overhead, as arrays don't
need to be "deep copied".  The exception is C# `params` arrays, which
continue to be bound as arrays, and are marshaled via an appropriate
`Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 10, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `Java.Interop.Java*Array` types.
This should help reduce marshaling logic & overhead, as arrays don't
need to be "deep copied".  The exception is C# `params` arrays, which
continue to be bound as arrays, and are marshaled via an appropriate
`Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 10, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `Java.Interop.Java*Array` types.
This should help reduce marshaling logic & overhead, as arrays don't
need to be "deep copied".  The exception is C# `params` arrays, which
continue to be bound as arrays, and are marshaled via an appropriate
`Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 11, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 16, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ TODO: Other API Changes? ~~

We should "unify" `java.lang.Object` and `System.Object`.  Consider
`java.lang.Class`:

	/* partial */ class Class<T> {
	    public boolean isInstance(java.lang.Object);
	    public java.lang.Object[] getSigners();
	}

If we unify `java.lang.Object` and `System.Object`, we'd have a
binding of:

	partial class Class {
	    public bool IsInstance (object value);
	    public object[] GetSigners();
	}

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 16, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ TODO: Other API Changes? ~~

We should "unify" `java.lang.Object` and `System.Object`.  Consider
`java.lang.Class`:

	/* partial */ class Class<T> {
	    public boolean isInstance(java.lang.Object);
	    public java.lang.Object[] getSigners();
	}

If we unify `java.lang.Object` and `System.Object`, we'd have a
binding of:

	partial class Class {
	    public bool IsInstance (object value);
	    public object[] GetSigners();
	}

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 16, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ TODO: Other API Changes? ~~

We should "unify" `java.lang.Object` and `System.Object`.  Consider
`java.lang.Class`:

	/* partial */ class Class<T> {
	    public boolean isInstance(java.lang.Object);
	    public java.lang.Object[] getSigners();
	}

If we unify `java.lang.Object` and `System.Object`, we'd have a
binding of:

	partial class Class {
	    public bool IsInstance (object value);
	    public object[] GetSigners();
	}

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 17, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ TODO: Other API Changes? ~~

We should "unify" `java.lang.Object` and `System.Object`.  Consider
`java.lang.Class`:

	/* partial */ class Class<T> {
	    public boolean isInstance(java.lang.Object);
	    public java.lang.Object[] getSigners();
	}

If we unify `java.lang.Object` and `System.Object`, we'd have a
binding of:

	partial class Class {
	    public bool IsInstance (object value);
	    public object[] GetSigners();
	}

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 17, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ TODO: Other API Changes? ~~

We should "unify" `java.lang.Object` and `System.Object`.  Consider
`java.lang.Class`:

	/* partial */ class Class<T> {
	    public boolean isInstance(java.lang.Object);
	    public java.lang.Object[] getSigners();
	}

If we unify `java.lang.Object` and `System.Object`, we'd have a
binding of:

	partial class Class {
	    public bool IsInstance (object value);
	    public object[] GetSigners();
	}

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 18, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ TODO: Other API Changes? ~~

We should "unify" `java.lang.Object` and `System.Object`.  Consider
`java.lang.Class`:

	/* partial */ class Class<T> {
	    public boolean isInstance(java.lang.Object);
	    public java.lang.Object[] getSigners();
	}

If we unify `java.lang.Object` and `System.Object`, we'd have a
binding of:

	partial class Class {
	    public bool IsInstance (object value);
	    public object[] GetSigners();
	}

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit to jonpryor/java.interop that referenced this issue Nov 19, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ TODO: Other API Changes? ~~

We should "unify" `java.lang.Object` and `System.Object`.  Consider
`java.lang.Class`:

	/* partial */ class Class<T> {
	    public boolean isInstance(java.lang.Object);
	    public java.lang.Object[] getSigners();
	}

If we unify `java.lang.Object` and `System.Object`, we'd have a
binding of:

	partial class Class {
	    public bool IsInstance (object value);
	    public object[] GetSigners();
	}

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit to jonpryor/java.interop that referenced this issue Dec 5, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ TODO: Other API Changes? ~~

We should "unify" `java.lang.Object` and `System.Object`.  Consider
`java.lang.Class`:

	/* partial */ class Class<T> {
	    public boolean isInstance(java.lang.Object);
	    public java.lang.Object[] getSigners();
	}

If we unify `java.lang.Object` and `System.Object`, we'd have a
binding of:

	partial class Class {
	    public bool IsInstance (object value);
	    public object[] GetSigners();
	}

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit to jonpryor/java.interop that referenced this issue Dec 5, 2021
Context: dotnet#858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Object`
  * `java.lang.Throwable`
  * `java.lang.Class`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

~~ Binding changes vs. Xamarin.Android ~~

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

~~ TODO: Marshal Methods? ~~

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

For Desktop, JCW's shouldn't have all the methods to register.
Instead, use the `jnimarshalmethod-gen`-originated strategy of
`[JniAddNativeMethodRegistrationAttribute]` within the binding, and
then have it use `MarshalMethodBuilder` to generate the marshal
methods.  Need to update `MarshalMethodBuilder` to look for overrides
in addition to methods with [`JavaCallable`], which in turn will
require an equivalent to `Android.Runtime.RegisterAttribute(…)`.

Perhaps `JniMethodSignatureAttribute(string name, string sig)`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ TODO: Other API Changes? ~~

We should "unify" `java.lang.Object` and `System.Object`.  Consider
`java.lang.Class`:

	/* partial */ class Class<T> {
	    public boolean isInstance(java.lang.Object);
	    public java.lang.Object[] getSigners();
	}

If we unify `java.lang.Object` and `System.Object`, we'd have a
binding of:

	partial class Class {
	    public bool IsInstance (object value);
	    public object[] GetSigners();
	}

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  Why?  How do we fix that to instead
bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`?

Currently, it's differently *worse*; I don't know why, but `__id`
is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

Fixing unit tests...
jonpryor added a commit that referenced this issue Dec 7, 2021
Context: #858

What do *I* want?  To be able to use our wonderful Java binding
infrastructure against *Desktop Java*, not just Android.

At the same time, I don't want "Android-isms" "leaking" into such a
binding.  *Just* Java.Interop, no xamarin-android.

"Take over" the `generator --codegen-target=JavaInterop1` format
so that it *isn't* useful for Xamarin.Android, and is instead usable
for non-Android usage.

This is a work-in-progress, and *far* from complete.  For prototype
purposes, this *only* binds:

  * `java.lang.Class`
  * `java.lang.Math`
  * `java.lang.Number`
  * `java.lang.Object`
  * `java.lang.Throwable`

The `Java.Base` binding is only for .NET 6 and above.  I'm not
interested in .NET Standard support at this point in time.

~~ Build Changes ~~

Additionally, this `Java.Base` binding is only for the
`java.base.jmod` file provided by JDK-11.  `make prepare` is updated
to look for a JDK-11 installation path.

~~ Test Changes ~~

Update `samples/Hello` so that it (1) works, and (2) instantiates the
`Java.Lang.Object` binding:

	dotnet run --project samples/Hello

Update `tests/generator-Tests` so that
`tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1`
is now used *exclusively* for `generator --codegen-target=JavaInterop1`
output; `generator --codegen-target=XAJavaInterop1` now has its own
separate files in
`tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1`.
(This contributes to much of the commit diff.)

Update `tests/generator-Tests/SupportFiles` so that types in
`Android.*` are excluded when `JAVA_INTEROP1` is `#define`d, and
update the unit test infrastructure so that building for `JavaInterop1`
causes `JAVA_INTEROP1` to be `#define`d.  This allows many of the unit
tests to ensure that *some* `JavaInterop1` constructs *compile*.

However, not all unit tests compile under `JavaInterop1`.  The new
`BaseGeneratorTest.TryJavaInterop1` property can be set to false to
prevent compiling a test suite using `JavaInterop1`.  This will allow
us to slowly implement `JavaInterop1` support over time.

The build logs will also now emit a command-line that can be used to
manually compile unit test code.  See e.g.
`bin/TestDebug/TestOutput-generator-Tests.txt`.

~~ API Changes ~~

Update `Java.Interop.JavaObject` so that
`JavaObject.DisposeUnlessReferenced()` is now `virtual`.
Override `DisposeUnlessReferenced()` from the `Java*Array` types
so that if the instance was created via the new
`JniEnvironment.Arrays.CreateMarshal*Array()` methods, the array
instance will be disposed.  This is intended for marshaling array
parameters:

	public void WithArray(int[] array)
	{
	    const string __id = "withArray.[I";
	    var native_array = JniEnvironment.Arrays.CreateMarshalInt32Array (array);
	    try {
	        JniArgumentValue* __args = stackalloc JniArgumentValue [1];
	        __args [0] = new JniArgumentValue (native_array);
	        _members.StaticMethods.InvokeVoidMethod (__id, __args);
	    } finally {
	        if (array != null) native_array.DisposeUnlessReferenced ();
	    }
	}

Add `Java.Interop.JavaTypeParametersAttribute(string[] typeParameters)`
from Xamarin.Android.

~~ Bindings vs. Xamarin.Android ~~

Pull in `src/Java.Base/Transforms/map.csv` [from xamarin-android][0],
removing the `android*` types.

Instead of `[Android.Runtime.RegisterAttribute]` on types, use
`[Java.Interop.JniTypeSignatureAttribute]`.

Java arrays are bound as appropriate `IList<T>`, using the
`Java.Interop.Java*Array` types as an intermediary.  This should help
reduce marshaling logic & overhead, as if the "source" array is a
`Java*Array`, it doesn't need to be "deep copied".  The exception is
C# `params` arrays, which continue to be bound as arrays, and are
marshaled via an appropriate `Java*Array` type.

`java.io.InputStream` isn't bound as `System.IO.Stream`, etc.

"Java.Interop-style" constructors are used (25de1f3), e.g.

	// This
	DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options);

	// Not Xamarin.Android-style
	DeclaringType (IntPtr handle, JniHandleOwnership transfer);

"Java.Interop-style" wrapper construction is used, e.g.

	// This
	var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose);

	// Not this
	var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle);

~~ TODO: Marshal Methods ~~

Marshal methods are currently skipped.  Java-to-managed invocations
are not currently supported.

Xamarin.Android uses Java Callable Wrappers + `Runtime.register()`
to specify which methods to register, via lots of reflection, etc.

Proposal: For Desktop, JCW's shouldn't need to specify all the
methods to register.  Instead, use the `jnimarshalmethod-gen`-
originated strategy of `[JniAddNativeMethodRegistrationAttribute]`
within the binding, and then have it use `MarshalMethodBuilder` to
generate the marshal methods.  Need to update `MarshalMethodBuilder`
to look for overrides in addition to methods with `[JavaCallable]`,
which in turn may require an equivalent to
`[Android.Runtime.RegisterAttribute(…)]`.

Perhaps `[JniMethodSignatureAttribute(string name, string sig)]`?

In the meantime, `Java.Base` will skip all marshal-method logic
plus runtime method generation.  Leave that for later.

~~ TODO: Other Binding Changes? ~~

We should eventually "unify" `java.lang.Object` and `System.Object`.
Consider `java.lang.Class`:

	/* partial */ class Class<T> {
	    public boolean isInstance(java.lang.Object);
	    public java.lang.Object[] getSigners();
	}

If we unify `java.lang.Object` and `System.Object`, we'd have a
binding of:

	partial class Class {
	    public bool IsInstance (object value);
	    public IList<object> GetSigners();
	}

~~ Open Questions ~~

What's up with `java.lang.Class.getAnnotationsByType()`?

During an iteration of this PR, I got:

	public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass)
	{
	    const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;";

From `__id` we see that the Java return type is `Annotation[]`, yet
we bind it as an `Object` array?  This is because of
[#669][1].

That said, it's currently "differently *worse*"; I don't know why,
but `__id` is now:

	const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;";

i.e. the return type is an `Object` array instead of an `Annotation`
array, which is wrong, as per `javap`:

	% javap -s java.lang.Class
	…
	  public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>);
	    descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;

This needs additional investigation.

[0]: https://github.com/xamarin/xamarin-android/blob/99523feab02e8622a3357e9e6a025f5afc44c970/src/Mono.Android/map.csv
[1]: #669
@jonpryor jonpryor self-assigned this Jul 19, 2022
@jonpryor jonpryor changed the title Desktop Java Bindings Desktop JDK-11 Java.Base Binding Jul 19, 2022
jonpryor added a commit that referenced this issue Sep 14, 2023
Fixes: #910

Context: bc5bcf4
Context: #858

Consider the Java `java.lang.Runnable` interface:

	package java.lang;
	public interface Runnable {
	    void run ();
	}

This is bound as:

	package Java.Lang;
	public interface IRunnable : IJavaPeerable {
	    void Run ();
	}

with some slight differences depending on whether we're dealing with
.NET Android (`generator --codegen-target=xajavainterop1`) or
`src/Java.Base` (`generator --codegen-target=javainterop1`).

Now, assume a Java API + corresponding binding which returns a
`Runnable` instance:

	package example;
	public class Whatever {
	    public static Runnable createRunnable();
	}

You can invoke `IRunnable.Run()` on the return value:

	IRunnable r = Whatever.CreateRunnable();
	r.Run();

but how does that work?

This works via an "interface Invoker", which is a class emitted by
`generator` which implements the interface and invokes the interface
methods through JNI:

	internal partial class IRunnableInvoker : Java.Lang.Object, IRunnable {
	    public void Run() => …
	}

Once Upon A Time™, the interface invoker implementation mirrored that
of classes: a static `IntPtr` field held the `jmethodID` value, which
would be looked up on first-use and cached for subsequent invocations:

	partial class IRunnableInvoker {
	    static IntPtr id_run;
	    public unsafe void Run() {
	        if (id_run == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "run", "()V");
	        JNIEnv.CallVoidMethod (Handle, id_run, …);
	    }
	}

This approach works until you have interface inheritance and methods
which come from different interfaces:

	package android.view;
	public /* partial */ interface ViewManager {
	    void addView(View view, ViewGroup.LayoutParams params);
	}
	public /* partial */ interface WindowManager extends ViewManager {
	    void removeViewImmediate(View view);
	}

This would be bound as:

	namespace Android.Views;
	public partial interface IViewManager : IJavaPeerable {
	    void AddView (View view, ViewGroup.LayoutParams @params);
	}
	public partial IWindowManager : IViewManager {
	    void RemoveViewImmediate (View view);
	}
	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    static IntPtr id_addView;
	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        if (id_addView == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "addView", "…");
	        JNIEnv.CallVoidMethod (Handle, id_addView, …);
	    }
	}

Unfortunately, *invoking* `IViewManager.AddView()` through an
`IWindowManagerInvoker` would crash!

	D/dalvikvm( 6645): GetMethodID: method not found: Landroid/view/WindowManager;.addView:(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V
	I/MonoDroid( 6645): UNHANDLED EXCEPTION: Java.Lang.NoSuchMethodError: Exception of type 'Java.Lang.NoSuchMethodError' was thrown.
	I/MonoDroid( 6645): at Android.Runtime.JNIEnv.GetMethodID (intptr,string,string)
	I/MonoDroid( 6645): at Android.Views.IWindowManagerInvoker.AddView (Android.Views.View,Android.Views.ViewGroup/LayoutParams)
	I/MonoDroid( 6645): at Mono.Samples.Hello.HelloActivity.OnCreate (Android.OS.Bundle)
	I/MonoDroid( 6645): at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr)
	I/MonoDroid( 6645): at (wrapper dynamic-method) object.ecadbe0b-9124-445e-a498-f351075f6c89 (intptr,intptr,intptr)

Interfaces are not classes, and this is one of the places that this
is most apparent.  Because of this crash, we had to use *instance*
`jmethodID` caches:

	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    IntPtr id_addView;
	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        if (id_addView == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "addView", "…");
	        JNIEnv.CallVoidMethod (Handle, id_addView, …);
	    }
	}

Pro: no more crash!

Con: *every different instance* of `IWindowManagerInvoker` needs to
separately lookup whatever methods are required.  There is *some*
caching, so repeated calls to `AddView()` on the same instance will
hit the cache, but if you obtain a different `IWindowManager`
instance, `jmethodID` values will need to be looked up again.

This was "fine", until #858 enters the picture:
interface invokers were full of Android-isms --
`Android.Runtime.JNIEnv.GetMethodID()`! -- and thus ***not*** APIs
that @jonpryor wished to expose within desktop Java.Base bindings.

Enter `generator --lang-features=emit-legacy-interface-invokers`:
when *not* specified, interface invokers will now use
`JniPeerMembers` for method lookup and invocation, allowing
`jmethodID` values to be cached *across* instances.  In order to
prevent the runtime crash, an interface may have *multiple*
`JniPeerMembers` values, one per inherited interface, which is used
to invoke methods from that interface.

`IWindowManagerInvoker` now becomes:

	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    static readonly JniPeerMembers _members_ViewManager = …;
	    static readonly JniPeerMembers _members_WindowManager = …;

	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        const string __id = "addView.…";
	        _members_ViewManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …);
	    }

	    public void RemoveViewImmediate(View view)
	    {
	        const string __id = "removeViewImmediate.…";
	        _members_WindowManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …);
	    }
	}

This has two advantages:

 1. More caching!
 2. Desktop `Java.Base` binding can now have interface invokers.

TODO: Performance?  How much better is this?
Update tests/invocation-overhead.

Update `tests/generator-Tests` expected output.
Note: to keep this patch small, only JavaInterop1 output uses the
new pattern.  XAJavaInterop1 output is unchanged, *even though*
it will use the new output *by default*.

Added [CS0114][0] to `$(NoWarn)` in `Java.Base.csproj` to ignore
warnings such as:

	…/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.ICharSequence.cs(195,25): warning CS0114:
	'ICharSequenceInvoker.ToString()' hides inherited member 'Object.ToString()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.

Ignoring CS0114 is also done in `Mono.Android.dll` as well, so this
is not a new or unique requirement.

Update `Java.Interop.dll` so that
`JniRuntime.JniValueManager.GetActivationConstructor()` now knows
about and looks for `*Invoker` types, then uses the activation
constructor from the `*Invoker` type when the source type is an
abstract `class` or `interface`.

Update `tests/Java.Base-Tests` to test for implicit `*Invoker` lookup
and invocation support.

[0]: https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0114
jonpryor added a commit that referenced this issue Oct 26, 2023
#1145)

Fixes: #910

Context: bc5bcf4
Context: #858

Consider the Java `java.lang.Runnable` interface:

	package java.lang;
	public interface Runnable {
	    void run ();
	}

This is bound as:

	package Java.Lang;
	public interface IRunnable : IJavaPeerable {
	    void Run ();
	}

with some slight differences depending on whether we're dealing with
.NET Android (`generator --codegen-target=xajavainterop1`) or
`src/Java.Base` (`generator --codegen-target=javainterop1`).

Now, assume a Java API + corresponding binding which returns a
`Runnable` instance:

	package example;
	public class Whatever {
	    public static Runnable createRunnable();
	}

You can invoke `IRunnable.Run()` on the return value:

	IRunnable r = Whatever.CreateRunnable();
	r.Run();

but how does that work?

This works via an "interface Invoker", which is a class emitted by
`generator` which implements the interface and invokes the interface
methods through JNI:

	internal partial class IRunnableInvoker : Java.Lang.Object, IRunnable {
	    public void Run() => …
	}

Once Upon A Time™, the interface invoker implementation mirrored that
of classes: a static `IntPtr` field held the `jmethodID` value, which
would be looked up on first-use and cached for subsequent invocations:

	partial class IRunnableInvoker {
	    static IntPtr id_run;
	    public unsafe void Run() {
	        if (id_run == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "run", "()V");
	        JNIEnv.CallVoidMethod (Handle, id_run, …);
	    }
	}

This approach works until you have interface inheritance and methods
which come from inherited interfaces:

	package android.view;
	public /* partial */ interface ViewManager {
	    void addView(View view, ViewGroup.LayoutParams params);
	}
	public /* partial */ interface WindowManager extends ViewManager {
	    void removeViewImmediate(View view);
	}

This would be bound as:

	namespace Android.Views;
	public partial interface IViewManager : IJavaPeerable {
	    void AddView (View view, ViewGroup.LayoutParams @params);
	}
	public partial IWindowManager : IViewManager {
	    void RemoveViewImmediate (View view);
	}
	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    static IntPtr id_addView;
	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        if (id_addView == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "addView", "…");
	        JNIEnv.CallVoidMethod (Handle, id_addView, …);
	    }
	}

Unfortunately, *invoking* `IViewManager.AddView()` through an
`IWindowManagerInvoker` would crash!

	D/dalvikvm( 6645): GetMethodID: method not found: Landroid/view/WindowManager;.addView:(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V
	I/MonoDroid( 6645): UNHANDLED EXCEPTION: Java.Lang.NoSuchMethodError: Exception of type 'Java.Lang.NoSuchMethodError' was thrown.
	I/MonoDroid( 6645): at Android.Runtime.JNIEnv.GetMethodID (intptr,string,string)
	I/MonoDroid( 6645): at Android.Views.IWindowManagerInvoker.AddView (Android.Views.View,Android.Views.ViewGroup/LayoutParams)
	I/MonoDroid( 6645): at Mono.Samples.Hello.HelloActivity.OnCreate (Android.OS.Bundle)
	I/MonoDroid( 6645): at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr)
	I/MonoDroid( 6645): at (wrapper dynamic-method) object.ecadbe0b-9124-445e-a498-f351075f6c89 (intptr,intptr,intptr)

Interfaces are not classes, and this is one of the places that this
is most apparent.  Because of this crash, we had to use *instance*
`jmethodID` caches:

	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    IntPtr id_addView;
	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        if (id_addView == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "addView", "…");
	        JNIEnv.CallVoidMethod (Handle, id_addView, …);
	    }
	}

Pro: no more crash!

Con: *every different instance* of `IWindowManagerInvoker` needs to
separately lookup whatever methods are invoked.  There is *some*
caching, so repeated calls to `AddView()` on the same instance will
hit the cache, but if you obtain a different `IWindowManager`
instance, `jmethodID` values will need to be looked up again.

This was "fine", until #858 enters the picture:
interface invokers were full of Android-isms --
`Android.Runtime.JNIEnv.GetMethodID()`! `JNIEnv.CallVoidMethod()`! --
and thus ***not*** APIs that @jonpryor wished to expose within
desktop Java.Base bindings.

Enter `generator --lang-features=emit-legacy-interface-invokers`:
when *not* specified, interface invokers will now use
`JniPeerMembers` for method lookup and invocation, allowing
`jmethodID` values to be cached *across* instances.  In order to
prevent the runtime crash, an interface may have *multiple*
`JniPeerMembers` values, one per implemented interface, which is used
to invoke methods from that interface.

`IWindowManagerInvoker` now becomes:

	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    static readonly JniPeerMembers _members_android_view_ViewManager    = …;
	    static readonly JniPeerMembers _members_android_view_WindowManager  = …;

	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        const string __id = "addView.…";
	        _members_android_view_ViewManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …);
	    }

	    public void RemoveViewImmediate(View view)
	    {
	        const string __id = "removeViewImmediate.…";
	        _members_android_view_WindowManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …);
	    }
	}

This has two advantages:

 1. More caching!
 2. Desktop `Java.Base` binding can now have interface invokers.

Update `tests/generator-Tests` expected output.
Note: to keep this patch smaller, JavaInterop1 output uses the
new pattern, and only *some* XAJavaInterop1 tests use the new
pattern.

Added [CS0114][0] to `$(NoWarn)` in `Java.Base.csproj` to ignore
warnings such as:

	…/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.ICharSequence.cs(195,25): warning CS0114:
	'ICharSequenceInvoker.ToString()' hides inherited member 'Object.ToString()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.

[Ignoring CS0114 is also done in `Mono.Android.dll` as well][1], so
this is not a new or unique requirement.

Update `Java.Interop.dll` so that
`JniRuntime.JniValueManager.GetActivationConstructor()` now knows
about and looks for `*Invoker` types, then uses the activation
constructor from the `*Invoker` type when the source type is an
abstract `class` or `interface`.

Update `tests/Java.Base-Tests` to test for implicit `*Invoker` lookup
and invocation support.


~~ Property Setters ~~

While testing on dotnet/android#8339, we hit this error
(among others, to be addressed later):

	src/Mono.Android/obj/Debug/net8.0/android-34/mcw/Android.Views.IWindowInsetsController.cs(304,41): error CS0103: The name 'behavior' does not exist in the current context

This was caused because of code such as:

	public partial interface IWindowInsetsController {
	    public unsafe int SystemBarsBehavior {
	        get {
	            const string __id = "getSystemBarsBehavior.()I";
	            try {
	                var __rm = _members_IWindowInsetsController.InstanceMethods.InvokeAbstractInt32Method (__id, this, null);
	                return __rm;
	            } finally {
	            }
	        }
	        set {
	            const string __id = "setSystemBarsBehavior.(I)V";
	            try {
	                JniArgumentValue* __args = stackalloc JniArgumentValue [1];
	                __args [0] = new JniArgumentValue (behavior);
	                _members_IWindowInsetsController.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args);
	            } finally {
	            }
	        }
	    }
	}

This happened because when emitting the property setter, we need
to update the `set*` method's parameter name to be `value` so that
the normal property setter body is emitted properly.

Update `InterfaceInvokerProperty.cs` so that the parameter name
is set to `value`.


~~ Performance ~~

What does this do for performance?

Add a new `InterfaceInvokerTiming` test fixture to
`Java.Interop-PerformanceTests.dll`, which:

 1. "Reimplements" the "legacy" and "JniPeerMembers" Invoker
    strategies
 2. For each Invoker strategy:
     a. Invokes a Java method which returns a `java.lang.Runnable`
        instance
     b. Invokes `Runnable.run()` on the instance returned by (2.a)
        …100 times.
     c. Repeat (2.a) and (2.b) 100 times.

The result is that using `JniPeerMembers` is *much* faster:

	% dotnet build tests/Java.Interop-PerformanceTests/*.csproj && \
		dotnet test --logger "console;verbosity=detailed"  bin/TestDebug-net7.0/Java.Interop-PerformanceTests.dll --filter "Name~InterfaceInvokerTiming"
	…
	 Passed InterfaceInvokers [1 s]
	 Standard Output Messages:
	## InterfaceInvokers Timing: instanceIds: 00:00:01.1095502
	## InterfaceInvokers Timing: peerMembers: 00:00:00.1400427

Using `JniPeerMembers` takes ~1/8th the time as using `jmethodID`s.

TODO: something is *probably* wrong with my test -- reviews welcome!
-- as when I increase the (2.b) iteration count, the `peerMembers`
time is largely unchanged (~0.14s), while the `instanceIds` time
increases linearly.

*Something* is wrong there.  I'm not sure what.  (Or *nothing* is
wrong, and instance `jmethodID` are just *that* bad.)


[0]: https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0114
[1]: https://github.com/xamarin/xamarin-android/blob/d5c4ec09f7658428a10bbe49c8a7a3eb2f71cb86/src/Mono.Android/Mono.Android.csproj#L12C7-L12C7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Proposed change to current functionality
Projects
None yet
Development

No branches or pull requests

2 participants