Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ public void WriteInterfaceDeclaration (InterfaceGen @interface, string indent)
writer.WriteLine ("{0}{1} partial interface {2}{3} {{", indent, @interface.Visibility, @interface.Name,
@interface.IsConstSugar ? string.Empty : @interface.Interfaces.Count == 0 || sb.Length == 0 ? " : " + GetAllInterfaceImplements () : " : " + sb.ToString ());

if (opt.SupportDefaultInterfaceMethods && @interface.HasDefaultMethods)
if (opt.SupportDefaultInterfaceMethods && (@interface.HasDefaultMethods || @interface.HasStaticMethods))
WriteClassHandle (@interface, indent + "\t", @interface.Name);

WriteInterfaceFields (@interface, indent + "\t");
Expand Down Expand Up @@ -994,16 +994,18 @@ public void WriteInterfaceMethods (InterfaceGen @interface, string indent)
WriteMethodDeclaration (m, indent, @interface, @interface.AssemblyQualifiedName + "Invoker");
}

foreach (var m in @interface.Methods.Where (m => m.IsInterfaceDefaultMethod))
WriteMethod (m, indent, @interface, true);
if (opt.SupportDefaultInterfaceMethods)
foreach (var m in @interface.Methods.Where (m => m.IsInterfaceDefaultMethod || m.IsStatic))
WriteMethod (m, indent, @interface, true);
}

public void WriteInterfaceProperties (InterfaceGen @interface, string indent)
{
foreach (var prop in @interface.Properties.Where (p => !p.Getter.IsStatic && !p.Getter.IsInterfaceDefaultMethod))
WritePropertyDeclaration (prop, indent, @interface, @interface.AssemblyQualifiedName + "Invoker");

WriteImplementedProperties (@interface.Properties.Where (p => p.Getter.IsInterfaceDefaultMethod), indent, false, @interface);
if (opt.SupportDefaultInterfaceMethods)
WriteImplementedProperties (@interface.Properties.Where (p => p.Getter.IsInterfaceDefaultMethod || p.Getter.IsStatic), indent, false, @interface);
}

public void WriteInterfacePropertyInvokers (InterfaceGen @interface, IEnumerable<Property> properties, string indent, HashSet<string> members)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ public IEnumerable<Field> GetGeneratableFields (CodeGenerationOptions options)

public bool HasDefaultMethods => GetAllMethods ().Any (m => m.IsInterfaceDefaultMethod);

public bool HasStaticMethods => GetAllMethods ().Any (m => m.IsStatic);

