Skip to content

Commit eba0dc6

Browse files
committed
[generator] Add support for interface constants.
1 parent 3a10c87 commit eba0dc6

File tree

12 files changed

+250
-50
lines changed

12 files changed

+250
-50
lines changed

tools/generator/CodeGenerationOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ internal CodeGenerator CreateCodeGenerator (TextWriter writer)
7070
public bool UseShortFileNames { get; set; }
7171
public IList<GenBase> Gens {get;set;}
7272
public int ProductVersion { get; set; }
73+
public bool SupportDefaultInterfaceMethods { get; set; }
7374

7475
bool? buildingCoreAssembly;
7576
public bool BuildingCoreAssembly {

tools/generator/CodeGenerator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve
6363
UseGlobal = options.GlobalTypeNames,
6464
IgnoreNonPublicType = true,
6565
UseShortFileNames = options.UseShortFileNames,
66-
ProductVersion = options.ProductVersion
66+
ProductVersion = options.ProductVersion,
67+
SupportDefaultInterfaceMethods = options.SupportDefaultInterfaceMethods,
6768
};
6869

6970
// Load reference libraries

tools/generator/CodeGeneratorOptions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public CodeGeneratorOptions ()
3939
public string MappingReportFile { get; set; }
4040
public bool OnlyRunApiXmlAdjuster { get; set; }
4141
public string ApiXmlAdjusterOutput { get; set; }
42+
public bool SupportDefaultInterfaceMethods { get; set; }
4243