public bool IsConstSugar {
get {
if (Methods.Count > 0 || Properties.Count > 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
public abstract class MyInterface : Java.Lang.Object {

internal MyInterface ()
{
}
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
[Register ("DoSomething", "()V", "")]
public static unsafe void DoSomething ()
{
const string __id = "DoSomething.()V";
try {
_members.StaticMethods.InvokeVoidMethod (__id, null);
} finally {
}
}


static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (MyInterface));
}

[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
[global::System.Obsolete ("Use the 'MyInterface' type. This type will be removed in a future release.")]
public abstract class MyInterfaceConsts : MyInterface {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we emitting an empty MyInterfaceConsts type? If the interface had some constants, that would be one thing, but it doesn't...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MyInterfaceConsts type is always generated if we generate the MyInterface class. It does not attempt to detect why we are writing a MyInterface class.

Logic for determining if we are writing a MyInterface class:

interface.Fields.Any () || interface.Methods.Where (m => m.IsStatic)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we alter that logic? Particularly considering that the generated type is [Obsolete] (lol?), and there's no reason for the type to exist in a C#8 default-interface-members world order?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically a break if an existing library enables DIM, so we discussed making these [Obsolete(error:true)] so maybe eventually we could remove them. (For a separate PR.)


private MyInterfaceConsts ()
{
}
}

// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);

// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
[Register ("DoSomething", "()V", "")]
public static unsafe void DoSomething ()
{
const string __id = "DoSomething.()V";
try {
_members.StaticMethods.InvokeVoidMethod (__id, null);
} finally {
}
}

}

[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface {

internal static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterfaceInvoker));

static IntPtr java_class_ref {
get { return _members.JniPeerType.PeerReference.Handle; }
}

public override global::Java.Interop.JniPeerMembers JniPeerMembers {
get { return _members; }
}

protected override IntPtr ThresholdClass {
get { return class_ref; }
}

protected override global::System.Type ThresholdType {
get { return _members.ManagedPeerType; }
}

IntPtr class_ref;

public static IMyInterface GetObject (IntPtr handle, JniHandleOwnership transfer)
{
return global::Java.Lang.Object.GetObject<IMyInterface> (handle, transfer);
}

static IntPtr Validate (IntPtr handle)
{
if (!JNIEnv.IsInstanceOf (handle, java_class_ref))
throw new InvalidCastException (string.Format ("Unable to convert instance of type '{0}' to type '{1}'.",
JNIEnv.GetClassNameFromInstance (handle), "java.code.IMyInterface"));
return handle;
}

protected override void Dispose (bool disposing)
{
if (this.class_ref != IntPtr.Zero)
JNIEnv.DeleteGlobalRef (this.class_ref);
this.class_ref = IntPtr.Zero;
base.Dispose (disposing);
}

public IMyInterfaceInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
{
IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
this.class_ref = JNIEnv.NewGlobalRef (local_ref);
JNIEnv.DeleteLocalRef (local_ref);
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);

static unsafe int Value {
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Value' and count(parameter)=0]"
[Register ("get_Value", "()I", "Getget_ValueHandler")]
get {
const string __id = "get_Value.()I";
try {
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
return __rm;
} finally {
}
}
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Value' and count(parameter)=1 and parameter[1][@type='int']]"
[Register ("set_Value", "(I)V", "Getset_Value_IHandler")]
set {
const string __id = "set_Value.(I)V";
try {
JniArgumentValue* __args = stackalloc JniArgumentValue [1];
__args [0] = new JniArgumentValue (value);
_members.StaticMethods.InvokeVoidMethod (__id, __args);
} finally {
}
}
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
public abstract class MyInterface : Java.Lang.Object {

internal MyInterface ()
{
}
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
[Register ("DoSomething", "()V", "")]
public static unsafe void DoSomething ()
{
const string __id = "DoSomething.()V";
try {
_members.StaticMethods.InvokeVoidMethod (__id, null);
} finally {
}
}


static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (MyInterface));
}

[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
[global::System.Obsolete ("Use the 'MyInterface' type. This type will be removed in a future release.")]
public abstract class MyInterfaceConsts : MyInterface {

private MyInterfaceConsts ()
{
}
}

// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);

// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
[Register ("DoSomething", "()V", "")]
public static unsafe void DoSomething ()
{
const string __id = "DoSomething.()V";
try {
_members.StaticMethods.InvokeVoidMethod (__id, null);
} finally {
}
}

}

[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface {

internal static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterfaceInvoker));

static IntPtr java_class_ref {
get { return _members.JniPeerType.PeerReference.Handle; }
}

public override global::Java.Interop.JniPeerMembers JniPeerMembers {
get { return _members; }
}

protected override IntPtr ThresholdClass {
get { return class_ref; }
}

protected override global::System.Type ThresholdType {
get { return _members.ManagedPeerType; }
}

IntPtr class_ref;

public static IMyInterface GetObject (IntPtr handle, JniHandleOwnership transfer)
{
return global::Java.Lang.Object.GetObject<IMyInterface> (handle, transfer);
}

static IntPtr Validate (IntPtr handle)
{
if (!JNIEnv.IsInstanceOf (handle, java_class_ref))
throw new InvalidCastException (string.Format ("Unable to convert instance of type '{0}' to type '{1}'.",
JNIEnv.GetClassNameFromInstance (handle), "java.code.IMyInterface"));
return handle;
}

protected override void Dispose (bool disposing)
{
if (this.class_ref != IntPtr.Zero)
JNIEnv.DeleteGlobalRef (this.class_ref);
this.class_ref = IntPtr.Zero;
base.Dispose (disposing);
}

public IMyInterfaceInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
{
IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
this.class_ref = JNIEnv.NewGlobalRef (local_ref);
JNIEnv.DeleteLocalRef (local_ref);
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);

static unsafe int Value {
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Value' and count(parameter)=0]"
[Register ("get_Value", "()I", "Getget_ValueHandler")]
get {
const string __id = "get_Value.()I";
try {
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
return __rm;
} finally {
}
}
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Value' and count(parameter)=1 and parameter[1][@type='int']]"
[Register ("set_Value", "(I)V", "Getset_Value_IHandler")]
set {
const string __id = "set_Value.(I)V";
try {
JniArgumentValue* __args = stackalloc JniArgumentValue [1];
__args [0] = new JniArgumentValue (value);
_members.StaticMethods.InvokeVoidMethod (__id, __args);
} finally {
}
}
}

}

35 changes: 35 additions & 0 deletions tools/generator/Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,40 @@ public void WriteSealedOverriddenDefaultMethod ()
// The method should not be marked as 'virtual sealed'
Assert.False (writer.ToString ().Contains ("virtual sealed"));
}

[Test]
public void WriteStaticInterfaceMethod ()
{
// Create an interface with a static method
var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface");
iface.Methods.Add (new TestMethod (iface, "DoSomething").SetStatic ());

iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ());

generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly"));

Assert.AreEqual (GetTargetedExpected (nameof (WriteStaticInterfaceMethod)), writer.ToString ().NormalizeLineEndings ());
}

[Test]
public void WriteStaticInterfaceProperty ()
{
// Create an interface with a static property
var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface");
var prop = SupportTypeBuilder.CreateProperty (iface, "Value", "int", options);

prop.Getter.IsStatic = true;
prop.Getter.IsVirtual = false;
prop.Setter.IsStatic = true;
prop.Setter.IsVirtual = false;

iface.Properties.Add (prop);

iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ());

generator.WriteInterfaceDeclaration (iface, string.Empty);

Assert.AreEqual (GetTargetedExpected (nameof (WriteStaticInterfaceProperty)), writer.ToString ().NormalizeLineEndings ());
}
}
}
12 changes: 12 additions & 0 deletions tools/generator/Tests/generator-Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,12 @@
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\JavaInterop1\WriteMethodWithVoidReturn.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\JavaInterop1\WriteStaticInterfaceMethod.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\JavaInterop1\WriteStaticInterfaceProperty.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteClass.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down Expand Up @@ -427,6 +433,12 @@
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteProperty.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteStaticInterfaceMethod.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteStaticInterfaceProperty.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XamarinAndroid\WriteClass.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down