4344
public static CodeGeneratorOptions Parse (string[] args)
4445
{
@@ -85,6 +86,9 @@ public static CodeGeneratorOptions Parse (string[] args)
8586
{ "sdk-platform|api-level=",
8687
"SDK Platform {VERSION}/API level.",
8788
v => opts.ApiLevel = v },
89+
{ "default-interface-methods",
90+
"For internal use.",
91+
v => opts.SupportDefaultInterfaceMethods = v != null },
8892
{ "preserve-enums",
8993
"For internal use.",
9094
v => opts.PreserveEnums = v != null },

tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs

Lines changed: 83 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -439,61 +439,26 @@ internal virtual void WriteField (Field field, string indent, GenBase type)
439439
public void WriteInterface (InterfaceGen @interface, string indent, GenerationInfo gen_info)
440440
{
441441
opt.ContextTypes.Push (@interface);
442+
442443
// interfaces don't nest, so generate as siblings
443444
foreach (GenBase nest in @interface.NestedTypes) {
444445
WriteType (nest, indent, gen_info);
445446
writer.WriteLine ();
446447
}
447448

448-
var staticMethods = @interface.Methods.Where (m => m.IsStatic);
449-
if (@interface.Fields.Any () || staticMethods.Any ()) {
450-
string name = @interface.HasManagedName
451-
? @interface.Name.Substring (1) + "Consts"
452-
: @interface.Name.Substring (1);
453-
writer.WriteLine ("{0}[Register (\"{1}\"{2}, DoNotGenerateAcw=true)]", indent, @interface.RawJniName, @interface.AdditionalAttributeString ());
454-
writer.WriteLine ("{0}public abstract class {1} : Java.Lang.Object {{", indent, name);
455-
writer.WriteLine ();
456-
writer.WriteLine ("{0}\tinternal {1} ()", indent, name);
457-
writer.WriteLine ("{0}\t{{", indent);
458-
writer.WriteLine ("{0}\t}}", indent);
459-
460-
var seen = new HashSet<string> ();
461-
bool needsClassRef = WriteFields (@interface.Fields, indent + "\t", @interface, seen) || staticMethods.Any ();
462-
foreach (var iface in @interface.GetAllImplementedInterfaces ().OfType<InterfaceGen> ()) {
463-
writer.WriteLine ();
464-
writer.WriteLine ("{0}\t// The following are fields from: {1}", indent, iface.JavaName);
465-
bool v = WriteFields (iface.Fields, indent + "\t", iface, seen);
466-
needsClassRef = needsClassRef || v;
467-
}
468-
469-
foreach (var m in @interface.Methods.Where (m => m.IsStatic))
470-
WriteMethod (m, indent + "\t", @interface, true);
471-
472-
if (needsClassRef) {
473-
writer.WriteLine ();
474-
WriteClassHandle (@interface, indent + "\t", name);
475-
}
449+
WriteInterfaceImplementedMembersAlternative (@interface, indent);
476450

477-
writer.WriteLine ("{0}}}", indent, @interface.Name);
478-
writer.WriteLine ();
451+
// If this interface is just fields and we can't generate any of them
452+
// then we don't need to write the interface
453+
if (@interface.IsConstSugar && @interface.GetGeneratableFields (opt).Count () == 0)
454+
return;
479455

480-
if (!@interface.HasManagedName) {
481-
writer.WriteLine ("{0}[Register (\"{1}\"{2}, DoNotGenerateAcw=true)]", indent, @interface.RawJniName, @interface.AdditionalAttributeString ());
482-
writer.WriteLine ("{0}[global::System.Obsolete (\"Use the '{1}' type. This type will be removed in a future release.\")]", indent, name);
483-
writer.WriteLine ("{0}public abstract class {1}Consts : {1} {{", indent, name);
484-
writer.WriteLine ();
485-
writer.WriteLine ("{0}\tprivate {1}Consts ()", indent, name);
486-
writer.WriteLine ("{0}\t{{", indent);
487-
writer.WriteLine ("{0}\t}}", indent);
488-
writer.WriteLine ("{0}}}", indent);
489-
writer.WriteLine ();
490-
}
491-
}
456+
WriteInterfaceDeclaration (@interface, indent);
492457

458+
// If this interface is just constant fields we don't need to write all the invoker bits
493459
if (@interface.IsConstSugar)
494460
return;
495461

496-
WriteInterfaceDeclaration (@interface, indent);
497462
if (!@interface.AssemblyQualifiedName.Contains ('/'))
498463
WriteInterfaceExtensionsDeclaration (@interface, indent, null);
499464
WriteInterfaceInvoker (@interface, indent);
@@ -545,11 +510,15 @@ public void WriteInterfaceDeclaration (InterfaceGen @interface, string indent)
545510

546511
if (@interface.IsDeprecated)
547512
writer.WriteLine ("{0}[ObsoleteAttribute (@\"{1}\")]", indent, @interface.DeprecatedComment);
548-
writer.WriteLine ("{0}[Register (\"{1}\", \"\", \"{2}\"{3})]", indent, @interface.RawJniName, @interface.Namespace + "." + @interface.FullName.Substring (@interface.Namespace.Length + 1).Replace ('.', '/') + "Invoker", @interface.AdditionalAttributeString ());
513+
514+
if (!@interface.IsConstSugar)
515+
writer.WriteLine ("{0}[Register (\"{1}\", \"\", \"{2}\"{3})]", indent, @interface.RawJniName, @interface.Namespace + "." + @interface.FullName.Substring (@interface.Namespace.Length + 1).Replace ('.', '/') + "Invoker", @interface.AdditionalAttributeString ());
516+
549517
if (@interface.TypeParameters != null && @interface.TypeParameters.Any ())
550518
writer.WriteLine ("{0}{1}", indent, @interface.TypeParameters.ToGeneratedAttributeString ());
551-
writer.WriteLine ("{0}{1} partial interface {2} : {3} {{", indent, @interface.Visibility, @interface.Name,
552-
@interface.Interfaces.Count == 0 || sb.Length == 0 ? "IJavaObject" : sb.ToString ());
519+
writer.WriteLine ("{0}{1} partial interface {2}{3} {{", indent, @interface.Visibility, @interface.Name,
520+
@interface.IsConstSugar ? string.Empty : @interface.Interfaces.Count == 0 || sb.Length == 0 ? " : IJavaObject" : " : " + sb.ToString ());
521+
WriteInterfaceFields (@interface, indent + "\t");
553522
writer.WriteLine ();
554523
WriteInterfaceProperties (@interface, indent + "\t");
555524
WriteInterfaceMethods (@interface, indent + "\t");
@@ -707,6 +676,74 @@ public void WriteInterfaceExtensionsDeclaration (InterfaceGen @interface, string
707676
writer.WriteLine ();
708677
}
709678

679+
public void WriteInterfaceFields (InterfaceGen iface, string indent)
680+
{
681+
// Interface fields are only supported with DIM
682+
if (!opt.SupportDefaultInterfaceMethods)
683+
return;
684+
685+
var seen = new HashSet<string> ();
686+
var fields = iface.GetGeneratableFields (opt).ToList ();
687+
688+
WriteFields (fields, indent, iface, seen);
689+
}
690+
691+
public void WriteInterfaceImplementedMembersAlternative (InterfaceGen @interface, string indent)
692+
{
693+
// Historically .NET has not allowed interface implemented fields or constants, so we
694+
// initially worked around that by moving them to an abstract class, generally
695+
// IMyInterface -> MyInterfaceConsts
696+
// This was later expanded to accomodate static interface methods, creating a more appropriately named class
697+
// IMyInterface -> MyInterface
698+
// In this case the XXXConsts class is [Obsolete]'d and simply inherits from the newer class
699+
// in order to maintain backward compatibility.
700+
var staticMethods = @interface.Methods.Where (m => m.IsStatic);
701+
702+
if (@interface.Fields.Any () || staticMethods.Any ()) {
703+
string name = @interface.HasManagedName
704+
? @interface.Name.Substring (1) + "Consts"
705+
: @interface.Name.Substring (1);
706+
writer.WriteLine ("{0}[Register (\"{1}\"{2}, DoNotGenerateAcw=true)]", indent, @interface.RawJniName, @interface.AdditionalAttributeString ());
707+
writer.WriteLine ("{0}public abstract class {1} : Java.Lang.Object {{", indent, name);
708+
writer.WriteLine ();
709+
writer.WriteLine ("{0}\tinternal {1} ()", indent, name);
710+
writer.WriteLine ("{0}\t{{", indent);
711+
writer.WriteLine ("{0}\t}}", indent);
712+
713+
var seen = new HashSet<string> ();
714+
bool needsClassRef = WriteFields (@interface.Fields, indent + "\t", @interface, seen) || staticMethods.Any ();
715+
foreach (var iface in @interface.GetAllImplementedInterfaces ().OfType<InterfaceGen> ()) {
716+
writer.WriteLine ();
717+
writer.WriteLine ("{0}\t// The following are fields from: {1}", indent, iface.JavaName);
718+
bool v = WriteFields (iface.Fields, indent + "\t", iface, seen);
719+
needsClassRef = needsClassRef || v;
720+
}
721+
722+
foreach (var m in @interface.Methods.Where (m => m.IsStatic))
723+
WriteMethod (m, indent + "\t", @interface, true);
724+
725+
if (needsClassRef) {
726+
writer.WriteLine ();
727+
WriteClassHandle (@interface, indent + "\t", name);
728+
}
729+
730+
writer.WriteLine ("{0}}}", indent, @interface.Name);
731+
writer.WriteLine ();
732+
733+
if (!@interface.HasManagedName) {
734+
writer.WriteLine ("{0}[Register (\"{1}\"{2}, DoNotGenerateAcw=true)]", indent, @interface.RawJniName, @interface.AdditionalAttributeString ());
735+
writer.WriteLine ("{0}[global::System.Obsolete (\"Use the '{1}' type. This type will be removed in a future release.\")]", indent, name);
736+
writer.WriteLine ("{0}public abstract class {1}Consts : {1} {{", indent, name);
737+
writer.WriteLine ();
738+
writer.WriteLine ("{0}\tprivate {1}Consts ()", indent, name);
739+
writer.WriteLine ("{0}\t{{", indent);
740+
writer.WriteLine ("{0}\t}}", indent);
741+
writer.WriteLine ("{0}}}", indent);
742+
writer.WriteLine ();
743+
}
744+
}
745+
}
746+
710747
public void WriteInterfaceInvoker (InterfaceGen @interface, string indent)
711748
{
712749
writer.WriteLine ("{0}[global::Android.Runtime.Register (\"{1}\", DoNotGenerateAcw=true{2})]", indent, @interface.RawJniName, @interface.AdditionalAttributeString ());

tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections;
23
using System.Collections.Generic;
34
using System.IO;
45
using System.Linq;
@@ -27,6 +28,15 @@ public override string DefaultValue {
2728

2829
public bool HasManagedName => hasManagedName;
2930

31+
// These are fields that we currently support generating on the interface with DIM
32+
public IEnumerable<Field> GetGeneratableFields (CodeGenerationOptions options)
33+
{
34+
if (!options.SupportDefaultInterfaceMethods)
35+
return Enumerable.Empty<Field> ();
36+
37+
return Fields.Where (f => !f.NeedsProperty && !(f.DeprecatedComment?.Contains ("constant will be removed") == true));
38+
}
39+
3040
public bool IsConstSugar {
3141
get {
3242
if (Methods.Count > 0 || Properties.Count > 0)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
2+
public partial interface IMyInterface {
3+
4+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/field[@name='MyConstantField']"
5+
[Register ("MyConstantField")]
6+
public const int MyConstantField = (int) 7;
7+
8+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/field[@name='MyConstantStringField']"
9+
[Register ("MyConstantStringField")]
10+
public const string MyConstantStringField = (string) "hello";
11+
12+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/field[@name='MyDeprecatedField']"
13+
[Register ("MyDeprecatedField")]
14+
[Obsolete ("")]
15+
public const int MyDeprecatedField = (int) 7;
16+
17+
}
18+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
2+
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
3+
public partial interface IMyInterface : IJavaObject {
4+
5+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/field[@name='MyConstantField']"
6+
[Register ("MyConstantField")]
7+
public const int MyConstantField = (int) 7;
8+
9+
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
10+
[Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterfaceInvoker, ")]
11+
void DoSomething ();
12+
13+
}
14+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
2+
public partial interface IMyInterface {
3+
4+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/field[@name='MyConstantField']"
5+
[Register ("MyConstantField")]
6+
public const int MyConstantField = (int) 7;
7+
8+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/field[@name='MyConstantStringField']"
9+
[Register ("MyConstantStringField")]
10+
public const string MyConstantStringField = (string) "hello";
11+
12+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/field[@name='MyDeprecatedField']"
13+
[Register ("MyDeprecatedField")]
14+
[Obsolete ("")]
15+
public const int MyDeprecatedField = (int) 7;
16+
17+
}
18+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
2+
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
3+
public partial interface IMyInterface : IJavaObject {
4+
5+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/field[@name='MyConstantField']"
6+
[Register ("MyConstantField")]
7+
public const int MyConstantField = (int) 7;
8+
9+
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
10+
[Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterfaceInvoker, ")]
11+
void DoSomething ();
12+
13+
}
14+
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using generatortests.Unit_Tests;
3+
using MonoDroid.Generation;
4+
using NUnit.Framework;
5+
6+
namespace generatortests
7+
{
8+
[TestFixture]
9+
class JavaInteropDefaultInterfaceMethodsTests : DefaultInterfaceMethodsTests
10+
{
11+
protected override Xamarin.Android.Binder.CodeGenerationTarget Target => Xamarin.Android.Binder.CodeGenerationTarget.JavaInterop1;
12+
}
13+
14+
[TestFixture]
15+
class XamarinAndroidDefaultInterfaceMethodsTests : DefaultInterfaceMethodsTests
16+
{
17+
protected override Xamarin.Android.Binder.CodeGenerationTarget Target => Xamarin.Android.Binder.CodeGenerationTarget.XamarinAndroid;
18+
}
19+
20+
abstract class DefaultInterfaceMethodsTests : CodeGeneratorTestBase
21+
{
22+
[Test]
23+
public void WriteInterfaceFields ()
24+
{
25+
// This is an interface that has both fields and method declarations
26+
var iface = SupportTypeBuilder.CreateEmptyInterface("java.code.IMyInterface");
27+
28+
iface.Fields.Add (new TestField ("int", "MyConstantField").SetConstant ().SetValue ("7"));
29+
iface.Methods.Add (new TestMethod (iface, "DoSomething").SetAbstract ());
30+
31+
iface.Validate (options, new GenericParameterDefinitionList ());
32+
33+
options.ContextTypes.Push (iface);
34+
generator.WriteInterfaceDeclaration (iface, string.Empty);
35+
options.ContextTypes.Pop ();
36+
37+
Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceFields)), writer.ToString ().NormalizeLineEndings ());
38+
}
39+
40+
[Test]
41+
public void WriteConstSugarInterfaceFields ()
42+
{
43+
// This is an interface that only has fields (IsConstSugar)
44+
// We treat a little differenly because they don't need to interop with Java
45+
var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface");
46+
47+
// These interface fields are supported and should be in the output
48+
iface.Fields.Add (new TestField ("int", "MyConstantField").SetConstant ().SetValue ("7"));
49+
iface.Fields.Add (new TestField ("java.lang.String", "MyConstantStringField").SetConstant ().SetValue ("\"hello\""));
50+
iface.Fields.Add (new TestField ("int", "MyDeprecatedField").SetConstant ().SetValue ("7").SetDeprecated ());
51+
52+
// These interface fields are not supported and should be ignored
53+
iface.Fields.Add (new TestField ("int", "MyDeprecatedEnumField").SetConstant ().SetValue ("MyEnumValue").SetDeprecated ("This constant will be removed in the future version."));
54+
iface.Fields.Add (new TestField ("int", "MyStaticField").SetStatic ().SetValue ("7"));
55+
56+
iface.Validate (options, new GenericParameterDefinitionList ());
57+
58+
options.ContextTypes.Push (iface);
59+
generator.WriteInterfaceDeclaration (iface, string.Empty);
60+
options.ContextTypes.Pop ();
61+
62+
Assert.AreEqual (GetTargetedExpected (nameof (WriteConstSugarInterfaceFields)), writer.ToString ().NormalizeLineEndings ());
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)