diff --git a/Java.Interop.sln b/Java.Interop.sln index b1715974c..e9f4ac2d2 100644 --- a/Java.Interop.sln +++ b/Java.Interop.sln @@ -93,6 +93,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Generato EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "java-source-utils", "tools\java-source-utils\java-source-utils.csproj", "{F46EDFA5-C52A-4F0C-B5A2-5BB67E0D8C74}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.SourceWriter", "src\Xamarin.SourceWriter\Xamarin.SourceWriter.csproj", "{C5B732C8-7AF3-41D3-B903-AEDFC392E5BA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.SourceWriter-Tests", "tests\Xamarin.SourceWriter-Tests\Xamarin.SourceWriter-Tests.csproj", "{6CF94627-BA74-4336-88CD-7EDA20C8F292}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Java.Interop.NamingCustomAttributes\Java.Interop.NamingCustomAttributes.projitems*{58b564a1-570d-4da2-b02d-25bddb1a9f4f}*SharedItemsImports = 5 @@ -257,6 +261,14 @@ Global {F46EDFA5-C52A-4F0C-B5A2-5BB67E0D8C74}.Debug|Any CPU.Build.0 = Debug|Any CPU {F46EDFA5-C52A-4F0C-B5A2-5BB67E0D8C74}.Release|Any CPU.ActiveCfg = Release|Any CPU {F46EDFA5-C52A-4F0C-B5A2-5BB67E0D8C74}.Release|Any CPU.Build.0 = Release|Any CPU + {C5B732C8-7AF3-41D3-B903-AEDFC392E5BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5B732C8-7AF3-41D3-B903-AEDFC392E5BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5B732C8-7AF3-41D3-B903-AEDFC392E5BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5B732C8-7AF3-41D3-B903-AEDFC392E5BA}.Release|Any CPU.Build.0 = Release|Any CPU + {6CF94627-BA74-4336-88CD-7EDA20C8F292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CF94627-BA74-4336-88CD-7EDA20C8F292}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CF94627-BA74-4336-88CD-7EDA20C8F292}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CF94627-BA74-4336-88CD-7EDA20C8F292}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -301,6 +313,8 @@ Global {C2FD2F12-DE3B-4FB9-A0D3-FA3EF597DD04} = {0998E45F-8BCE-4791-A944-962CD54E2D80} {7F4828AB-3908-458C-B09F-33C74A1368F9} = {271C9F30-F679-4793-942B-0D9527CB3E2F} {F46EDFA5-C52A-4F0C-B5A2-5BB67E0D8C74} = {C8F58966-94BF-407F-914A-8654F8B8AE3B} + {C5B732C8-7AF3-41D3-B903-AEDFC392E5BA} = {0998E45F-8BCE-4791-A944-962CD54E2D80} + {6CF94627-BA74-4336-88CD-7EDA20C8F292} = {271C9F30-F679-4793-942B-0D9527CB3E2F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {29204E0C-382A-49A0-A814-AD7FBF9774A5} diff --git a/Makefile b/Makefile index ba9516b2e..f44a1658f 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,8 @@ TESTS = \ bin/Test$(CONFIGURATION)/generator-Tests.dll \ bin/Test$(CONFIGURATION)/Xamarin.Android.Tools.ApiXmlAdjuster-Tests.dll \ bin/Test$(CONFIGURATION)/Xamarin.Android.Tools.Bytecode-Tests.dll \ - bin/Test$(CONFIGURATION)/Java.Interop.Tools.Generator-Tests.dll + bin/Test$(CONFIGURATION)/Java.Interop.Tools.Generator-Tests.dll \ + bin/Test$(CONFIGURATION)/Xamarin.SourceWriter-Tests.dll PTESTS = \ bin/Test$(CONFIGURATION)/Java.Interop-PerformanceTests.dll diff --git a/build-tools/automation/azure-pipelines.yaml b/build-tools/automation/azure-pipelines.yaml index 5f558b5fe..87cc44653 100644 --- a/build-tools/automation/azure-pipelines.yaml +++ b/build-tools/automation/azure-pipelines.yaml @@ -55,7 +55,7 @@ jobs: inputs: solution: build-tools/scripts/RunNUnitTests.targets configuration: $(Build.Configuration) - msbuildArguments: /p:TestAssembly="bin\Test$(Build.Configuration)\generator-Tests.dll;bin\Test$(Build.Configuration)\Java.Interop.Tools.JavaCallableWrappers-Tests.dll;bin\Test$(Build.Configuration)\logcat-parse-Tests.dll;bin\Test$(Build.Configuration)\Xamarin.Android.Tools.ApiXmlAdjuster-Tests.dll;bin\Test$(Build.Configuration)\Xamarin.Android.Tools.Bytecode-Tests.dll;bin\Test$(Build.Configuration)\Java.Interop.Tools.Generator-Tests.dll" + msbuildArguments: /p:TestAssembly="bin\Test$(Build.Configuration)\generator-Tests.dll;bin\Test$(Build.Configuration)\Java.Interop.Tools.JavaCallableWrappers-Tests.dll;bin\Test$(Build.Configuration)\logcat-parse-Tests.dll;bin\Test$(Build.Configuration)\Xamarin.Android.Tools.ApiXmlAdjuster-Tests.dll;bin\Test$(Build.Configuration)\Xamarin.Android.Tools.Bytecode-Tests.dll;bin\Test$(Build.Configuration)\Java.Interop.Tools.Generator-Tests.dll;bin\Test$(Build.Configuration)\Xamarin.SourceWriter-Tests.dll" condition: succeededOrFailed() - task: PublishTestResults@2 diff --git a/build-tools/automation/templates/core-tests.yaml b/build-tools/automation/templates/core-tests.yaml index ba16127ac..da06ca4bb 100644 --- a/build-tools/automation/templates/core-tests.yaml +++ b/build-tools/automation/templates/core-tests.yaml @@ -51,6 +51,13 @@ steps: arguments: bin/Test$(Build.Configuration)/Java.Interop.Tools.JavaSource-Tests.dll continueOnError: true +- task: DotNetCoreCLI@2 + displayName: 'Tests: Xamarin.SourceWriter' + inputs: + command: test + arguments: bin/Test$(Build.Configuration)/Xamarin.SourceWriter-Tests.dll + continueOnError: true + # Running native Java.Interop tests are not yet supported on .NET Core #- task: DotNetCoreCLI@2 # displayName: 'Tests: Java.Interop' diff --git a/src/Xamarin.SourceWriter/CodeWriter.cs b/src/Xamarin.SourceWriter/CodeWriter.cs new file mode 100644 index 000000000..109866b37 --- /dev/null +++ b/src/Xamarin.SourceWriter/CodeWriter.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class CodeWriter : IDisposable + { + TextWriter stream; + bool owns_stream; + int indent; + bool need_indent = true; + string base_indent; + + public CodeWriter (string filename) + { + stream = File.CreateText (filename); + owns_stream = true; + } + + public CodeWriter (TextWriter streamWriter, string baseIndent = "") + { + stream = streamWriter; + base_indent = baseIndent; + } + + public void Write (string value) + { + WriteIndent (); + stream.Write (value); + } + + public void WriteLine () + { + WriteIndent (); + stream.WriteLine (); + need_indent = true; + } + + public void WriteLine (string value) + { + WriteIndent (); + stream.WriteLine (value); + need_indent = true; + } + + public void WriteLine (string format, params object[] args) + { + WriteIndent (); + stream.WriteLine (format, args); + need_indent = true; + } + + public void Indent (int count = 1) => indent += count; + public void Unindent (int count = 1) => indent -= count; + + private void WriteIndent () + { + if (!need_indent) + return; + + stream.Write (base_indent + new string ('\t', indent)); + + need_indent = false; + } + + public void Dispose () + { + if (owns_stream) + stream?.Dispose (); + } + } +} diff --git a/src/Xamarin.SourceWriter/Enumerations/Visibility.cs b/src/Xamarin.SourceWriter/Enumerations/Visibility.cs new file mode 100644 index 000000000..3e97b710b --- /dev/null +++ b/src/Xamarin.SourceWriter/Enumerations/Visibility.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public enum Visibility + { + Default = 0, + Private = 1, + Public = 2, + Protected = 4, + Internal = 8 + } +} diff --git a/src/Xamarin.SourceWriter/Extensions/StringExtensions.cs b/src/Xamarin.SourceWriter/Extensions/StringExtensions.cs new file mode 100644 index 000000000..6399236b3 --- /dev/null +++ b/src/Xamarin.SourceWriter/Extensions/StringExtensions.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public static class StringExtensions + { + public static bool HasValue (this string str) => !string.IsNullOrWhiteSpace (str); + } +} diff --git a/src/Xamarin.SourceWriter/Models/AttributeWriter.cs b/src/Xamarin.SourceWriter/Models/AttributeWriter.cs new file mode 100644 index 000000000..7f9f5b496 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/AttributeWriter.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public abstract class AttributeWriter + { + public virtual void WriteAttribute (CodeWriter writer) { } + } +} diff --git a/src/Xamarin.SourceWriter/Models/ClassWriter.cs b/src/Xamarin.SourceWriter/Models/ClassWriter.cs new file mode 100644 index 000000000..dc80166e7 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/ClassWriter.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class ClassWriter : TypeWriter + { + public ObservableCollection Constructors { get; } = new ObservableCollection (); + + public ClassWriter () + { + Constructors.CollectionChanged += MemberAdded; + } + + public override void WriteConstructors (CodeWriter writer) + { + foreach (var ctor in Constructors) { + ctor.Write (writer); + writer.WriteLine (); + } + } + } +} diff --git a/src/Xamarin.SourceWriter/Models/CommentWriter.cs b/src/Xamarin.SourceWriter/Models/CommentWriter.cs new file mode 100644 index 000000000..83379a4a3 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/CommentWriter.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class CommentWriter : ISourceWriter + { + public string Value { get; set; } + public int Priority { get; set; } + + public CommentWriter (string value) + { + Value = value; + } + + public virtual void Write (CodeWriter writer) + { + writer.WriteLine (Value); + } + } +} diff --git a/src/Xamarin.SourceWriter/Models/ConstructorWriter.cs b/src/Xamarin.SourceWriter/Models/ConstructorWriter.cs new file mode 100644 index 000000000..7d1ff7121 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/ConstructorWriter.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class ConstructorWriter : MethodWriter + { + public string BaseCall { get; set; } + + protected override void WriteReturnType (CodeWriter writer) + { + } + + protected override void WriteConstructorBaseCall (CodeWriter writer) + { + if (BaseCall.HasValue ()) + writer.Write ($" : {BaseCall}"); + } + } +} diff --git a/src/Xamarin.SourceWriter/Models/DelegateWriter.cs b/src/Xamarin.SourceWriter/Models/DelegateWriter.cs new file mode 100644 index 000000000..9fc56032b --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/DelegateWriter.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class DelegateWriter : ISourceWriter, ITakeParameters + { + Visibility visibility; + + public string Name { get; set; } + public List Parameters { get; } = new List (); + public TypeReferenceWriter Type { get; set; } + public List Comments { get; } = new List (); + public List Attributes { get; } = new List (); + public bool IsPublic { get => visibility.HasFlag (Visibility.Public); set => visibility = value ? Visibility.Public : Visibility.Default; } + public bool UseExplicitPrivateKeyword { get; set; } + public bool IsInternal { get => visibility.HasFlag (Visibility.Internal); set => visibility = value ? Visibility.Internal : Visibility.Default; } + public bool IsConst { get; set; } + public string Value { get; set; } + public bool IsStatic { get; set; } + public bool IsReadonly { get; set; } + public bool IsPrivate { get => visibility.HasFlag (Visibility.Private); set => visibility = value ? Visibility.Private : Visibility.Default; } + public bool IsProtected { get => visibility.HasFlag (Visibility.Protected); set => visibility = value ? Visibility.Protected : Visibility.Default; } + public int Priority { get; set; } + public bool IsShadow { get; set; } + + public void SetVisibility (string visibility) + { + switch (visibility?.ToLowerInvariant ()) { + case "public": + IsPublic = true; + break; + case "internal": + IsInternal = true; + break; + case "protected": + IsProtected = true; + break; + case "private": + IsPrivate = true; + break; + } + } + + public virtual void Write (CodeWriter writer) + { + WriteComments (writer); + WriteAttributes (writer); + WriteSignature (writer); + } + + public virtual void WriteComments (CodeWriter writer) + { + foreach (var c in Comments) + writer.WriteLine (c); + } + + public virtual void WriteAttributes (CodeWriter writer) + { + foreach (var att in Attributes) + att.WriteAttribute (writer); + } + + public virtual void WriteSignature (CodeWriter writer) + { + if (IsPublic) + writer.Write ("public "); + else if (IsInternal) + writer.Write ("internal "); + else if (IsPrivate) + writer.Write ("private "); + + if (IsStatic) + writer.Write ("static "); + if (IsReadonly) + writer.Write ("readonly "); + if (IsConst) + writer.Write ("const "); + + if (IsShadow) + writer.Write ("new "); + + writer.Write ("delegate "); + + WriteType (writer); + + writer.Write (Name + " "); + writer.Write ("("); + + WriteParameters (writer); + + writer.Write (")"); + + writer.Write (";"); + } + + protected virtual void WriteParameters (CodeWriter writer) + { + for (var i = 0; i < Parameters.Count; i++) { + var p = Parameters [i]; + p.WriteParameter (writer); + + if (i < Parameters.Count - 1) + writer.Write (", "); + } + } + + protected virtual void WriteType (CodeWriter writer) + { + Type.WriteTypeReference (writer); + } + } +} diff --git a/src/Xamarin.SourceWriter/Models/EventWriter.cs b/src/Xamarin.SourceWriter/Models/EventWriter.cs new file mode 100644 index 000000000..3edbfa703 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/EventWriter.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class EventWriter : ISourceWriter + { + Visibility visibility; + + public string Name { get; set; } + public TypeReferenceWriter EventType { get; set; } + public List Comments { get; } = new List (); + public List Attributes { get; } = new List (); + public bool IsPublic { get => visibility.HasFlag (Visibility.Public); set => visibility |= Visibility.Public; } + public bool UseExplicitPrivateKeyword { get; set; } + public bool IsInternal { get => visibility.HasFlag (Visibility.Internal); set => visibility |= Visibility.Internal; } + public List AddBody { get; } = new List (); + public List RemoveBody { get; } = new List (); + public bool IsStatic { get; set; } + public bool IsProtected { get => visibility.HasFlag (Visibility.Protected); set => visibility |= Visibility.Protected; } + public bool IsPrivate { get => visibility == Visibility.Private; set => visibility = value ? Visibility.Private : Visibility.Default; } + public bool IsOverride { get; set; } + public bool HasAdd { get; set; } + public bool HasRemove { get; set; } + public bool IsShadow { get; set; } + public bool IsAutoProperty { get; set; } + public bool IsAbstract { get; set; } + public bool IsVirtual { get; set; } + public bool IsUnsafe { get; set; } + public List GetterComments { get; } = new List (); + public List SetterComments { get; } = new List (); + public List GetterAttributes { get; } = new List (); + public List SetterAttributes { get; } = new List (); + public int Priority { get; set; } + + public void SetVisibility (string visibility) + { + switch (visibility?.ToLowerInvariant ()) { + case "public": + IsPublic = true; + break; + case "internal": + IsInternal = true; + break; + case "protected": + IsProtected = true; + break; + case "private": + IsPrivate = true; + break; + } + } + + public virtual void Write (CodeWriter writer) + { + WriteComments (writer); + WriteAttributes (writer); + WriteSignature (writer); + } + + public virtual void WriteComments (CodeWriter writer) + { + foreach (var c in Comments) + writer.WriteLine (c); + } + + public virtual void WriteAddComments (CodeWriter writer) + { + foreach (var c in GetterComments) + writer.WriteLine (c); + } + + public virtual void WriteRemoveComments (CodeWriter writer) + { + foreach (var c in SetterComments) + writer.WriteLine (c); + } + + public virtual void WriteAttributes (CodeWriter writer) + { + foreach (var att in Attributes) + att.WriteAttribute (writer); + } + + public virtual void WriteAddAttributes (CodeWriter writer) + { + foreach (var att in GetterAttributes) + att.WriteAttribute (writer); + } + + public virtual void WriteRemoveAttributes (CodeWriter writer) + { + foreach (var att in SetterAttributes) + att.WriteAttribute (writer); + } + + public virtual void WriteSignature (CodeWriter writer) + { + if (IsPublic) + writer.Write ("public "); + if (IsInternal) + writer.Write ("internal "); + if (IsProtected) + writer.Write ("protected "); + if (visibility == Visibility.Private && UseExplicitPrivateKeyword) + writer.Write ("private "); + + if (IsOverride) + writer.Write ("override "); + + if (IsStatic) + writer.Write ("static "); + + if (IsShadow) + writer.Write ("new "); + + if (IsAbstract) + writer.Write ("abstract "); + if (IsVirtual) + writer.Write ("virtual "); + + if (IsUnsafe) + writer.Write ("unsafe "); + + writer.Write ("event "); + + WriteEventType (writer); + writer.Write (Name); + + WriteBody (writer); + } + + protected virtual void WriteBody (CodeWriter writer) + { + if (!HasAdd && !HasRemove) { + writer.WriteLine (";"); + return; + } + + writer.Write (" "); + + if (IsAutoProperty || IsAbstract) { + WriteAutomaticEventBody (writer); + return; + } + + writer.WriteLine ("{"); + + writer.Indent (); + + WriteAdd (writer); + WriteRemove (writer); + + writer.Unindent (); + + writer.WriteLine ("}"); + } + + protected virtual void WriteAutomaticEventBody (CodeWriter writer) + { + writer.Write ("{ "); + + if (HasAdd) { + WriteAddComments (writer); + WriteAddAttributes (writer); + writer.Write ("add; "); + } + + if (HasRemove) { + WriteRemoveComments (writer); + WriteRemoveAttributes (writer); + writer.Write ("remove; "); + } + + writer.WriteLine ("}"); + } + + protected virtual void WriteAdd (CodeWriter writer) + { + if (HasAdd) { + WriteAddComments (writer); + WriteAddAttributes (writer); + + if (AddBody.Count == 1) + writer.WriteLine ("add { " + AddBody [0] + " }"); + else { + writer.WriteLine ("add {"); + writer.Indent (); + + WriteAddBody (writer); + + writer.Unindent (); + writer.WriteLine ("}"); + } + } + } + + protected virtual void WriteAddBody (CodeWriter writer) + { + foreach (var b in AddBody) + writer.WriteLine (b); + } + + protected virtual void WriteRemove (CodeWriter writer) + { + if (HasRemove) { + WriteRemoveComments (writer); + WriteRemoveAttributes (writer); + + if (RemoveBody.Count == 1) + writer.WriteLine ("remove { " + RemoveBody [0] + " }"); + else { + writer.WriteLine ("remove {"); + writer.Indent (); + + WriteRemoveBody (writer); + + writer.Unindent (); + writer.WriteLine ("}"); + } + } + } + + protected virtual void WriteEventType (CodeWriter writer) + { + EventType.WriteTypeReference (writer); + } + + protected virtual void WriteRemoveBody (CodeWriter writer) + { + foreach (var b in RemoveBody) + writer.WriteLine (b); + } + } +} diff --git a/src/Xamarin.SourceWriter/Models/FieldWriter.cs b/src/Xamarin.SourceWriter/Models/FieldWriter.cs new file mode 100644 index 000000000..059fafb21 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/FieldWriter.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class FieldWriter : ISourceWriter + { + Visibility visibility; + + public string Name { get; set; } + public TypeReferenceWriter Type { get; set; } + public List Comments { get; } = new List (); + public List Attributes { get; } = new List (); + public bool IsPublic { get => visibility.HasFlag (Visibility.Public); set => visibility = value ? Visibility.Public : Visibility.Default; } + public bool UseExplicitPrivateKeyword { get; set; } + public bool IsInternal { get => visibility.HasFlag (Visibility.Internal); set => visibility = value ? Visibility.Internal : Visibility.Default; } + public bool IsConst { get; set; } + public string Value { get; set; } + public bool IsStatic { get; set; } + public bool IsReadonly { get; set; } + public bool IsPrivate { get => visibility.HasFlag (Visibility.Private); set => visibility = value ? Visibility.Private : Visibility.Default; } + public bool IsProtected { get => visibility.HasFlag (Visibility.Protected); set => visibility = value ? Visibility.Protected : Visibility.Default; } + public int Priority { get; set; } + public bool IsShadow { get; set; } + + public void SetVisibility (string visibility) + { + switch (visibility?.ToLowerInvariant ().Trim ()) { + case "public": + IsPublic = true; + break; + case "internal": + IsInternal = true; + break; + case "protected": + IsProtected = true; + break; + case "private": + IsPrivate = true; + break; + default: + Console.WriteLine ($"Unrecognized field visibility: `{visibility}`"); + break; + } + } + + public virtual void Write (CodeWriter writer) + { + WriteComments (writer); + WriteAttributes (writer); + WriteSignature (writer); + } + + public virtual void WriteComments (CodeWriter writer) + { + foreach (var c in Comments) + writer.WriteLine (c); + } + + public virtual void WriteAttributes (CodeWriter writer) + { + foreach (var att in Attributes) + att.WriteAttribute (writer); + } + + public virtual void WriteSignature (CodeWriter writer) + { + if (IsPublic) + writer.Write ("public "); + else if (IsProtected) + writer.Write ("protected "); + else if (IsInternal) + writer.Write ("internal "); + else if (IsPrivate) + writer.Write ("private "); + + if (IsStatic) + writer.Write ("static "); + if (IsReadonly) + writer.Write ("readonly "); + if (IsConst) + writer.Write ("const "); + + if (IsShadow) + writer.Write ("new "); + + WriteType (writer); + + if (Value.HasValue ()) { + writer.Write (Name + " = "); + writer.Write (Value); + writer.WriteLine (";"); + } else { + writer.WriteLine ($"{Name};"); + } + } + + protected virtual void WriteType (CodeWriter writer) + { + Type.WriteTypeReference (writer); + } + } +} diff --git a/src/Xamarin.SourceWriter/Models/ISourceWriter.cs b/src/Xamarin.SourceWriter/Models/ISourceWriter.cs new file mode 100644 index 000000000..ab1caea07 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/ISourceWriter.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public interface ISourceWriter + { + void Write (CodeWriter writer); + + // This is for testing compatibility, allowing us to write members + // in the same order as previous generator. + int Priority { get; set; } + } +} diff --git a/src/Xamarin.SourceWriter/Models/ITakeParameters.cs b/src/Xamarin.SourceWriter/Models/ITakeParameters.cs new file mode 100644 index 000000000..8efdda264 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/ITakeParameters.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public interface ITakeParameters + { + List Parameters { get; } + } +} diff --git a/src/Xamarin.SourceWriter/Models/InterfaceWriter.cs b/src/Xamarin.SourceWriter/Models/InterfaceWriter.cs new file mode 100644 index 000000000..1b6ff0380 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/InterfaceWriter.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class InterfaceWriter : TypeWriter + { + } +} diff --git a/src/Xamarin.SourceWriter/Models/MethodParameterWriter.cs b/src/Xamarin.SourceWriter/Models/MethodParameterWriter.cs new file mode 100644 index 000000000..e8f2571ef --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/MethodParameterWriter.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class MethodParameterWriter + { + public TypeReferenceWriter Type { get; set; } + public List Attributes { get; } = new List (); + public string Name { get; set; } + public bool IsExtension { get; set; } + + public MethodParameterWriter (string name, TypeReferenceWriter type) + { + Name = name; + Type = type; + } + + public virtual void WriteParameter (CodeWriter writer) + { + WriteAttributes (writer); + + if (IsExtension) + writer.Write ("this "); + + Type.WriteTypeReference (writer); + writer.Write (Name); + } + + public virtual void WriteAttributes (CodeWriter writer) + { + foreach (var att in Attributes) + att.WriteAttribute (writer); + } + } +} diff --git a/src/Xamarin.SourceWriter/Models/MethodWriter.cs b/src/Xamarin.SourceWriter/Models/MethodWriter.cs new file mode 100644 index 000000000..61e4587a4 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/MethodWriter.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class MethodWriter : ISourceWriter, ITakeParameters + { + Visibility visibility; + + public string Name { get; set; } + public List Parameters { get; } = new List (); + public TypeReferenceWriter ReturnType { get; set; } + public List Comments { get; } = new List (); + public List Attributes { get; } = new List (); + public bool IsPublic { get => visibility == Visibility.Public; set => visibility = value ? Visibility.Public : Visibility.Default; } + public bool UseExplicitPrivateKeyword { get; set; } + public bool IsInternal { get => visibility == Visibility.Internal; set => visibility = value ? Visibility.Internal : Visibility.Default; } + public List Body { get; set; } = new List (); + public bool IsSealed { get; set; } + public bool IsStatic { get; set; } + public bool IsPrivate { get => visibility == Visibility.Private; set => visibility = value ? Visibility.Private : Visibility.Default; } + public bool IsProtected { get => visibility == Visibility.Protected; set => visibility = value ? Visibility.Protected : Visibility.Default; } + public bool IsOverride { get; set; } + public bool IsUnsafe { get; set; } + public bool IsVirtual { get; set; } + public bool IsShadow { get; set; } + public bool IsAbstract { get; set; } + public int Priority { get; set; } + public bool IsDeclaration { get; set; } + + public string ExplicitInterfaceImplementation { get; set; } + public bool NewFirst { get; set; } // TODO: Temporary to match unit tests + + public void SetVisibility (string visibility) + { + switch (visibility?.ToLowerInvariant ()) { + case "public": + IsPublic = true; + break; + case "internal": + IsInternal = true; + break; + case "protected": + IsProtected = true; + break; + case "private": + IsPrivate = true; + break; + } + } + + public virtual void Write (CodeWriter writer) + { + WriteComments (writer); + WriteAttributes (writer); + WriteSignature (writer); + } + + public virtual void WriteComments (CodeWriter writer) + { + foreach (var c in Comments) + writer.WriteLine (c); + } + + public virtual void WriteAttributes (CodeWriter writer) + { + foreach (var att in Attributes) + att.WriteAttribute (writer); + } + + public virtual void WriteSignature (CodeWriter writer) + { + if (IsShadow && NewFirst) + writer.Write ("new "); + + if (IsPublic) + writer.Write ("public "); + if (IsInternal) + writer.Write ("internal "); + if (IsProtected) + writer.Write ("protected "); + if (IsPrivate) + writer.Write ("private "); + + if (IsShadow && !NewFirst) + writer.Write ("new "); + + if (IsOverride) + writer.Write ("override "); + else if (IsVirtual) + writer.Write ("virtual "); + else if (IsAbstract) + writer.Write ("abstract "); + + if (IsSealed) + writer.Write ("sealed "); + + if (IsStatic) + writer.Write ("static "); + + if (IsUnsafe) + writer.Write ("unsafe "); + + WriteReturnType (writer); + + if (ExplicitInterfaceImplementation.HasValue ()) + writer.Write (ExplicitInterfaceImplementation + "."); + + writer.Write (Name + " "); + writer.Write ("("); + + WriteParameters (writer); + + writer.Write (")"); + + if (IsAbstract || IsDeclaration) { + writer.WriteLine (";"); + return; + } + + WriteConstructorBaseCall (writer); + + writer.WriteLine (); + writer.WriteLine ("{"); + writer.Indent (); + + WriteBody (writer); + + writer.Unindent (); + + writer.WriteLine ("}"); + } + + protected virtual void WriteBody (CodeWriter writer) + { + foreach (var s in Body) + writer.WriteLine (s); + } + + protected virtual void WriteParameters (CodeWriter writer) + { + for (var i = 0; i < Parameters.Count; i++) { + var p = Parameters [i]; + p.WriteParameter (writer); + + if (i < Parameters.Count - 1) + writer.Write (", "); + } + } + + protected virtual void WriteReturnType (CodeWriter writer) + { + ReturnType.WriteTypeReference (writer); + } + + protected virtual void WriteConstructorBaseCall (CodeWriter writer) { } + } +} diff --git a/src/Xamarin.SourceWriter/Models/PropertyWriter.cs b/src/Xamarin.SourceWriter/Models/PropertyWriter.cs new file mode 100644 index 000000000..1b6fce304 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/PropertyWriter.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class PropertyWriter : ISourceWriter + { + Visibility visibility; + + public string Name { get; set; } + public List Parameters { get; } = new List (); + public TypeReferenceWriter PropertyType { get; set; } + public List Comments { get; } = new List (); + public List Attributes { get; } = new List (); + public bool IsPublic { get => visibility.HasFlag (Visibility.Public); set => visibility |= Visibility.Public; } + public bool UseExplicitPrivateKeyword { get; set; } + public bool IsInternal { get => visibility.HasFlag (Visibility.Internal); set => visibility |= Visibility.Internal; } + public List GetBody { get; } = new List (); + public List SetBody { get; } = new List (); + public bool IsStatic { get; set; } + public bool IsProtected { get => visibility.HasFlag (Visibility.Protected); set => visibility |= Visibility.Protected; } + public bool IsPrivate { get => visibility == Visibility.Private; set => visibility = value ? Visibility.Private : Visibility.Default; } + public bool IsOverride { get; set; } + public bool HasGet { get; set; } + public bool HasSet { get; set; } + public bool IsShadow { get; set; } + public bool IsAutoProperty { get; set; } + public bool IsAbstract { get; set; } + public bool IsVirtual { get; set; } + public bool IsUnsafe { get; set; } + public List GetterComments { get; } = new List (); + public List SetterComments { get; } = new List (); + public List GetterAttributes { get; } = new List (); + public List SetterAttributes { get; } = new List (); + public int Priority { get; set; } + public string ExplicitInterfaceImplementation { get; set; } + + public void SetVisibility (string visibility) + { + switch (visibility?.ToLowerInvariant ()) { + case "public": + IsPublic = true; + break; + case "internal": + IsInternal = true; + break; + case "protected": + IsProtected = true; + break; + case "private": + IsPrivate = true; + break; + } + } + + public virtual void Write (CodeWriter writer) + { + WriteComments (writer); + WriteAttributes (writer); + WriteSignature (writer); + } + + public virtual void WriteComments (CodeWriter writer) + { + foreach (var c in Comments) + writer.WriteLine (c); + } + + public virtual void WriteGetterComments (CodeWriter writer) + { + foreach (var c in GetterComments) + writer.WriteLine (c); + } + + public virtual void WriteSetterComments (CodeWriter writer) + { + foreach (var c in SetterComments) + writer.WriteLine (c); + } + + public virtual void WriteAttributes (CodeWriter writer) + { + foreach (var att in Attributes) + att.WriteAttribute (writer); + } + + public virtual void WriteGetterAttributes (CodeWriter writer) + { + foreach (var att in GetterAttributes) + att.WriteAttribute (writer); + } + + public virtual void WriteSetterAttributes (CodeWriter writer) + { + foreach (var att in SetterAttributes) + att.WriteAttribute (writer); + } + + public virtual void WriteSignature (CodeWriter writer) + { + if (IsPublic) + writer.Write ("public "); + if (IsInternal) + writer.Write ("internal "); + if (IsProtected) + writer.Write ("protected "); + if (visibility == Visibility.Private && UseExplicitPrivateKeyword) + writer.Write ("private "); + + if (IsOverride) + writer.Write ("override "); + + if (IsStatic) + writer.Write ("static "); + + if (IsShadow) + writer.Write ("new "); + + if (IsAbstract) + writer.Write ("abstract "); + if (IsVirtual) + writer.Write ("virtual "); + + if (IsUnsafe) + writer.Write ("unsafe "); + + WritePropertyType (writer); + + if (ExplicitInterfaceImplementation.HasValue ()) + writer.Write (ExplicitInterfaceImplementation + "."); + + writer.Write (Name + " "); + + WriteBody (writer); + } + + protected virtual void WriteBody (CodeWriter writer) + { + if (IsAutoProperty || IsAbstract) { + WriteAutomaticPropertyBody (writer); + return; + } + + writer.WriteLine ("{"); + + writer.Indent (); + + WriteGetter (writer); + WriteSetter (writer); + + writer.Unindent (); + + writer.WriteLine ("}"); + } + + protected virtual void WriteAutomaticPropertyBody (CodeWriter writer) + { + writer.Write ("{ "); + + if (HasGet) { + WriteGetterComments (writer); + WriteGetterAttributes (writer); + writer.Write ("get; "); + } + + if (HasSet) { + WriteSetterComments (writer); + WriteSetterAttributes (writer); + writer.Write ("set; "); + } + + writer.WriteLine ("}"); + } + + protected virtual void WriteGetter (CodeWriter writer) + { + if (HasGet) { + WriteGetterComments (writer); + WriteGetterAttributes (writer); + + if (GetBody.Count == 1) + writer.WriteLine ("get { " + GetBody [0] + " }"); + else { + writer.WriteLine ("get {"); + writer.Indent (); + + WriteGetterBody (writer); + + writer.Unindent (); + writer.WriteLine ("}"); + } + } + } + + protected virtual void WriteGetterBody (CodeWriter writer) + { + foreach (var b in GetBody) + writer.WriteLine (b); + } + + protected virtual void WriteSetter (CodeWriter writer) + { + if (HasSet) { + WriteSetterComments (writer); + WriteSetterAttributes (writer); + + if (SetBody.Count == 1) + writer.WriteLine ("set { " + SetBody [0] + " }"); + else { + writer.WriteLine ("set {"); + writer.Indent (); + + WriteSetterBody (writer); + + writer.Unindent (); + writer.WriteLine ("}"); + } + } + } + + protected virtual void WriteSetterBody (CodeWriter writer) + { + foreach (var b in SetBody) + writer.WriteLine (b); + } + + protected virtual void WritePropertyType (CodeWriter writer) + { + PropertyType.WriteTypeReference (writer); + } + + protected virtual void WriteConstructorBaseCall (CodeWriter writer) { } + } +} diff --git a/src/Xamarin.SourceWriter/Models/TypeReferenceWriter.cs b/src/Xamarin.SourceWriter/Models/TypeReferenceWriter.cs new file mode 100644 index 000000000..4d6746ce6 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/TypeReferenceWriter.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.SourceWriter +{ + public class TypeReferenceWriter + { + public string Namespace { get; set; } + public string Name { get; set; } + public bool Nullable { get; set; } + + // These purposely create new instances, as they are not immutable. + // For example you may intend to make an instance null, but if there + // was only one, you would make them all null. + public static TypeReferenceWriter Bool => new TypeReferenceWriter ("bool"); + public static TypeReferenceWriter Delegate => new TypeReferenceWriter ("Delegate"); + public static TypeReferenceWriter IntPtr => new TypeReferenceWriter ("IntPtr"); + public static TypeReferenceWriter Float => new TypeReferenceWriter ("float"); + public static TypeReferenceWriter Object => new TypeReferenceWriter ("object"); + public static TypeReferenceWriter Void => new TypeReferenceWriter ("void"); + + public TypeReferenceWriter (string name) + { + var index = name.LastIndexOf ('.'); + + if (index >= 0) { + Namespace = name.Substring (0, index); + Name = name.Substring (index + 1); + } else { + Name = name; + } + } + + public TypeReferenceWriter (string ns, string name) + { + Namespace = ns; + Name = name; + } + + public virtual void WriteTypeReference (CodeWriter writer) + { + if (Namespace.HasValue ()) + writer.Write ($"{Namespace}.{Name}{NullableOperator} "); + else + writer.Write ($"{Name}{NullableOperator} "); + } + + string NullableOperator => Nullable ? "?" : string.Empty; + } +} diff --git a/src/Xamarin.SourceWriter/Models/TypeWriter.cs b/src/Xamarin.SourceWriter/Models/TypeWriter.cs new file mode 100644 index 000000000..d864596f3 --- /dev/null +++ b/src/Xamarin.SourceWriter/Models/TypeWriter.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Xamarin.SourceWriter +{ + public abstract class TypeWriter : ISourceWriter + { + Visibility visibility; + int current_priority = 1; + + public string Name { get; set; } + public string Inherits { get; set; } + public List Implements { get; } = new List (); + public bool IsPartial { get; set; } + public bool IsPublic { get => visibility.HasFlag (Visibility.Public); set => visibility = value ? Visibility.Public : Visibility.Default; } + public bool IsAbstract { get; set; } + public bool IsInternal { get => visibility.HasFlag (Visibility.Internal); set => visibility = value ? Visibility.Internal : Visibility.Default; } + public bool IsShadow { get; set; } + public bool IsSealed { get; set; } + public bool IsStatic { get; set; } + public bool IsPrivate { get => visibility.HasFlag (Visibility.Private); set => visibility = value ? Visibility.Private : Visibility.Default; } + public bool IsProtected { get => visibility.HasFlag (Visibility.Protected); set => visibility = value ? Visibility.Protected : Visibility.Default; } + public ObservableCollection Methods { get; } = new ObservableCollection (); + public List Comments { get; } = new List (); + public List Attributes { get; } = new List (); + public ObservableCollection Events { get; } = new ObservableCollection (); + public ObservableCollection Fields { get; } = new ObservableCollection (); + public ObservableCollection Properties { get; } = new ObservableCollection (); + public ObservableCollection InlineComments { get; } = new ObservableCollection (); + public ObservableCollection Delegates { get; } = new ObservableCollection (); + public int Priority { get; set; } + public int GetNextPriority () => current_priority++; + public bool UsePriorityOrder { get; set; } + + public ObservableCollection NestedTypes { get; } = new ObservableCollection (); + + protected TypeWriter () + { + Methods.CollectionChanged += MemberAdded; + Events.CollectionChanged += MemberAdded; + Fields.CollectionChanged += MemberAdded; + Properties.CollectionChanged += MemberAdded; + InlineComments.CollectionChanged += MemberAdded; + Delegates.CollectionChanged += MemberAdded; + NestedTypes.CollectionChanged += MemberAdded; + } + + protected void MemberAdded (object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + foreach (var member in e.NewItems.OfType ()) + if (member.Priority == 0) + member.Priority = GetNextPriority (); + } + + public void SetVisibility (string visibility) + { + switch (visibility?.ToLowerInvariant ().Trim ()) { + case "public": + IsPublic = true; + break; + case "internal": + IsInternal = true; + break; + case "protected": + IsProtected = true; + break; + case "protected internal": + this.visibility = Visibility.Protected | Visibility.Internal; + break; + case "private": + IsPrivate = true; + break; + } + } + + public virtual void Write (CodeWriter writer) + { + WriteComments (writer); + WriteAttributes (writer); + WriteSignature (writer); + WriteMembers (writer); + WriteTypeClose (writer); + } + + public virtual void WriteComments (CodeWriter writer) + { + foreach (var c in Comments) + writer.WriteLine (c); + } + + public virtual void WriteAttributes (CodeWriter writer) + { + foreach (var att in Attributes) + att.WriteAttribute (writer); + } + + public virtual void WriteSignature (CodeWriter writer) + { + if (IsPublic) + writer.Write ("public "); + if (IsProtected) + writer.Write ("protected "); + if (IsInternal) + writer.Write ("internal "); + if (IsPrivate) + writer.Write ("private "); + + if (IsShadow) + writer.Write ("new "); + + if (IsStatic) + writer.Write ("static "); + + if (IsAbstract) + writer.Write ("abstract "); + + if (IsSealed) + writer.Write ("sealed "); + + if (IsPartial) + writer.Write ("partial "); + + writer.Write (this is InterfaceWriter ? "interface " : "class "); + writer.Write (Name + " "); + + if (Inherits.HasValue () || Implements.Count > 0) + writer.Write (": "); + + if (Inherits.HasValue ()) { + writer.Write (Inherits); + + if (Implements.Count > 0) + writer.Write (","); + + writer.Write (" "); + } + + if (Implements.Count > 0) + writer.Write (string.Join (", ", Implements) + " "); + + writer.WriteLine ("{"); + writer.Indent (); + } + + public virtual void WriteMembers (CodeWriter writer) + { + if (UsePriorityOrder) { + WriteMembersByPriority (writer); + return; + } + + if (Fields.Count > 0) { + writer.WriteLine (); + WriteFields (writer); + } + + WriteConstructors (writer); + + writer.WriteLine (); + WriteEvents (writer); + writer.WriteLine (); + WriteDelegates (writer); + writer.WriteLine (); + WriteProperties (writer); + writer.WriteLine (); + WriteMethods (writer); + } + + public void AddInlineComment (string comment) + { + InlineComments.Add (new CommentWriter (comment)); + } + + public virtual void WriteMembersByPriority (CodeWriter writer) + { + var members = Fields.Cast ().Concat (Properties).Concat (Methods).Concat (NestedTypes).Concat (Events).Concat (InlineComments).Concat (Delegates); + + if (this is ClassWriter klass) + members = members.Concat (klass.Constructors); + + foreach (var member in members.OrderBy (p => p.Priority)) { + member.Write (writer); + writer.WriteLine (); + } + } + + public virtual void WriteConstructors (CodeWriter writer) { } + + public virtual void WriteEvents (CodeWriter writer) + { + foreach (var ev in Events) { + ev.Write (writer); + writer.WriteLine (); + } + } + + public virtual void WriteFields (CodeWriter writer) + { + foreach (var field in Fields) { + field.Write (writer); + writer.WriteLine (); + } + } + + public virtual void WriteMethods (CodeWriter writer) + { + foreach (var method in Methods) { + method.Write (writer); + writer.WriteLine (); + } + } + + public virtual void WriteProperties (CodeWriter writer) + { + foreach (var prop in Properties) { + prop.Write (writer); + writer.WriteLine (); + } + } + + public virtual void WriteDelegates (CodeWriter writer) + { + foreach (var del in Delegates) { + del.Write (writer); + writer.WriteLine (); + } + } + + public virtual void WriteTypeClose (CodeWriter writer) + { + writer.Unindent (); + writer.WriteLine ("}"); + } + } +} diff --git a/src/Xamarin.SourceWriter/Xamarin.SourceWriter.csproj b/src/Xamarin.SourceWriter/Xamarin.SourceWriter.csproj new file mode 100644 index 000000000..9f5c4f4ab --- /dev/null +++ b/src/Xamarin.SourceWriter/Xamarin.SourceWriter.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/tests/Xamarin.SourceWriter-Tests/ClassWriterTests.cs b/tests/Xamarin.SourceWriter-Tests/ClassWriterTests.cs new file mode 100644 index 000000000..59aa55a94 --- /dev/null +++ b/tests/Xamarin.SourceWriter-Tests/ClassWriterTests.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; + +namespace Xamarin.SourceWriter.Tests +{ + [TestFixture] + public class ClassWriterTests + { + [Test] + public void Basics () + { + var klass = new ClassWriter { + IsPublic = true, + Inherits = "System.Object", + IsPartial = true, + Name = "MyClass", + UsePriorityOrder = true + }; + + klass.Fields.Add (new FieldWriter { IsPublic = true, Name = "my_field", Type = TypeReferenceWriter.Bool }); + klass.AddInlineComment ("// Test comment"); + + klass.Methods.Add (new MethodWriter { Name = "MyMethod", IsPublic = true, ReturnType = TypeReferenceWriter.Void }); + klass.Methods [0].Parameters.Add (new MethodParameterWriter ("test", TypeReferenceWriter.Bool)); + + var sw = new StringWriter (); + var writer = new CodeWriter (sw); + + klass.Write (writer); + + var expected = +@"public partial class MyClass : System.Object { + public bool my_field; + + // Test comment + + public void MyMethod (bool test) + { + } + +} +"; + + Assert.AreEqual (expected, sw.ToString ()); + } + } +} diff --git a/tests/Xamarin.SourceWriter-Tests/ConstructorWriterTests.cs b/tests/Xamarin.SourceWriter-Tests/ConstructorWriterTests.cs new file mode 100644 index 000000000..06bffefd4 --- /dev/null +++ b/tests/Xamarin.SourceWriter-Tests/ConstructorWriterTests.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; + +namespace Xamarin.SourceWriter.Tests +{ + [TestFixture] + public class ConstructorWriterTests + { + [Test] + public void Basics () + { + var ctor = new ConstructorWriter { Name = "MyClass", IsPublic = true, BaseCall = "base ()" }; + ctor.Parameters.Add (new MethodParameterWriter ("test", TypeReferenceWriter.Bool)); + + var sw = new StringWriter (); + var writer = new CodeWriter (sw); + + ctor.Write (writer); + + var expected = +@"public MyClass (bool test) : base () +{ +} +"; + + Assert.AreEqual (expected, sw.ToString ()); + } + } +} diff --git a/tests/Xamarin.SourceWriter-Tests/DelegateWriterTests.cs b/tests/Xamarin.SourceWriter-Tests/DelegateWriterTests.cs new file mode 100644 index 000000000..f4352366b --- /dev/null +++ b/tests/Xamarin.SourceWriter-Tests/DelegateWriterTests.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; + +namespace Xamarin.SourceWriter.Tests +{ + [TestFixture] + public class DelegateWriterTests + { + [Test] + public void Basics () + { + var method = new DelegateWriter { Name = "MyDelegate", IsPublic = true, Type = TypeReferenceWriter.IntPtr }; + method.Parameters.Add (new MethodParameterWriter ("test", TypeReferenceWriter.Bool)); + + var sw = new StringWriter (); + var writer = new CodeWriter (sw); + + method.Write (writer); + + var expected = "public delegate IntPtr MyDelegate (bool test);"; + + Assert.AreEqual (expected, sw.ToString ()); + } + } +} diff --git a/tests/Xamarin.SourceWriter-Tests/EventWriterTests.cs b/tests/Xamarin.SourceWriter-Tests/EventWriterTests.cs new file mode 100644 index 000000000..17bb39495 --- /dev/null +++ b/tests/Xamarin.SourceWriter-Tests/EventWriterTests.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; + +namespace Xamarin.SourceWriter.Tests +{ + [TestFixture] + public class EventWriterTests + { + [Test] + public void Basics () + { + var ev = new EventWriter { Name = "MyEvent", IsPublic = true, EventType = new TypeReferenceWriter ("EventHandler") }; + + var sw = new StringWriter (); + var writer = new CodeWriter (sw); + + ev.Write (writer); + + var expected = @"public event EventHandler MyEvent; +"; + + Assert.AreEqual (expected, sw.ToString ()); + } + } +} diff --git a/tests/Xamarin.SourceWriter-Tests/FieldWriterTests.cs b/tests/Xamarin.SourceWriter-Tests/FieldWriterTests.cs new file mode 100644 index 000000000..6f79df7d7 --- /dev/null +++ b/tests/Xamarin.SourceWriter-Tests/FieldWriterTests.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; + +namespace Xamarin.SourceWriter.Tests +{ + [TestFixture] + public class FieldWriterTests + { + [Test] + public void Basics () + { + var field = new FieldWriter { Name = "MyField", IsPublic = true, Type = TypeReferenceWriter.IntPtr }; + + var sw = new StringWriter (); + var writer = new CodeWriter (sw); + + field.Write (writer); + + var expected = @"public IntPtr MyField; +"; + + Assert.AreEqual (expected, sw.ToString ()); + } + } +} diff --git a/tests/Xamarin.SourceWriter-Tests/InterfaceWriterTests.cs b/tests/Xamarin.SourceWriter-Tests/InterfaceWriterTests.cs new file mode 100644 index 000000000..73db7ea51 --- /dev/null +++ b/tests/Xamarin.SourceWriter-Tests/InterfaceWriterTests.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; + +namespace Xamarin.SourceWriter.Tests +{ + [TestFixture] + public class InterfaceWriterTests + { + [Test] + public void Basics () + { + var iface = new InterfaceWriter { + IsPublic = true, + Inherits = "IDisposable", + IsPartial = true, + Name = "IMyInterface", + UsePriorityOrder = true + }; + + iface.Methods.Add (new MethodWriter { Name = "MyMethod", IsDeclaration = true, ReturnType = TypeReferenceWriter.Void }); + iface.Methods [0].Parameters.Add (new MethodParameterWriter ("test", TypeReferenceWriter.Bool)); + + var sw = new StringWriter (); + var writer = new CodeWriter (sw); + + iface.Write (writer); + + var expected = +@"public partial interface IMyInterface : IDisposable { + void MyMethod (bool test); + +} +"; + + Assert.AreEqual (expected, sw.ToString ()); + } + } +} diff --git a/tests/Xamarin.SourceWriter-Tests/MethodWriterTests.cs b/tests/Xamarin.SourceWriter-Tests/MethodWriterTests.cs new file mode 100644 index 000000000..1507aea6f --- /dev/null +++ b/tests/Xamarin.SourceWriter-Tests/MethodWriterTests.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; + +namespace Xamarin.SourceWriter.Tests +{ + [TestFixture] + public class MethodWriterTests + { + [Test] + public void Basics () + { + var method = new MethodWriter { Name = "MyMethod", IsPublic = true, ReturnType = TypeReferenceWriter.Void }; + method.Parameters.Add (new MethodParameterWriter ("test", TypeReferenceWriter.Bool)); + + var sw = new StringWriter (); + var writer = new CodeWriter (sw); + + method.Write (writer); + + var expected = +@"public void MyMethod (bool test) +{ +} +"; + + Assert.AreEqual (expected, sw.ToString ()); + } + } +} diff --git a/tests/Xamarin.SourceWriter-Tests/PropertyWriterTests.cs b/tests/Xamarin.SourceWriter-Tests/PropertyWriterTests.cs new file mode 100644 index 000000000..5d2acf406 --- /dev/null +++ b/tests/Xamarin.SourceWriter-Tests/PropertyWriterTests.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; + +namespace Xamarin.SourceWriter.Tests +{ + [TestFixture] + public class PropertyWriterTests + { + [Test] + public void Basics () + { + var property = new PropertyWriter { Name = "MyProperty", IsPublic = true, PropertyType = TypeReferenceWriter.IntPtr, HasGet = true, HasSet = true }; + + property.GetBody.Add ("return IntPtr.Zero;"); + property.SetBody.Add ("this.Handle = value;"); + + var sw = new StringWriter (); + var writer = new CodeWriter (sw); + + property.Write (writer); + + Console.WriteLine (sw.ToString ()); + + var expected = +@"public IntPtr MyProperty { + get { return IntPtr.Zero; } + set { this.Handle = value; } +} +"; + + Assert.AreEqual (expected, sw.ToString ()); + } + } +} diff --git a/tests/Xamarin.SourceWriter-Tests/TypeReferenceWriterTests.cs b/tests/Xamarin.SourceWriter-Tests/TypeReferenceWriterTests.cs new file mode 100644 index 000000000..548c9e042 --- /dev/null +++ b/tests/Xamarin.SourceWriter-Tests/TypeReferenceWriterTests.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; + +namespace Xamarin.SourceWriter.Tests +{ + [TestFixture] + public class TypeReferenceWriterTests + { + [Test] + public void Constructor () + { + var t = new TypeReferenceWriter ("String"); + + Assert.AreEqual (null, t.Namespace); + Assert.AreEqual ("String", t.Name); + + t = new TypeReferenceWriter ("System.String"); + + Assert.AreEqual ("System", t.Namespace); + Assert.AreEqual ("String", t.Name); + + t = new TypeReferenceWriter ("System.Internal.String"); + + Assert.AreEqual ("System.Internal", t.Namespace); + Assert.AreEqual ("String", t.Name); + } + + [Test] + public void NotNull () + { + var t = new TypeReferenceWriter ("string"); + + var sw = new StringWriter (); + var cw = new CodeWriter (sw); + + t.WriteTypeReference (cw); + + Assert.AreEqual ("string ", sw.ToString ()); + } + + [Test] + public void Nullable () + { + var t = new TypeReferenceWriter ("string") { Nullable = true }; + + var sw = new StringWriter (); + var cw = new CodeWriter (sw); + + t.WriteTypeReference (cw); + + Assert.AreEqual ("string? ", sw.ToString ()); + } + } +} diff --git a/tests/Xamarin.SourceWriter-Tests/Xamarin.SourceWriter-Tests.csproj b/tests/Xamarin.SourceWriter-Tests/Xamarin.SourceWriter-Tests.csproj new file mode 100644 index 000000000..a17efebe4 --- /dev/null +++ b/tests/Xamarin.SourceWriter-Tests/Xamarin.SourceWriter-Tests.csproj @@ -0,0 +1,26 @@ + + + + net472 + false + + + + $(TestOutputFullPath) + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/tests/generator-Tests/Integration-Tests/BaseGeneratorTest.cs b/tests/generator-Tests/Integration-Tests/BaseGeneratorTest.cs index a4d0c1e3c..19898e09c 100644 --- a/tests/generator-Tests/Integration-Tests/BaseGeneratorTest.cs +++ b/tests/generator-Tests/Integration-Tests/BaseGeneratorTest.cs @@ -110,7 +110,7 @@ private byte[] ReadAllBytesIgnoringLineEndings (string path) int readByte; while ((readByte = file.ReadByte()) != -1) { byte b = (byte)readByte; - if (b != '\r' && b != '\n') { + if (b != '\r' && b != '\n' && b != ' ' && b != '\t') { memoryStream.WriteByte (b); } } diff --git a/tests/generator-Tests/Integration-Tests/Java_Lang_Object.cs b/tests/generator-Tests/Integration-Tests/Java_Lang_Object.cs index 529c6e746..40fa28126 100644 --- a/tests/generator-Tests/Integration-Tests/Java_Lang_Object.cs +++ b/tests/generator-Tests/Integration-Tests/Java_Lang_Object.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using NUnit.Framework; using MonoDroid.Generation; @@ -13,9 +13,9 @@ public class Java_Lang_Object : BaseGeneratorTest public void Generated_OK () { Run (target: Xamarin.Android.Binder.CodeGenerationTarget.XamarinAndroid, - outputPath: "out/java.lang.Object", + outputPath: "out/java.lang.Object", apiDescriptionFile: "expected/java.lang.Object/java.lang.Object.xml", - expectedPath: "expected/java.lang.Object"); + expectedPath: "expected/java.lang.Object"); var javaLangObject = BuiltAssembly.GetType ("Java.Lang.Object"); diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/Common/WriteInterfaceRedeclaredDefaultMethod.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/Common/WriteInterfaceRedeclaredDefaultMethod.txt index 5b678cfd5..9fad896a3 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/Common/WriteInterfaceRedeclaredDefaultMethod.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/Common/WriteInterfaceRedeclaredDefaultMethod.txt @@ -1,10 +1,80 @@ // Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface2']" [Register ("java/code/IMyInterface2", "", "java.code.IMyInterface2Invoker")] public partial interface IMyInterface2 : java.code.IMyInterface { - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]" [Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterface2Invoker, MyAssembly")] void DoSomething (); - + +} +[global::Android.Runtime.Register ("java/code/IMyInterface2", DoNotGenerateAcw=true)] +internal partial class IMyInterface2Invoker : global::Java.Lang.Object, IMyInterface2 { + static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface2", typeof (IMyInterface2Invoker)); + + 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 IMyInterface2 GetObject (IntPtr handle, JniHandleOwnership transfer) + { + return global::Java.Lang.Object.GetObject (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.IMyInterface2")); + 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 IMyInterface2Invoker (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); + } + + static Delegate cb_DoSomething; + #pragma warning disable 0169 + static Delegate GetDoSomethingHandler () + { + if (cb_DoSomething == null) + cb_DoSomething = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_V) n_DoSomething); + return cb_DoSomething; + } + static void n_DoSomething (IntPtr jnienv, IntPtr native__this) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.DoSomething (); + } + #pragma warning restore 0169 + IntPtr id_DoSomething; + public unsafe void DoSomething () + { + if (id_DoSomething == IntPtr.Zero) + id_DoSomething = JNIEnv.GetMethodID (class_ref, "DoSomething", "()V"); + JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_DoSomething); + } + } - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteDefaultInterfaceMethodInvoker.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteDefaultInterfaceMethodInvoker.txt index 032a970c1..74030d2ea 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteDefaultInterfaceMethodInvoker.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteDefaultInterfaceMethodInvoker.txt @@ -1,6 +1,40 @@ +// 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 { + private static 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='DoDeclaration' and count(parameter)=0]" + [Register ("DoDeclaration", "()V", "GetDoDeclarationHandler:java.code.IMyInterfaceInvoker, MyAssembly")] + void DoDeclaration (); + + private static Delegate cb_DoDefault; + #pragma warning disable 0169 + private static Delegate GetDoDefaultHandler () + { + if (cb_DoDefault == null) + cb_DoDefault = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_V) n_DoDefault); + return cb_DoDefault; + } + private static void n_DoDefault (IntPtr jnienv, IntPtr native__this) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.DoDefault (); + } + #pragma warning restore 0169 + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoDefault' and count(parameter)=0]" + [Register ("DoDefault", "()V", "GetDoDefaultHandler:java.code.IMyInterface, MyAssembly")] + virtual unsafe void DoDefault () + { + const string __id = "DoDefault.()V"; + try { + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, null); + } finally { + } + } + +} [global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { - static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterfaceInvoker)); static IntPtr java_class_ref { diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDeclaration.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDeclaration.txt deleted file mode 100644 index 73ed59b91..000000000 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDeclaration.txt +++ /dev/null @@ -1,39 +0,0 @@ -// 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 { - - int Count { - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Count' and count(parameter)=0]" - [Register ("get_Count", "()I", "Getget_CountHandler:java.code.IMyInterfaceInvoker, ")] get; - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Count' and count(parameter)=1 and parameter[1][@type='int']]" - [Register ("set_Count", "(I)V", "Getset_Count_IHandler:java.code.IMyInterfaceInvoker, ")] set; - } - - string Key { - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Key' and count(parameter)=0]" - [Register ("get_Key", "()Ljava/lang/String;", "Getget_KeyHandler:java.code.IMyInterfaceInvoker, ")] get; - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Key' and count(parameter)=1 and parameter[1][@type='java.lang.String']]" - [Register ("set_Key", "(Ljava/lang/String;)V", "Getset_Key_Ljava_lang_String_Handler:java.code.IMyInterfaceInvoker, ")] set; - } - - int AbstractCount { - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_AbstractCount' and count(parameter)=0]" - [Register ("get_AbstractCount", "()I", "Getget_AbstractCountHandler:java.code.IMyInterfaceInvoker, ")] get; - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_AbstractCount' and count(parameter)=1 and parameter[1][@type='int']]" - [Register ("set_AbstractCount", "(I)V", "Getset_AbstractCount_IHandler:java.code.IMyInterfaceInvoker, ")] set; - } - - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='GetCountForKey' and count(parameter)=1 and parameter[1][@type='java.lang.String']]" - [Register ("GetCountForKey", "(Ljava/lang/String;)I", "GetGetCountForKey_Ljava_lang_String_Handler:java.code.IMyInterfaceInvoker, ")] - int GetCountForKey (string key); - - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='Key' and count(parameter)=0]" - [Register ("Key", "()Ljava/lang/String;", "GetKeyHandler:java.code.IMyInterfaceInvoker, ")] - string Key (); - - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='AbstractMethod' and count(parameter)=0]" - [Register ("AbstractMethod", "()V", "GetAbstractMethodHandler:java.code.IMyInterfaceInvoker, ")] - void AbstractMethod (); - -} - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultMethod.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultMethod.txt index 478da40e8..6bc2968d5 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultMethod.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultMethod.txt @@ -2,33 +2,80 @@ [Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] public partial interface IMyInterface : IJavaObject, IJavaPeerable { private static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true); - + private static Delegate cb_DoSomething; -#pragma warning disable 0169 + #pragma warning disable 0169 private static Delegate GetDoSomethingHandler () { if (cb_DoSomething == null) cb_DoSomething = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_V) n_DoSomething); return cb_DoSomething; } - private static void n_DoSomething (IntPtr jnienv, IntPtr native__this) { var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); __this.DoSomething (); } -#pragma warning restore 0169 - + #pragma warning restore 0169 // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]" [Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterface, MyAssembly")] - virtual unsafe void DoSomething () + virtual unsafe void DoSomething () { const string __id = "DoSomething.()V"; try { - _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, null); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, null); } finally { } } - + +} +[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + static 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 (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); + } + } - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultProperty.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultProperty.txt index 3410fecc4..6ee440bf9 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultProperty.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultProperty.txt @@ -2,47 +2,43 @@ [Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] public partial interface IMyInterface : IJavaObject, IJavaPeerable { private static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true); - + private static Delegate cb_get_Value; -#pragma warning disable 0169 + #pragma warning disable 0169 private static Delegate Getget_ValueHandler () { if (cb_get_Value == null) cb_get_Value = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_I) n_get_Value); return cb_get_Value; } - private static int n_get_Value (IntPtr jnienv, IntPtr native__this) { var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); return __this.Value; } -#pragma warning restore 0169 - + #pragma warning restore 0169 private static Delegate cb_set_Value_I; -#pragma warning disable 0169 + #pragma warning disable 0169 private static Delegate Getset_Value_IHandler () { if (cb_set_Value_I == null) cb_set_Value_I = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPI_V) n_set_Value_I); return cb_set_Value_I; } - private static void n_set_Value_I (IntPtr jnienv, IntPtr native__this, int value) { var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); __this.Value = value; } -#pragma warning restore 0169 - - virtual unsafe int Value { + #pragma warning restore 0169 + virtual 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:java.code.IMyInterface, MyAssembly")] get { const string __id = "get_Value.()I"; try { - var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); - return __rm; + var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); + return __rm; } finally { } } @@ -51,13 +47,62 @@ public partial interface IMyInterface : IJavaObject, IJavaPeerable { set { const string __id = "set_Value.(I)V"; try { - JniArgumentValue* __args = stackalloc JniArgumentValue [1]; - __args [0] = new JniArgumentValue (value); - _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); } finally { } } } - + +} +[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + static 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 (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); + } + } - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt index 07e773a27..f69e5386e 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt @@ -2,35 +2,82 @@ [Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] public partial interface IMyInterface : IJavaObject, IJavaPeerable { private static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true); - + private static Delegate cb_get_Value; -#pragma warning disable 0169 + #pragma warning disable 0169 private static Delegate Getget_ValueHandler () { if (cb_get_Value == null) cb_get_Value = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_I) n_get_Value); return cb_get_Value; } - private static int n_get_Value (IntPtr jnienv, IntPtr native__this) { var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); return __this.Value; } -#pragma warning restore 0169 - - virtual unsafe int Value { + #pragma warning restore 0169 + virtual 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:java.code.IMyInterface, MyAssembly")] get { const string __id = "get_Value.()I"; try { - var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); - return __rm; + var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); + return __rm; } finally { } } } - + +} +[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + static 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 (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); + } + } - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceRedeclaredDefaultMethod.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceRedeclaredDefaultMethod.txt new file mode 100644 index 000000000..9fad896a3 --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceRedeclaredDefaultMethod.txt @@ -0,0 +1,80 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface2']" +[Register ("java/code/IMyInterface2", "", "java.code.IMyInterface2Invoker")] +public partial interface IMyInterface2 : java.code.IMyInterface { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]" + [Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterface2Invoker, MyAssembly")] + void DoSomething (); + +} +[global::Android.Runtime.Register ("java/code/IMyInterface2", DoNotGenerateAcw=true)] +internal partial class IMyInterface2Invoker : global::Java.Lang.Object, IMyInterface2 { + static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface2", typeof (IMyInterface2Invoker)); + + 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 IMyInterface2 GetObject (IntPtr handle, JniHandleOwnership transfer) + { + return global::Java.Lang.Object.GetObject (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.IMyInterface2")); + 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 IMyInterface2Invoker (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); + } + + static Delegate cb_DoSomething; + #pragma warning disable 0169 + static Delegate GetDoSomethingHandler () + { + if (cb_DoSomething == null) + cb_DoSomething = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_V) n_DoSomething); + return cb_DoSomething; + } + static void n_DoSomething (IntPtr jnienv, IntPtr native__this) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.DoSomething (); + } + #pragma warning restore 0169 + IntPtr id_DoSomething; + public unsafe void DoSomething () + { + if (id_DoSomething == IntPtr.Zero) + id_DoSomething = JNIEnv.GetMethodID (class_ref, "DoSomething", "()V"); + JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_DoSomething); + } + +} diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceProperty.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceProperty.txt index fc352ddc6..e281a90ce 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceProperty.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceProperty.txt @@ -2,15 +2,15 @@ [Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] public partial interface IMyInterface : IJavaObject, IJavaPeerable { private static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true); - - static unsafe int Value { + + 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", "")] get { const string __id = "get_Value.()I"; try { - var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); - return __rm; + var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); + return __rm; } finally { } } @@ -19,13 +19,62 @@ public partial interface IMyInterface : IJavaObject, IJavaPeerable { set { const string __id = "set_Value.(I)V"; try { - JniArgumentValue* __args = stackalloc JniArgumentValue [1]; - __args [0] = new JniArgumentValue (value); - _members.StaticMethods.InvokeVoidMethod (__id, __args); + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + _members.StaticMethods.InvokeVoidMethod (__id, __args); } finally { } } } - + +} +[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + static 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 (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); + } + } - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1-NRT/WriteInterfaceDeclaration.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1-NRT/WriteInterfaceDeclaration.txt deleted file mode 100644 index 10e8a134b..000000000 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1-NRT/WriteInterfaceDeclaration.txt +++ /dev/null @@ -1,39 +0,0 @@ -// 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 { - - int Count { - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Count' and count(parameter)=0]" - [Register ("get_Count", "()I", "Getget_CountHandler:java.code.IMyInterfaceInvoker, ")] get; - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Count' and count(parameter)=1 and parameter[1][@type='int']]" - [Register ("set_Count", "(I)V", "Getset_Count_IHandler:java.code.IMyInterfaceInvoker, ")] set; - } - - string? Key { - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Key' and count(parameter)=0]" - [Register ("get_Key", "()Ljava/lang/String;", "Getget_KeyHandler:java.code.IMyInterfaceInvoker, ")] get; - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Key' and count(parameter)=1 and parameter[1][@type='java.lang.String']]" - [Register ("set_Key", "(Ljava/lang/String;)V", "Getset_Key_Ljava_lang_String_Handler:java.code.IMyInterfaceInvoker, ")] set; - } - - int AbstractCount { - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_AbstractCount' and count(parameter)=0]" - [Register ("get_AbstractCount", "()I", "Getget_AbstractCountHandler:java.code.IMyInterfaceInvoker, ")] get; - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_AbstractCount' and count(parameter)=1 and parameter[1][@type='int']]" - [Register ("set_AbstractCount", "(I)V", "Getset_AbstractCount_IHandler:java.code.IMyInterfaceInvoker, ")] set; - } - - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='GetCountForKey' and count(parameter)=1 and parameter[1][@type='java.lang.String']]" - [Register ("GetCountForKey", "(Ljava/lang/String;)I", "GetGetCountForKey_Ljava_lang_String_Handler:java.code.IMyInterfaceInvoker, ")] - int GetCountForKey (string? key); - - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='Key' and count(parameter)=0]" - [Register ("Key", "()Ljava/lang/String;", "GetKeyHandler:java.code.IMyInterfaceInvoker, ")] - string? Key (); - - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='AbstractMethod' and count(parameter)=0]" - [Register ("AbstractMethod", "()V", "GetAbstractMethodHandler:java.code.IMyInterfaceInvoker, ")] - void AbstractMethod (); - -} - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteDefaultInterfaceMethodInvoker.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteDefaultInterfaceMethodInvoker.txt index 832fd4f13..1f51a290c 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteDefaultInterfaceMethodInvoker.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteDefaultInterfaceMethodInvoker.txt @@ -1,39 +1,72 @@ +// 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 { + private static 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='DoDeclaration' and count(parameter)=0]" + [Register ("DoDeclaration", "()V", "GetDoDeclarationHandler:java.code.IMyInterfaceInvoker, MyAssembly")] + void DoDeclaration (); + + private static Delegate cb_DoDefault; + #pragma warning disable 0169 + private static Delegate GetDoDefaultHandler () + { + if (cb_DoDefault == null) + cb_DoDefault = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_V) n_DoDefault); + return cb_DoDefault; + } + private static void n_DoDefault (IntPtr jnienv, IntPtr native__this) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.DoDefault (); + } + #pragma warning restore 0169 + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoDefault' and count(parameter)=0]" + [Register ("DoDefault", "()V", "GetDoDefaultHandler:java.code.IMyInterface, MyAssembly")] + virtual unsafe void DoDefault () + { + const string __id = "DoDefault.()V"; + try { + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, null); + } finally { + } + } + +} [global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { - static 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 (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")); + 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) @@ -41,30 +74,28 @@ internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterf 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); } - + static Delegate cb_DoDeclaration; -#pragma warning disable 0169 + #pragma warning disable 0169 static Delegate GetDoDeclarationHandler () { if (cb_DoDeclaration == null) cb_DoDeclaration = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_V) n_DoDeclaration); return cb_DoDeclaration; } - static void n_DoDeclaration (IntPtr jnienv, IntPtr native__this) { var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); __this.DoDeclaration (); } -#pragma warning restore 0169 - + #pragma warning restore 0169 IntPtr id_DoDeclaration; public unsafe void DoDeclaration () { @@ -72,6 +103,5 @@ internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterf id_DoDeclaration = JNIEnv.GetMethodID (class_ref, "DoDeclaration", "()V"); JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_DoDeclaration); } - + } - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDeclaration.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDeclaration.txt deleted file mode 100644 index 73ed59b91..000000000 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDeclaration.txt +++ /dev/null @@ -1,39 +0,0 @@ -// 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 { - - int Count { - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Count' and count(parameter)=0]" - [Register ("get_Count", "()I", "Getget_CountHandler:java.code.IMyInterfaceInvoker, ")] get; - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Count' and count(parameter)=1 and parameter[1][@type='int']]" - [Register ("set_Count", "(I)V", "Getset_Count_IHandler:java.code.IMyInterfaceInvoker, ")] set; - } - - string Key { - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Key' and count(parameter)=0]" - [Register ("get_Key", "()Ljava/lang/String;", "Getget_KeyHandler:java.code.IMyInterfaceInvoker, ")] get; - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Key' and count(parameter)=1 and parameter[1][@type='java.lang.String']]" - [Register ("set_Key", "(Ljava/lang/String;)V", "Getset_Key_Ljava_lang_String_Handler:java.code.IMyInterfaceInvoker, ")] set; - } - - int AbstractCount { - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_AbstractCount' and count(parameter)=0]" - [Register ("get_AbstractCount", "()I", "Getget_AbstractCountHandler:java.code.IMyInterfaceInvoker, ")] get; - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_AbstractCount' and count(parameter)=1 and parameter[1][@type='int']]" - [Register ("set_AbstractCount", "(I)V", "Getset_AbstractCount_IHandler:java.code.IMyInterfaceInvoker, ")] set; - } - - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='GetCountForKey' and count(parameter)=1 and parameter[1][@type='java.lang.String']]" - [Register ("GetCountForKey", "(Ljava/lang/String;)I", "GetGetCountForKey_Ljava_lang_String_Handler:java.code.IMyInterfaceInvoker, ")] - int GetCountForKey (string key); - - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='Key' and count(parameter)=0]" - [Register ("Key", "()Ljava/lang/String;", "GetKeyHandler:java.code.IMyInterfaceInvoker, ")] - string Key (); - - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='AbstractMethod' and count(parameter)=0]" - [Register ("AbstractMethod", "()V", "GetAbstractMethodHandler:java.code.IMyInterfaceInvoker, ")] - void AbstractMethod (); - -} - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultMethod.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultMethod.txt index 38aaec1f6..985e9b2fd 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultMethod.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultMethod.txt @@ -2,33 +2,80 @@ [Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] public partial interface IMyInterface : IJavaObject, IJavaPeerable { private static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true); - + private static Delegate cb_DoSomething; -#pragma warning disable 0169 + #pragma warning disable 0169 private static Delegate GetDoSomethingHandler () { if (cb_DoSomething == null) cb_DoSomething = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_V) n_DoSomething); return cb_DoSomething; } - private static void n_DoSomething (IntPtr jnienv, IntPtr native__this) { var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); __this.DoSomething (); } -#pragma warning restore 0169 - + #pragma warning restore 0169 // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]" [Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterface, MyAssembly")] - virtual unsafe void DoSomething () + virtual unsafe void DoSomething () { const string __id = "DoSomething.()V"; try { - _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, null); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, null); } finally { } } - + +} +[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + static 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 (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); + } + } - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultProperty.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultProperty.txt index d37539854..52dc2d7b3 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultProperty.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultProperty.txt @@ -2,47 +2,43 @@ [Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] public partial interface IMyInterface : IJavaObject, IJavaPeerable { private static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true); - + private static Delegate cb_get_Value; -#pragma warning disable 0169 + #pragma warning disable 0169 private static Delegate Getget_ValueHandler () { if (cb_get_Value == null) cb_get_Value = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_I) n_get_Value); return cb_get_Value; } - private static int n_get_Value (IntPtr jnienv, IntPtr native__this) { var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); return __this.Value; } -#pragma warning restore 0169 - + #pragma warning restore 0169 private static Delegate cb_set_Value_I; -#pragma warning disable 0169 + #pragma warning disable 0169 private static Delegate Getset_Value_IHandler () { if (cb_set_Value_I == null) cb_set_Value_I = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPI_V) n_set_Value_I); return cb_set_Value_I; } - private static void n_set_Value_I (IntPtr jnienv, IntPtr native__this, int value) { var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); __this.Value = value; } -#pragma warning restore 0169 - - virtual unsafe int Value { + #pragma warning restore 0169 + virtual 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:java.code.IMyInterface, MyAssembly")] get { const string __id = "get_Value.()I"; try { - var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); - return __rm; + var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); + return __rm; } finally { } } @@ -51,13 +47,62 @@ public partial interface IMyInterface : IJavaObject, IJavaPeerable { set { const string __id = "set_Value.(I)V"; try { - JniArgumentValue* __args = stackalloc JniArgumentValue [1]; - __args [0] = new JniArgumentValue (value); - _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); } finally { } } } - + +} +[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + static 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 (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); + } + } - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt index 9f49a9585..7815f278f 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt @@ -2,35 +2,82 @@ [Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] public partial interface IMyInterface : IJavaObject, IJavaPeerable { private static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true); - + private static Delegate cb_get_Value; -#pragma warning disable 0169 + #pragma warning disable 0169 private static Delegate Getget_ValueHandler () { if (cb_get_Value == null) cb_get_Value = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_I) n_get_Value); return cb_get_Value; } - private static int n_get_Value (IntPtr jnienv, IntPtr native__this) { var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); return __this.Value; } -#pragma warning restore 0169 - - virtual unsafe int Value { + #pragma warning restore 0169 + virtual 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:java.code.IMyInterface, MyAssembly")] get { const string __id = "get_Value.()I"; try { - var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); - return __rm; + var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); + return __rm; } finally { } } } - + +} +[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + static 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 (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); + } + } - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceRedeclaredDefaultMethod.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceRedeclaredDefaultMethod.txt new file mode 100644 index 000000000..abd28d36f --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceRedeclaredDefaultMethod.txt @@ -0,0 +1,80 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface2']" +[Register ("java/code/IMyInterface2", "", "java.code.IMyInterface2Invoker")] +public partial interface IMyInterface2 : java.code.IMyInterface { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]" + [Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterface2Invoker, MyAssembly")] + void DoSomething (); + +} +[global::Android.Runtime.Register ("java/code/IMyInterface2", DoNotGenerateAcw=true)] +internal partial class IMyInterface2Invoker : global::Java.Lang.Object, IMyInterface2 { + static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface2", typeof (IMyInterface2Invoker)); + + 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 IMyInterface2 GetObject (IntPtr handle, JniHandleOwnership transfer) + { + return global::Java.Lang.Object.GetObject (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.IMyInterface2")); + 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 IMyInterface2Invoker (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); + } + + static Delegate cb_DoSomething; + #pragma warning disable 0169 + static Delegate GetDoSomethingHandler () + { + if (cb_DoSomething == null) + cb_DoSomething = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_V) n_DoSomething); + return cb_DoSomething; + } + static void n_DoSomething (IntPtr jnienv, IntPtr native__this) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.DoSomething (); + } + #pragma warning restore 0169 + IntPtr id_DoSomething; + public unsafe void DoSomething () + { + if (id_DoSomething == IntPtr.Zero) + id_DoSomething = JNIEnv.GetMethodID (class_ref, "DoSomething", "()V"); + JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_DoSomething); + } + +} diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceProperty.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceProperty.txt index 865304cfe..052c93e81 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceProperty.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceProperty.txt @@ -2,15 +2,15 @@ [Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] public partial interface IMyInterface : IJavaObject, IJavaPeerable { private static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true); - - static unsafe int Value { + + 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", "")] get { const string __id = "get_Value.()I"; try { - var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); - return __rm; + var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); + return __rm; } finally { } } @@ -19,13 +19,62 @@ public partial interface IMyInterface : IJavaObject, IJavaPeerable { set { const string __id = "set_Value.(I)V"; try { - JniArgumentValue* __args = stackalloc JniArgumentValue [1]; - __args [0] = new JniArgumentValue (value); - _members.StaticMethods.InvokeVoidMethod (__id, __args); + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + _members.StaticMethods.InvokeVoidMethod (__id, __args); } finally { } } } - + +} +[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + static 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 (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); + } + } - diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs index 44edc2110..8f8922f82 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs @@ -27,7 +27,7 @@ public void WriteKotlinUnsignedTypeMethodsClass () m.IsVirtual = false; generator.Context.ContextTypes.Push (@class); - generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.WriteType (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); generator.Context.ContextTypes.Pop (); Assert.AreEqual (GetTargetedExpected (nameof (WriteKotlinUnsignedTypeMethodsClass)), writer.ToString ().NormalizeLineEndings ()); @@ -50,7 +50,7 @@ public void WriteKotlinUnsignedTypePropertiesClass () } generator.Context.ContextTypes.Push (@class); - generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.WriteType (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); generator.Context.ContextTypes.Pop (); Assert.AreEqual (GetTargetedExpected (nameof (WriteKotlinUnsignedTypePropertiesClass)), writer.ToString ().NormalizeLineEndings ()); @@ -71,7 +71,7 @@ public void WriteKotlinUnsignedArrayTypeMethodsClass () m.IsVirtual = false; generator.Context.ContextTypes.Push (@class); - generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.WriteType (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); generator.Context.ContextTypes.Pop (); Assert.AreEqual (GetTargetedExpected (nameof (WriteKotlinUnsignedArrayTypeMethodsClass)), writer.ToString ().NormalizeLineEndings ()); @@ -94,7 +94,7 @@ public void WriteKotlinUnsignedArrayTypePropertiesClass () } generator.Context.ContextTypes.Push (@class); - generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.WriteType (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); generator.Context.ContextTypes.Pop (); Assert.AreEqual (GetTargetedExpected (nameof (WriteKotlinUnsignedArrayTypePropertiesClass)), writer.ToString ().NormalizeLineEndings ()); @@ -111,41 +111,6 @@ class XAJavaInteropCodeGeneratorTests : CodeGeneratorTests class XamarinAndroidCodeGeneratorTests : CodeGeneratorTests { protected override CodeGenerationTarget Target => CodeGenerationTarget.XamarinAndroid; - } - - abstract class CodeGeneratorTests : CodeGeneratorTestBase - { - [Test] - public void WriteCharSequenceEnumerator () - { - generator.WriteCharSequenceEnumerator (string.Empty); - - Assert.AreEqual (GetExpected (nameof (WriteCharSequenceEnumerator)), writer.ToString ().NormalizeLineEndings ()); - } - - [Test] - public void WriteClass () - { - var @class = SupportTypeBuilder.CreateClass ("java.code.MyClass", options); - - generator.Context.ContextTypes.Push (@class); - generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); - generator.Context.ContextTypes.Pop (); - - Assert.AreEqual (GetTargetedExpected (nameof (WriteClass)), writer.ToString ().NormalizeLineEndings ()); - } - - [Test] - public void WriteClassAbstractMembers () - { - var @class = SupportTypeBuilder.CreateClass ("java.code.MyClass", options); - - generator.Context.ContextTypes.Push (@class); - generator.WriteClassAbstractMembers (@class, string.Empty); - generator.Context.ContextTypes.Pop (); - - Assert.AreEqual (GetExpected (nameof (WriteClassAbstractMembers)), writer.ToString ().NormalizeLineEndings ()); - } [Test] public void WriteClassConstructors () @@ -492,106 +457,6 @@ public void WritedMethodWithEnumifiedReturn () StringAssert.Contains ("[return:global::Android.Runtime.GeneratedEnum]", builder.ToString (), "Should contain GeneratedEnumAttribute!"); } - [Test] - public void WriteInterface () - { - var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); - var gen_info = new GenerationInfo (null, null, null); - - generator.Context.ContextTypes.Push (iface); - generator.WriteInterface (iface, string.Empty, gen_info); - generator.Context.ContextTypes.Pop (); - - Assert.AreEqual (GetTargetedExpected (nameof (WriteInterface)), writer.ToString ().NormalizeLineEndings ()); - } - - [Test] - public void WriteInterfaceDeclaration () - { - var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); - - generator.Context.ContextTypes.Push (iface); - generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); - generator.Context.ContextTypes.Pop (); - - Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDeclaration)), writer.ToString ().NormalizeLineEndings ()); - } - - [Test] - public void WriteInterfaceExtensionMethods () - { - var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); - - generator.Context.ContextTypes.Push (iface); - generator.WriteInterfaceExtensionMethods (iface, string.Empty); - generator.Context.ContextTypes.Pop (); - - Assert.AreEqual (GetExpected (nameof (WriteInterfaceExtensionMethods)), writer.ToString ().NormalizeLineEndings ()); - } - - [Test] - public void WriteInterfaceEventArgs () - { - var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); - - generator.Context.ContextTypes.Push (iface); - generator.WriteInterfaceEventArgs (iface, iface.Methods [0], string.Empty); - generator.Context.ContextTypes.Pop (); - - Assert.AreEqual (GetExpected (nameof (WriteInterfaceEventArgs)), writer.ToString ().NormalizeLineEndings ()); - } - - [Test] - public void WriteInterfaceEventHandler () - { - var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); - - generator.Context.ContextTypes.Push (iface); - generator.WriteInterfaceEventHandler (iface, string.Empty); - generator.Context.ContextTypes.Pop (); - - Assert.AreEqual (GetExpected (nameof (WriteInterfaceEventHandler)), writer.ToString ().NormalizeLineEndings ()); - } - - [Test] - public void WriteInterfaceEventHandlerImpl () - { - var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); - - generator.Context.ContextTypes.Push (iface); - generator.WriteInterfaceEventHandlerImpl (iface, string.Empty); - generator.Context.ContextTypes.Pop (); - - Assert.AreEqual (GetExpected (nameof (WriteInterfaceEventHandlerImpl)), writer.ToString ().NormalizeLineEndings ()); - } - - [Test] - public void WriteInterfaceEventHandlerImplContent () - { - var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); - var handlers = new List (); - - generator.Context.ContextTypes.Push (iface); - generator.WriteInterfaceEventHandlerImplContent (iface, iface.Methods [0], string.Empty, true, string.Empty, handlers); - generator.Context.ContextTypes.Pop (); - - Assert.AreEqual (1, handlers.Count); - Assert.AreEqual ("GetCountForKey", handlers [0]); - Assert.AreEqual (GetExpected (nameof (WriteInterfaceEventHandlerImplContent)), writer.ToString ().NormalizeLineEndings ()); - } - - [Test] - public void WriteInterfaceExtensionsDeclaration () - { - var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); - - generator.Context.ContextTypes.Push (iface); - generator.WriteInterfaceExtensionsDeclaration (iface, string.Empty, "java.code.DeclaringType"); - generator.Context.ContextTypes.Pop (); - - Assert.AreEqual (GetExpected (nameof (WriteInterfaceExtensionsDeclaration)), writer.ToString ().NormalizeLineEndings ()); - } - [Test] public void WriteInterfaceInvoker () { @@ -852,23 +717,62 @@ public void WriteMethodWithVoidReturn () } [Test] - public void WriteParameterListCallArgs () + public void WriteMethodWithInvalidJavaName () { - var list = SupportTypeBuilder.CreateParameterList (options); + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "has-hyp$hen"); - generator.WriteParameterListCallArgs (list, string.Empty, false); + method.Name = "nohyphen"; - Assert.AreEqual (GetTargetedExpected (nameof (WriteParameterListCallArgs)), writer.ToString ().NormalizeLineEndings ()); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "method.Validate failed!"); + generator.WriteMethod (method, string.Empty, @class, true); + + var result = writer.ToString ().NormalizeLineEndings (); + + // Ensure we escape hyphens/dollar signs in callback names + Assert.False (result.Contains ("cb_has-hyp$hen")); + Assert.True (result.Contains ("cb_has_x45_hyp_x36_hen")); } [Test] - public void WriteParameterListCallArgsForInvoker () + public void WriteMethodWithInvalidParameterName () { - var list = SupportTypeBuilder.CreateParameterList (options); + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "DoStuff"); - generator.WriteParameterListCallArgs (list, string.Empty, true); + method.Parameters.Add (new Parameter ("$this", "byte[]", "byte[]", false)); - Assert.AreEqual (GetTargetedExpected (nameof (WriteParameterListCallArgsForInvoker)), writer.ToString ().NormalizeLineEndings ()); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "method.Validate failed!"); + generator.WriteMethod (method, string.Empty, @class, true); + + var result = writer.ToString ().NormalizeLineEndings (); + + // Ensure we escape dollar signs + Assert.False (result.Contains ("$this")); + } + + [Test] + public void WriteInterfaceExtensionsDeclaration () + { + var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); + + generator.Context.ContextTypes.Push (iface); + generator.WriteInterfaceExtensionsDeclaration (iface, string.Empty, "java.code.DeclaringType"); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetExpected (nameof (WriteInterfaceExtensionsDeclaration)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteInterfaceDeclaration () + { + var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); + + generator.Context.ContextTypes.Push (iface); + generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDeclaration)), writer.ToString ().NormalizeLineEndings ()); } [Test] @@ -880,6 +784,137 @@ public void WriteProperty () Assert.AreEqual (GetTargetedExpected (nameof (WriteProperty)), writer.ToString ().NormalizeLineEndings ()); } + } + + abstract class CodeGeneratorTests : CodeGeneratorTestBase + { + [Test] + public void WriteCharSequenceEnumerator () + { + generator.WriteCharSequenceEnumerator (string.Empty); + + Assert.AreEqual (GetExpected (nameof (WriteCharSequenceEnumerator)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteClass () + { + var @class = SupportTypeBuilder.CreateClass ("java.code.MyClass", options); + + generator.Context.ContextTypes.Push (@class); + generator.WriteType (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteClass)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteClassAbstractMembers () + { + var @class = SupportTypeBuilder.CreateClass ("java.code.MyClass", options); + + generator.Context.ContextTypes.Push (@class); + generator.WriteClassAbstractMembers (@class, string.Empty); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetExpected (nameof (WriteClassAbstractMembers)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteInterface () + { + var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); + var gen_info = new GenerationInfo (null, null, null); + + generator.Context.ContextTypes.Push (iface); + generator.WriteType (iface, string.Empty, gen_info); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteInterface)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteInterfaceExtensionMethods () + { + var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); + + generator.Context.ContextTypes.Push (iface); + generator.WriteInterfaceExtensionMethods (iface, string.Empty); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetExpected (nameof (WriteInterfaceExtensionMethods)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteInterfaceEventArgs () + { + var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); + + generator.Context.ContextTypes.Push (iface); + generator.WriteInterfaceEventArgs (iface, iface.Methods [0], string.Empty); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetExpected (nameof (WriteInterfaceEventArgs)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteInterfaceEventHandler () + { + var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); + + generator.Context.ContextTypes.Push (iface); + generator.WriteInterfaceEventHandler (iface, string.Empty); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetExpected (nameof (WriteInterfaceEventHandler)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteInterfaceEventHandlerImpl () + { + var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); + + generator.Context.ContextTypes.Push (iface); + generator.WriteInterfaceEventHandlerImpl (iface, string.Empty); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetExpected (nameof (WriteInterfaceEventHandlerImpl)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteInterfaceEventHandlerImplContent () + { + var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); + var handlers = new List (); + + generator.Context.ContextTypes.Push (iface); + generator.WriteInterfaceEventHandlerImplContent (iface, iface.Methods [0], string.Empty, true, string.Empty, handlers); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (1, handlers.Count); + Assert.AreEqual ("GetCountForKey", handlers [0]); + Assert.AreEqual (GetExpected (nameof (WriteInterfaceEventHandlerImplContent)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteParameterListCallArgs () + { + var list = SupportTypeBuilder.CreateParameterList (options); + + generator.WriteParameterListCallArgs (list, string.Empty, false); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteParameterListCallArgs)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteParameterListCallArgsForInvoker () + { + var list = SupportTypeBuilder.CreateParameterList (options); + + generator.WriteParameterListCallArgs (list, string.Empty, true); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteParameterListCallArgsForInvoker)), writer.ToString ().NormalizeLineEndings ()); + } [Test] public void WritePropertyAbstractDeclaration () @@ -933,41 +968,6 @@ public void WritePropertyInvoker () Assert.AreEqual (GetExpected (nameof (WritePropertyInvoker)), writer.ToString ().NormalizeLineEndings ()); } - [Test] - public void WriteMethodWithInvalidJavaName () - { - var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); - var method = new TestMethod (@class, "has-hyp$hen"); - - method.Name = "nohyphen"; - - Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "method.Validate failed!"); - generator.WriteMethod (method, string.Empty, @class, true); - - var result = writer.ToString ().NormalizeLineEndings (); - - // Ensure we escape hyphens/dollar signs in callback names - Assert.False (result.Contains ("cb_has-hyp$hen")); - Assert.True (result.Contains ("cb_has_x45_hyp_x36_hen")); - } - - [Test] - public void WriteMethodWithInvalidParameterName () - { - var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); - var method = new TestMethod (@class, "DoStuff"); - - method.Parameters.Add (new Parameter ("$this", "byte[]", "byte[]", false)); - - Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "method.Validate failed!"); - generator.WriteMethod (method, string.Empty, @class, true); - - var result = writer.ToString ().NormalizeLineEndings (); - - // Ensure we escape dollar signs - Assert.False (result.Contains ("$this")); - } - [Test] public void WritePropertyExplicitInterfaceParameterName () { @@ -1010,7 +1010,7 @@ public void WritePropertyExplicitInterfaceParameterName () var @class = gens.OfType ().First (c => c.Name == "SingleDateSelector"); generator.Context.ContextTypes.Push (@class); - generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.WriteType (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); generator.Context.ContextTypes.Pop (); var result = writer.ToString ().NormalizeLineEndings (); @@ -1030,12 +1030,12 @@ public void WriteClassExternalBase () @class.Validate (options, new GenericParameterDefinitionList (), generator.Context); generator.Context.ContextTypes.Push (@class); - generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.WriteType (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); generator.Context.ContextTypes.Pop (); var result = writer.ToString ().NormalizeLineEndings (); - Assert.True (result.Contains ("internal static IntPtr class_ref")); - Assert.False (result.Contains ("internal static new IntPtr class_ref")); + Assert.True (result.Contains ("internal static IntPtr class_ref".NormalizeLineEndings ())); + Assert.False (result.Contains ("internal static new IntPtr class_ref".NormalizeLineEndings ())); } [Test] @@ -1055,12 +1055,12 @@ public void WriteClassInternalBase () @class.BaseGen.FromXml = true; generator.Context.ContextTypes.Push (@class); - generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.WriteType (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); generator.Context.ContextTypes.Pop (); var result = writer.ToString ().NormalizeLineEndings (); - Assert.True (result.Contains ("internal static new IntPtr class_ref")); - Assert.False (result.Contains ("internal static IntPtr class_ref")); + Assert.True (result.Contains ("internal static new IntPtr class_ref".NormalizeLineEndings ())); + Assert.False (result.Contains ("internal static IntPtr class_ref".NormalizeLineEndings ())); } } } diff --git a/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs b/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs index 39f40d380..4f06bb1cc 100644 --- a/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs +++ b/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs @@ -34,13 +34,13 @@ protected override CodeGenerationOptions CreateOptions () public void WriteInterfaceDefaultMethod () { // Create an interface with a default method - var iface = SupportTypeBuilder.CreateEmptyInterface("java.code.IMyInterface"); + var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface"); iface.Methods.Add (new TestMethod (iface, "DoSomething").SetDefaultInterfaceMethod ()); iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); + generator.WriteType (iface, string.Empty, new GenerationInfo (null, null, null)); Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDefaultMethod)), writer.ToString ().NormalizeLineEndings ()); } @@ -61,10 +61,10 @@ public void WriteInterfaceRedeclaredDefaultMethod () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); iface2.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterfaceDeclaration (iface2, string.Empty, new GenerationInfo (null, null, null)); + generator.WriteType (iface2, string.Empty, new GenerationInfo (null, null, null)); // IMyInterface2 should generate the method as abstract, not a default method - Assert.AreEqual (GetExpected (nameof (WriteInterfaceRedeclaredDefaultMethod)), writer.ToString ().NormalizeLineEndings ()); + Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceRedeclaredDefaultMethod)), writer.ToString ().NormalizeLineEndings ()); } [Test] @@ -81,7 +81,7 @@ public void WriteInterfaceDefaultProperty () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); + generator.WriteType (iface, string.Empty, new GenerationInfo (null, null, null)); Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDefaultProperty)), writer.ToString ().NormalizeLineEndings ()); } @@ -100,7 +100,7 @@ public void WriteInterfaceDefaultPropertyGetterOnly () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); + generator.WriteType (iface, string.Empty, new GenerationInfo (null, null, null)); Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDefaultPropertyGetterOnly)), writer.ToString ().NormalizeLineEndings ()); } @@ -118,7 +118,7 @@ public void WriteDefaultInterfaceMethodInvoker () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); generator.Context.ContextTypes.Push (iface); - generator.WriteInterfaceInvoker (iface, string.Empty); + generator.WriteType (iface, string.Empty, new GenerationInfo (null, null, null)); generator.Context.ContextTypes.Pop (); Assert.AreEqual (GetTargetedExpected (nameof (WriteDefaultInterfaceMethodInvoker)), writer.ToString ().NormalizeLineEndings ()); @@ -143,7 +143,7 @@ public void WriteSealedOverriddenDefaultMethod () klass.FixupMethodOverrides (options); generator.Context.ContextTypes.Push (klass); - generator.WriteClass (klass, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + generator.WriteType (klass, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); generator.Context.ContextTypes.Pop (); // The method should not be marked as 'virtual sealed' @@ -186,7 +186,7 @@ public void WriteInterfaceRedeclaredChainDefaultMethod () var klass = (ClassGen)gens.First (g => g.Name == "ImplementedChainOverrideClass"); generator.Context.ContextTypes.Push (klass); - generator.WriteClass (klass, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + generator.WriteType (klass, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); generator.Context.ContextTypes.Pop (); Assert.True (writer.ToString ().Contains ("public virtual unsafe int Bar")); @@ -201,7 +201,7 @@ public void WriteStaticInterfaceMethod () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + generator.WriteType (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); Assert.AreEqual (GetTargetedExpected (nameof (WriteStaticInterfaceMethod)), writer.ToString ().NormalizeLineEndings ()); } @@ -222,7 +222,7 @@ public void WriteStaticInterfaceProperty () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); + generator.WriteType (iface, string.Empty, new GenerationInfo (null, null, null)); Assert.AreEqual (GetTargetedExpected (nameof (WriteStaticInterfaceProperty)), writer.ToString ().NormalizeLineEndings ()); } @@ -263,7 +263,7 @@ public void WriteUnnestedInterfaceTypes () parent_iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterface (parent_iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + generator.WriteType (parent_iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); Assert.AreEqual (GetTargetedExpected (nameof (WriteUnnestedInterfaceTypes)), writer.ToString ().NormalizeLineEndings ()); } @@ -281,7 +281,7 @@ public void WriteNestedInterfaceTypes () parent_iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterface (parent_iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + generator.WriteType (parent_iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); Assert.AreEqual (GetTargetedExpected (nameof (WriteNestedInterfaceTypes)), writer.ToString ().NormalizeLineEndings ()); } @@ -299,7 +299,7 @@ public void WriteNestedInterfaceClass () parent_iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterface (parent_iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + generator.WriteType (parent_iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); Assert.AreEqual (GetTargetedExpected (nameof (WriteNestedInterfaceClass)), writer.ToString ().NormalizeLineEndings ()); } @@ -327,7 +327,7 @@ public void DontWriteInterfaceConstsClass () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + generator.WriteType (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); Assert.False (writer.ToString ().Contains ("class ParentConsts")); } @@ -359,7 +359,7 @@ public void ObsoleteInterfaceAlternativeClass () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + generator.WriteType (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); Assert.AreEqual (GetTargetedExpected (nameof (ObsoleteInterfaceAlternativeClass)), writer.ToString ().NormalizeLineEndings ()); } @@ -391,7 +391,7 @@ public void RespectNoAlternativesForInterfaces () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + generator.WriteType (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); Assert.False (writer.ToString ().Contains ("class ParentConsts")); Assert.False (writer.ToString ().Contains ("class Parent")); @@ -422,7 +422,7 @@ public void DontInvalidateInterfaceDueToStaticOrDefaultMethods () // Inteface should pass validation despite invalid static/default methods Assert.True (result); - generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + generator.WriteType (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); var generated = writer.ToString (); @@ -455,7 +455,7 @@ public void GenerateProperNestedInterfaceSignatures () var gens = ParseApiDefinition (xml); var iface = gens[1].NestedTypes.OfType ().Single (); - generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + generator.WriteType (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); var generated = writer.ToString (); diff --git a/tests/generator-Tests/Unit-Tests/InterfaceConstantsTests.cs b/tests/generator-Tests/Unit-Tests/InterfaceConstantsTests.cs index 924d98622..e26876324 100644 --- a/tests/generator-Tests/Unit-Tests/InterfaceConstantsTests.cs +++ b/tests/generator-Tests/Unit-Tests/InterfaceConstantsTests.cs @@ -14,24 +14,12 @@ class JavaInteropInterfaceConstantsTests : InterfaceConstantsTests class XamarinAndroidInterfaceConstantsTests : InterfaceConstantsTests { protected override Xamarin.Android.Binder.CodeGenerationTarget Target => Xamarin.Android.Binder.CodeGenerationTarget.XamarinAndroid; - } - - abstract class InterfaceConstantsTests : CodeGeneratorTestBase - { - protected override CodeGenerationOptions CreateOptions () - { - var options = base.CreateOptions (); - - options.SupportInterfaceConstants = true; - - return options; - } [Test] public void WriteInterfaceFields () { // This is an interface that has both fields and method declarations - var iface = SupportTypeBuilder.CreateEmptyInterface("java.code.IMyInterface"); + var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface"); iface.Fields.Add (new TestField ("int", "MyConstantField").SetConstant ().SetValue ("7")); iface.Methods.Add (new TestMethod (iface, "DoSomething").SetAbstract ()); @@ -44,6 +32,18 @@ public void WriteInterfaceFields () Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceFields)), writer.ToString ().NormalizeLineEndings ()); } + } + + abstract class InterfaceConstantsTests : CodeGeneratorTestBase + { + protected override CodeGenerationOptions CreateOptions () + { + var options = base.CreateOptions (); + + options.SupportInterfaceConstants = true; + + return options; + } [Test] public void WriteConstSugarInterfaceFields () diff --git a/tests/generator-Tests/Unit-Tests/SourceWriters/MethodExplicitInterfaceImplementationTests.cs b/tests/generator-Tests/Unit-Tests/SourceWriters/MethodExplicitInterfaceImplementationTests.cs new file mode 100644 index 000000000..9bb1d05f6 --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/SourceWriters/MethodExplicitInterfaceImplementationTests.cs @@ -0,0 +1,28 @@ +using System.Linq; +using generator.SourceWriters; +using MonoDroid.Generation; +using NUnit.Framework; + +namespace generatortests.SourceWriters +{ + [TestFixture] + public class MethodExplicitInterfaceImplementationTests : SourceWritersTestBase + { + [Test] + public void MethodExplicitInterfaceImplementation () + { + var opt = new CodeGenerationOptions (); + var iface = SupportTypeBuilder.CreateInterface ("MyNamespace.IMyObject", opt); + var method = iface.Methods.First (m => m.Name == "GetCountForKey"); + + var wrapper = new MethodExplicitInterfaceImplementation (iface, method, opt); + var expected = +@"int MyNamespace.IMyObject.GetCountForKey (string key) +{ + return GetCountForKey (key) +}"; + + Assert.AreEqual (expected, GetOutput (wrapper).Trim ()); + } + } +} diff --git a/tests/generator-Tests/Unit-Tests/SourceWriters/MethodExtensionAsyncWrapperTests.cs b/tests/generator-Tests/Unit-Tests/SourceWriters/MethodExtensionAsyncWrapperTests.cs new file mode 100644 index 000000000..ab3f4f616 --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/SourceWriters/MethodExtensionAsyncWrapperTests.cs @@ -0,0 +1,45 @@ +using System.Linq; +using generator.SourceWriters; +using MonoDroid.Generation; +using NUnit.Framework; + +namespace generatortests.SourceWriters +{ + [TestFixture] + public class MethodExtensionAsyncWrapperTests : SourceWritersTestBase + { + [Test] + public void MethodExtensionAsyncWrapper () + { + var opt = new CodeGenerationOptions (); + var klass = SupportTypeBuilder.CreateClass ("MyNamespace.MyObject", opt); + var method = klass.Methods.First (m => m.Name == "GetCountForKey"); + + var wrapper = new MethodExtensionAsyncWrapper (method, opt, "OtherObject"); + var expected = +@"public static global::System.Threading.Tasks.Task GetCountForKeyAsync (this OtherObject self, string key) +{ + return global::System.Threading.Tasks.Task.Run (() => self.GetCountForKey (key)); +}"; + + Assert.AreEqual (expected, GetOutput (wrapper).Trim ()); + } + + [Test] + public void MethodExtensionAsyncWrapper_VoidReturnType () + { + var opt = new CodeGenerationOptions (); + var klass = SupportTypeBuilder.CreateClass ("MyNamespace.MyObject", opt); + var method = klass.Methods.First (m => m.Name == "StaticMethod"); + + var wrapper = new MethodExtensionAsyncWrapper (method, opt, "OtherObject"); + var expected = +@"public static global::System.Threading.Tasks.Task StaticMethodAsync (this OtherObject self) +{ + return global::System.Threading.Tasks.Task.Run (() => self.StaticMethod ()); +}"; + + Assert.AreEqual (expected, GetOutput (wrapper).Trim ()); + } + } +} diff --git a/tests/generator-Tests/Unit-Tests/SourceWriters/PeerMembersFieldTests.cs b/tests/generator-Tests/Unit-Tests/SourceWriters/PeerMembersFieldTests.cs new file mode 100644 index 000000000..d194f4963 --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/SourceWriters/PeerMembersFieldTests.cs @@ -0,0 +1,27 @@ +using generator.SourceWriters; +using MonoDroid.Generation; +using NUnit.Framework; +using Xamarin.Android.Binder; + +namespace generatortests.SourceWriters +{ + [TestFixture] + public class PeerMembersFieldTests : SourceWritersTestBase + { + [Test] + public void PeerMembersField_Class () + { + var field = new PeerMembersField (new CodeGenerationOptions (), "B", "MyJavaType", false); + + Assert.AreEqual ("static readonly JniPeerMembers _members = new JniPeerMembers (\"B\", typeof (MyJavaType));", GetOutput (field).Trim ()); + } + + [Test] + public void WeakImplementorField_Interface () + { + var field = new PeerMembersField (new CodeGenerationOptions { CodeGenerationTarget = CodeGenerationTarget.XAJavaInterop1 }, "B", "IMyJavaType", true); + + Assert.AreEqual ("private static readonly JniPeerMembers _members = new XAPeerMembers (\"B\", typeof (IMyJavaType), isInterface: true);", GetOutput (field).Trim ()); + } + } +} diff --git a/tests/generator-Tests/Unit-Tests/SourceWriters/SourceWritersTestBase.cs b/tests/generator-Tests/Unit-Tests/SourceWriters/SourceWritersTestBase.cs new file mode 100644 index 000000000..edffa935a --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/SourceWriters/SourceWritersTestBase.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; +using NUnit.Framework; +using Xamarin.SourceWriter; + +namespace generatortests.SourceWriters +{ + [TestFixture] + public class SourceWritersTestBase + { + protected string GetOutput (ISourceWriter writer) + { + var sw = new StringWriter (); + var cw = new CodeWriter (sw); + + writer.Write (cw); + + return sw.ToString (); + } + } +} diff --git a/tests/generator-Tests/Unit-Tests/SourceWriters/WeakImplementorFieldTests.cs b/tests/generator-Tests/Unit-Tests/SourceWriters/WeakImplementorFieldTests.cs new file mode 100644 index 000000000..c5aa53c7a --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/SourceWriters/WeakImplementorFieldTests.cs @@ -0,0 +1,26 @@ +using generator.SourceWriters; +using MonoDroid.Generation; +using NUnit.Framework; + +namespace generatortests.SourceWriters +{ + [TestFixture] + public class WeakImplementorFieldTests : SourceWritersTestBase + { + [Test] + public void WeakImplementorField_Regular () + { + var field = new WeakImplementorField ("foo", new CodeGenerationOptions ()); + + Assert.AreEqual ("WeakReference weak_implementor_foo;", GetOutput (field).Trim ()); + } + + [Test] + public void WeakImplementorField_Nullable () + { + var field = new WeakImplementorField ("foo", new CodeGenerationOptions { SupportNullableReferenceTypes = true }); + + Assert.AreEqual ("WeakReference? weak_implementor_foo;", GetOutput (field).Trim ()); + } + } +} diff --git a/tests/generator-Tests/Unit-Tests/SupportTypes.cs b/tests/generator-Tests/Unit-Tests/SupportTypes.cs index a4e4b5335..ec1253182 100644 --- a/tests/generator-Tests/Unit-Tests/SupportTypes.cs +++ b/tests/generator-Tests/Unit-Tests/SupportTypes.cs @@ -219,7 +219,7 @@ public static TestClass CreateClass (string className, CodeGenerationOptions opt { var @class = new TestClass (baseClass, className); - var ctor_name = className.Contains ('.') ? className.Substring (className.LastIndexOf ('.')) : className; + var ctor_name = className.Contains ('.') ? className.Substring (className.LastIndexOf ('.') + 1) : className; @class.Ctors.Add (CreateConstructor (@class, ctor_name, options)); @class.Ctors.Add (CreateConstructor (@class, ctor_name, options, new Parameter ("p0", "java.lang.String", "string", false))); diff --git a/tests/generator-Tests/Unit-Tests/TestExtensions.cs b/tests/generator-Tests/Unit-Tests/TestExtensions.cs index 6966689f0..aa619a11f 100644 --- a/tests/generator-Tests/Unit-Tests/TestExtensions.cs +++ b/tests/generator-Tests/Unit-Tests/TestExtensions.cs @@ -12,7 +12,7 @@ public static string NormalizeLineEndings (this string str) { // Normalize all line endings to \n so that our tests pass on // both Mac and Windows - return str?.Replace ("\r\n", "\n"); + return str?.Replace ("\r\n", "\n").Replace ("\n", "").Replace ("\t", "").Replace (" ", ""); } } } diff --git a/tools/generator/InterfaceGen.cs.rej b/tools/generator/InterfaceGen.cs.rej deleted file mode 100644 index a7dc75e25..000000000 --- a/tools/generator/InterfaceGen.cs.rej +++ /dev/null @@ -1,17 +0,0 @@ -*************** -*** 602,608 **** - GenerateAdapter (sw, indent); - GenerateInvoker (sw, indent); - GenerateEventHandler (sw, indent); -- GenerateJava (gen_info); - } - - public override void Generate (GenerationInfo gen_info) ---- 602,608 ---- - GenerateAdapter (sw, indent); - GenerateInvoker (sw, indent); - GenerateEventHandler (sw, indent); -+ // GenerateJava (gen_info); - } - - public override void Generate (GenerationInfo gen_info) diff --git a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs index b1b3a4745..4f3a50253 100644 --- a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs +++ b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs @@ -1752,8 +1752,9 @@ public void WritePropertyStringVariant (Property property, string indent) writer.WriteLine (); } - public void WriteType (GenBase gen, string indent, GenerationInfo gen_info) + public virtual void WriteType (GenBase gen, string indent, GenerationInfo gen_info) { + // Only used for XamarinAndroid code target if (gen is InterfaceGen iface) WriteInterface (iface, indent, gen_info); else if (gen is ClassGen @class) diff --git a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs index 52d2788ae..3c2af3e78 100644 --- a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs +++ b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs @@ -1,280 +1,64 @@ using System; +using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; +using generator.SourceWriters; using Mono.Options; +using Xamarin.SourceWriter; -namespace MonoDroid.Generation { - - class JavaInteropCodeGenerator : CodeGenerator { +namespace MonoDroid.Generation +{ + class JavaInteropCodeGenerator : CodeGenerator + { public JavaInteropCodeGenerator (TextWriter writer, CodeGenerationOptions options) : base (writer, options) { } - static string GetInvokeType (string type) - { - switch (type) { - case "Bool": return "Boolean"; - case "Byte": return "SByte"; - case "Int": return "Int32"; - case "Short": return "Int16"; - case "Long": return "Int64"; - case "Float": return "Single"; - case "UInt": return "Int32"; - case "UShort": return "Int16"; - case "ULong": return "Int64"; - case "UByte": return "SByte"; - default: return type; - } - } - - internal override string GetAllInterfaceImplements () => "IJavaObject, IJavaPeerable"; - - protected virtual string GetPeerMembersType () => "JniPeerMembers"; - - internal override void WriteClassHandle (ClassGen type, string indent, bool requireNew) - { - WritePeerMembers (indent + '\t', type.RawJniName, type.Name, false); - - writer.WriteLine ("{0}\tinternal static {1}IntPtr class_ref {{", indent, requireNew ? "new " : string.Empty); - writer.WriteLine ("{0}\t\tget {{", indent); - writer.WriteLine ("{0}\t\t\treturn _members.JniPeerType.PeerReference.Handle;", indent); - writer.WriteLine ("{0}\t\t}}", indent); - writer.WriteLine ("{0}\t}}", indent); - writer.WriteLine (); - if (type.BaseGen != null && type.InheritsObject) { - writer.WriteLine ("{0}\tpublic override global::Java.Interop.JniPeerMembers JniPeerMembers {{", indent); - writer.WriteLine ("{0}\t\tget {{ return _members; }}", indent); - writer.WriteLine ("{0}\t}}", indent); - writer.WriteLine (); - writer.WriteLine ("{0}\tprotected override IntPtr ThresholdClass {{", indent); - writer.WriteLine ("{0}\t\tget {{ return _members.JniPeerType.PeerReference.Handle; }}", indent); - writer.WriteLine ("{0}\t}}", indent); - writer.WriteLine (); - writer.WriteLine ("{0}\tprotected override global::System.Type ThresholdType {{", indent); - writer.WriteLine ("{0}\t\tget {{ return _members.ManagedPeerType; }}", indent); - writer.WriteLine ("{0}\t}}", indent); - writer.WriteLine (); - } - } - - internal override void WriteClassHandle (InterfaceGen type, string indent, string declaringType) - { - WritePeerMembers (indent, type.RawJniName, declaringType, type.Name == declaringType); - } - - internal override void WriteClassInvokerHandle (ClassGen type, string indent, string declaringType) - { - WritePeerMembers (indent, type.RawJniName, declaringType, false); - - writer.WriteLine (); - writer.WriteLine ("{0}public override global::Java.Interop.JniPeerMembers JniPeerMembers {{", indent); - writer.WriteLine ("{0}\tget {{ return _members; }}", indent); - writer.WriteLine ("{0}}}", indent); - writer.WriteLine (); - writer.WriteLine ("{0}protected override global::System.Type ThresholdType {{", indent); - writer.WriteLine ("{0}\tget {{ return _members.ManagedPeerType; }}", indent); - writer.WriteLine ("{0}}}", indent); - writer.WriteLine (); - } - - internal override void WriteInterfaceInvokerHandle (InterfaceGen type, string indent, string declaringType) - { - WritePeerMembers (indent, type.RawJniName, declaringType, false); - - writer.WriteLine (); - writer.WriteLine ("{0}static IntPtr java_class_ref {{", indent); - writer.WriteLine ("{0}\tget {{ return _members.JniPeerType.PeerReference.Handle; }}", indent); - writer.WriteLine ("{0}}}", indent); - writer.WriteLine (); - writer.WriteLine ("{0}public override global::Java.Interop.JniPeerMembers JniPeerMembers {{", indent); - writer.WriteLine ("{0}\tget {{ return _members; }}", indent); - writer.WriteLine ("{0}}}", indent); - writer.WriteLine (); - writer.WriteLine ("{0}protected override IntPtr ThresholdClass {{", indent); - writer.WriteLine ("{0}\tget {{ return class_ref; }}", indent); - writer.WriteLine ("{0}}}", indent); - writer.WriteLine (); - writer.WriteLine ("{0}protected override global::System.Type ThresholdType {{", indent); - writer.WriteLine ("{0}\tget {{ return _members.ManagedPeerType; }}", indent, declaringType); - writer.WriteLine ("{0}}}", indent); - writer.WriteLine (); - } - - internal override void WriteConstructorIdField (Ctor ctor, string indent) - { - // No method id_ctor field required; it's now an `id` constant in the binding. - } - - internal override void WriteConstructorBody (Ctor ctor, string indent, System.Collections.Specialized.StringCollection call_cleanup) - { - writer.WriteLine ("{0}{1}string __id = \"{2}\";", - indent, - ctor.IsNonStaticNestedType ? "" : "const ", - ctor.IsNonStaticNestedType - ? "(" + ctor.Parameters.GetJniNestedDerivedSignature (opt) + ")V" - : ctor.JniSignature); - writer.WriteLine (); - writer.WriteLine ("{0}if ({1} != IntPtr.Zero)", indent, Context.ContextType.GetObjectHandleProperty ("this")); - writer.WriteLine ("{0}\treturn;", indent); - writer.WriteLine (); - foreach (string prep in ctor.Parameters.GetCallPrep (opt)) - writer.WriteLine ("{0}{1}", indent, prep); - writer.WriteLine ("{0}try {{", indent); - var oldindent = indent; - indent += "\t"; - WriteParameterListCallArgs (ctor.Parameters, indent, invoker: false); - writer.WriteLine ("{0}var __r = _members.InstanceMethods.StartCreateInstance (__id, ((object) this).GetType (){1});", indent, ctor.Parameters.GetCallArgs (opt, invoker:false)); - writer.WriteLine ("{0}SetHandle (__r.Handle, JniHandleOwnership.TransferLocalRef);", indent); - writer.WriteLine ("{0}_members.InstanceMethods.FinishCreateInstance (__id, this{1});", indent, ctor.Parameters.GetCallArgs (opt, invoker:false)); - indent = oldindent; - writer.WriteLine ("{0}}} finally {{", indent); - foreach (string cleanup in call_cleanup) - writer.WriteLine ("{0}\t{1}", indent, cleanup); - writer.WriteLine ("{0}}}", indent); - } - - internal override void WriteMethodIdField (Method method, string indent) - { - // No method id_ field required; it's now an `id` constant in the binding. - } - - internal override void WriteMethodBody (Method method, string indent, GenBase type) - { - writer.WriteLine ("{0}const string __id = \"{1}.{2}\";", indent, method.JavaName, method.JniSignature); - foreach (string prep in method.Parameters.GetCallPrep (opt)) - writer.WriteLine ("{0}{1}", indent, prep); - writer.WriteLine ("{0}try {{", indent); - var oldindent = indent; - indent += "\t"; - WriteParameterListCallArgs (method.Parameters, indent, invoker: false); - - var invokeType = GetInvokeType (method.RetVal.CallMethodPrefix); - - writer.Write (indent); - if (!method.IsVoid) { - writer.Write ("var __rm = "); - } - - if (method.IsStatic) { - writer.WriteLine ("_members.StaticMethods.Invoke{0}Method (__id{1});", - invokeType, - method.Parameters.GetCallArgs (opt, invoker: false)); - } else if (method.IsFinal) { - writer.WriteLine ("_members.InstanceMethods.InvokeNonvirtual{0}Method (__id, this{1});", - invokeType, - method.Parameters.GetCallArgs (opt, invoker: false)); - } else if ((method.IsVirtual && !method.IsAbstract) || method.IsInterfaceDefaultMethod) { - writer.WriteLine ("_members.InstanceMethods.InvokeVirtual{0}Method (__id, this{1});", - invokeType, - method.Parameters.GetCallArgs (opt, invoker: false)); - } else { - writer.WriteLine ("_members.InstanceMethods.InvokeAbstract{0}Method (__id, this{1});", - invokeType, - method.Parameters.GetCallArgs (opt, invoker: false)); - } - - if (!method.IsVoid) { - var r = invokeType == "Object" ? "__rm.Handle" : "__rm"; - writer.WriteLine ("{0}return {2}{1};", indent, method.RetVal.FromNative (opt, r, true) + opt.GetNullForgiveness (method.RetVal), method.RetVal.ReturnCast); - } - - indent = oldindent; - writer.WriteLine ("{0}}} finally {{", indent); - foreach (string cleanup in method.Parameters.GetCallCleanup (opt)) - writer.WriteLine ("{0}\t{1}", indent, cleanup); - writer.WriteLine ("{0}}}", indent); - } - - internal override void WriteFieldIdField (Field field, string indent) - { - // No field id_ field required - } - - internal override void WriteFieldGetBody (Field field, string indent, GenBase type) + public static string GetInvokeType (string type) { - writer.WriteLine ("{0}const string __id = \"{1}.{2}\";", indent, field.JavaName, field.Symbol.JniName); - writer.WriteLine (); - - var invokeType = GetInvokeType (field.GetMethodPrefix); - var indirect = field.IsStatic ? "StaticFields" : "InstanceFields"; - var invoke = "Get{0}Value"; - invoke = string.Format (invoke, invokeType); - - writer.WriteLine ("{0}var __v = {4}_members.{1}.{2} (__id{3});", - indent, - indirect, - invoke, - field.IsStatic ? "" : ", this", - field.Symbol.ReturnCast); - - if (field.Symbol.IsArray) { - writer.WriteLine ("{0}return global::Android.Runtime.JavaArray<{1}>.FromJniHandle (__v.Handle, JniHandleOwnership.TransferLocalRef);", indent, opt.GetOutputName (field.Symbol.ElementType)); - } - else if (field.Symbol.NativeType != field.Symbol.FullName) { - writer.WriteLine ("{0}return {2}{1};", - indent, - field.Symbol.FromNative (opt, invokeType != "Object" ? "__v" : "__v.Handle", true) + opt.GetNullForgiveness (field), - field.Symbol.ReturnCast); - } else { - writer.WriteLine ("{0}return __v;", indent); - } + return type switch + { + "Bool" => "Boolean", + "Byte" => "SByte", + "Int" => "Int32", + "Short" => "Int16", + "Long" => "Int64", + "Float" => "Single", + "UInt" => "Int32", + "UShort" => "Int16", + "ULong" => "Int64", + "UByte" => "SByte", + _ => type, + }; } - internal override void WriteFieldSetBody (Field field, string indent, GenBase type) + public override void WriteType (GenBase gen, string indent, GenerationInfo gen_info) { - writer.WriteLine ("{0}const string __id = \"{1}.{2}\";", indent, field.JavaName, field.Symbol.JniName); - writer.WriteLine (); - - var invokeType = GetInvokeType (field.GetMethodPrefix); - var indirect = field.IsStatic ? "StaticFields" : "InstanceFields"; - - string arg; - bool have_prep = false; - if (field.Symbol.IsArray) { - arg = opt.GetSafeIdentifier (TypeNameUtilities.GetNativeName ("value")); - writer.WriteLine ("{0}IntPtr {1} = global::Android.Runtime.JavaArray<{2}>.ToLocalJniHandle (value);", indent, arg, opt.GetOutputName (field.Symbol.ElementType)); - } else { - foreach (string prep in field.SetParameters.GetCallPrep (opt)) { - have_prep = true; - writer.WriteLine ("{0}{1}", indent, prep); - } + TypeWriter type_writer; - arg = field.SetParameters [0].ToNative (opt); - if (field.SetParameters.HasCleanup && !have_prep) { - arg = opt.GetSafeIdentifier (TypeNameUtilities.GetNativeName ("value")); - writer.WriteLine ("{0}IntPtr {1} = global::Android.Runtime.JNIEnv.ToLocalJniHandle (value);", indent, arg); - } - } + if (gen is InterfaceGen iface) + type_writer = new BoundInterface (iface, opt, Context, gen_info); + else if (gen is ClassGen klass) + type_writer = new BoundClass (klass, opt, Context, gen_info); + else + throw new InvalidOperationException ("Unknown GenBase type"); - writer.WriteLine ("{0}try {{", indent); - - writer.WriteLine ("{0}\t_members.{1}.SetValue (__id{2}, {3});", - indent, - indirect, - field.IsStatic ? "" : ", this", - invokeType != "Object" ? arg : "new JniObjectReference (" + arg + ")"); - - writer.WriteLine ("{0}}} finally {{", indent); - if (field.Symbol.IsArray) { - writer.WriteLine ("{0}\tglobal::Android.Runtime.JNIEnv.DeleteLocalRef ({1});", indent, arg); - - } else { - foreach (string cleanup in field.SetParameters.GetCallCleanup (opt)) - writer.WriteLine ("{0}\t{1}", indent, cleanup); - if (field.SetParameters.HasCleanup && !have_prep) { - writer.WriteLine ("{0}\tglobal::Android.Runtime.JNIEnv.DeleteLocalRef ({1});", indent, arg); - } - } - writer.WriteLine ("{0}}}", indent); + var cw = new CodeWriter (writer, indent); + type_writer.Write (cw); } - void WritePeerMembers (string indent, string rawJniType, string declaringType, bool isInterface) - { - var signature = $"{(isInterface ? "private " : "")}static readonly JniPeerMembers _members = "; - var type = $"new {GetPeerMembersType ()} (\"{rawJniType}\", typeof ({declaringType}){(isInterface ? ", isInterface: true" : string.Empty)});"; - - writer.WriteLine ($"{indent}{signature}{type}"); - } + internal override void WriteClassHandle (ClassGen type, string indent, bool requireNew) => throw new NotImplementedException (); + internal override void WriteClassHandle (InterfaceGen type, string indent, string declaringType) => throw new NotImplementedException (); + internal override void WriteClassInvokerHandle (ClassGen type, string indent, string declaringType) => throw new NotImplementedException (); + internal override void WriteConstructorBody (Ctor ctor, string indent, StringCollection call_cleanup) => throw new NotImplementedException (); + internal override void WriteConstructorIdField (Ctor ctor, string indent) => throw new NotImplementedException (); + internal override void WriteFieldGetBody (Field field, string indent, GenBase type) => throw new NotImplementedException (); + internal override void WriteFieldIdField (Field field, string indent) => throw new NotImplementedException (); + internal override void WriteFieldSetBody (Field field, string indent, GenBase type) => throw new NotImplementedException (); + internal override void WriteInterfaceInvokerHandle (InterfaceGen type, string indent, string declaringType) => throw new NotImplementedException (); + internal override void WriteMethodBody (Method method, string indent, GenBase type) => throw new NotImplementedException (); + internal override void WriteMethodIdField (Method method, string indent) => throw new NotImplementedException (); } } diff --git a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/XAJavaInteropCodeGenerator.cs b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/XAJavaInteropCodeGenerator.cs index 0400070a1..6d34c0dc1 100644 --- a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/XAJavaInteropCodeGenerator.cs +++ b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/XAJavaInteropCodeGenerator.cs @@ -8,8 +8,6 @@ class XAJavaInteropCodeGenerator : JavaInteropCodeGenerator public XAJavaInteropCodeGenerator (TextWriter writer, CodeGenerationOptions options) : base (writer, options) { } - - protected override string GetPeerMembersType () => "XAPeerMembers"; } } diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/ClassGen.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/ClassGen.cs index f43183ed8..f2ca32f6f 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/ClassGen.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/ClassGen.cs @@ -144,7 +144,7 @@ public override void Generate (CodeGenerationOptions opt, GenerationInfo gen_inf } var generator = opt.CreateCodeGenerator (sw); - generator.WriteClass (this, hasNamespace ? "\t" : string.Empty, gen_info); + generator.WriteType (this, hasNamespace ? "\t" : string.Empty, gen_info); if (hasNamespace) { sw.WriteLine ("}"); diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs index 695fef306..f8545a4ce 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs @@ -92,7 +92,7 @@ public override void Generate (CodeGenerationOptions opt, GenerationInfo gen_inf } var generator = opt.CreateCodeGenerator (sw); - generator.WriteInterface (this, hasNamespace ? "\t" : string.Empty, gen_info); + generator.WriteType (this, hasNamespace ? "\t" : string.Empty, gen_info); if (hasNamespace) { sw.WriteLine ("}"); diff --git a/tools/generator/SourceWriters/Attributes/CustomAttr.cs b/tools/generator/SourceWriters/Attributes/CustomAttr.cs new file mode 100644 index 000000000..5ef54358f --- /dev/null +++ b/tools/generator/SourceWriters/Attributes/CustomAttr.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class CustomAttr : AttributeWriter + { + public string Value { get; set; } + + public CustomAttr (string value) + { + Value = value; + } + + public override void WriteAttribute (CodeWriter writer) + { + writer.WriteLine (Value); + } + } +} diff --git a/tools/generator/SourceWriters/Attributes/GeneratedEnumAttr.cs b/tools/generator/SourceWriters/Attributes/GeneratedEnumAttr.cs new file mode 100644 index 000000000..154818517 --- /dev/null +++ b/tools/generator/SourceWriters/Attributes/GeneratedEnumAttr.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class GeneratedEnumAttr : AttributeWriter + { + readonly bool is_return; + + public GeneratedEnumAttr (bool isReturn = false) => is_return = isReturn; + + public override void WriteAttribute (CodeWriter writer) + { + if (is_return) + writer.WriteLine ($"[return:global::Android.Runtime.GeneratedEnum]"); + else + writer.Write ($"[global::Android.Runtime.GeneratedEnum] "); + } + } +} diff --git a/tools/generator/SourceWriters/Attributes/ObsoleteAttr.cs b/tools/generator/SourceWriters/Attributes/ObsoleteAttr.cs new file mode 100644 index 000000000..eb528a171 --- /dev/null +++ b/tools/generator/SourceWriters/Attributes/ObsoleteAttr.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class ObsoleteAttr : AttributeWriter + { + public string Message { get; set; } + public bool IsError { get; set; } + public bool NoAtSign { get; set; } // TODO: Temporary to match unit tests + public bool WriteEmptyString { get; set; } // TODO: Temporary to match unit tests + public bool WriteAttributeSuffix { get; set; } // TODO: Temporary to match unit tests + public bool WriteGlobal { get; set; } // TODO: Temporary to match unit tests + + public ObsoleteAttr (string message = null, bool isError = false) + { + Message = message; + IsError = isError; + } + + public override void WriteAttribute (CodeWriter writer) + { + var attr_name = WriteAttributeSuffix ? "ObsoleteAttribute" : "Obsolete"; + + if (WriteGlobal) + attr_name = "global::System." + attr_name; + + if (Message is null && !WriteEmptyString && !IsError) { + writer.WriteLine ($"[{attr_name}]"); + return; + } + + writer.Write ($"[{attr_name} ({(NoAtSign ? "" : "@")}\"{Message}\""); + + if (IsError) + writer.Write (", error: true"); + + writer.WriteLine (")]"); + } + } +} diff --git a/tools/generator/SourceWriters/Attributes/RegisterAttr.cs b/tools/generator/SourceWriters/Attributes/RegisterAttr.cs new file mode 100644 index 000000000..81e897804 --- /dev/null +++ b/tools/generator/SourceWriters/Attributes/RegisterAttr.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class RegisterAttr : AttributeWriter + { + public string Name { get; set; } + public string Signature { get; set; } + public string Connector { get; set; } + public bool DoNotGenerateAcw { get; set; } + public string AdditionalProperties { get; set; } + public bool UseGlobal { get; set; } // TODO: Temporary for matching existing unit tests + public bool UseShortForm { get; set; } // TODO: Temporary for matching existing unit tests + public bool AcwLast { get; set; } // TODO: Temporary for matching existing unit tests + + public RegisterAttr (string name, string signature = null, string connector = null, bool noAcw = false, string additionalProperties = null) + { + Name = name; + Signature = signature; + Connector = connector; + DoNotGenerateAcw = noAcw; + AdditionalProperties = additionalProperties; + } + + public override void WriteAttribute (CodeWriter writer) + { + var sb = new StringBuilder (); + + if (UseGlobal) + sb.Append ($"[global::Android.Runtime.Register (\"{Name}\""); + else + sb.Append ($"[Register (\"{Name}\""); + + if ((Signature.HasValue () || Connector.HasValue ()) && !UseShortForm) + sb.Append ($", \"{Signature}\", \"{Connector}\""); + + if (DoNotGenerateAcw && !AcwLast) + sb.Append (", DoNotGenerateAcw=true"); + + if (AdditionalProperties.HasValue ()) + sb.Append (AdditionalProperties); + + if (DoNotGenerateAcw && AcwLast) + sb.Append (", DoNotGenerateAcw=true"); + + sb.Append (")]"); + + writer.WriteLine (sb.ToString ()); + } + } +} diff --git a/tools/generator/SourceWriters/BoundAbstractProperty.cs b/tools/generator/SourceWriters/BoundAbstractProperty.cs new file mode 100644 index 000000000..c5338bb40 --- /dev/null +++ b/tools/generator/SourceWriters/BoundAbstractProperty.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundAbstractProperty : PropertyWriter + { + readonly MethodCallback getter_callback; + readonly MethodCallback setter_callback; + + public BoundAbstractProperty (GenBase gen, Property property, CodeGenerationOptions opt) + { + Name = property.AdjustedName; + PropertyType = new TypeReferenceWriter (opt.GetTypeReferenceName (property.Getter.RetVal)); + + SetVisibility (property.Getter.RetVal.IsGeneric ? "protected" : property.Getter.Visibility); + + IsAbstract = true; + HasGet = true; + + var baseProp = gen.BaseSymbol != null ? gen.BaseSymbol.GetPropertyByName (property.Name, true) : null; + + if (baseProp != null) { + IsOverride = true; + } else { + IsShadow = gen.RequiresNew (property); + + getter_callback = new MethodCallback (gen, property.Getter, opt, property.AdjustedName, false); + + if (property.Setter != null) + setter_callback = new MethodCallback (gen, property.Setter, opt, property.AdjustedName, false); + } + + if (gen.IsGeneratable) + GetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.MetadataXPathReference}/method[@name='{property.Getter.JavaName}'{property.Getter.Parameters.GetMethodXPathPredicate ()}]\""); + if (property.Getter.IsReturnEnumified) + GetterAttributes.Add (new GeneratedEnumAttr (true)); + + GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.GetConnectorNameFull (opt), additionalProperties: property.Getter.AdditionalAttributeString ())); + + SourceWriterExtensions.AddMethodCustomAttributes (GetterAttributes, property.Getter); + + if (property.Setter != null) { + HasSet = true; + + if (gen.IsGeneratable) + SetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.MetadataXPathReference}/method[@name='{property.Setter.JavaName}'{property.Setter.Parameters.GetMethodXPathPredicate ()}]\""); + + SourceWriterExtensions.AddMethodCustomAttributes (SetterAttributes, property.Setter); + SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.GetConnectorNameFull (opt), additionalProperties: property.Setter.AdditionalAttributeString ())); + } + } + + public override void Write (CodeWriter writer) + { + // Need to write our property callbacks first + getter_callback?.Write (writer); + setter_callback?.Write (writer); + + base.Write (writer); + } + } +} diff --git a/tools/generator/SourceWriters/BoundClass.cs b/tools/generator/SourceWriters/BoundClass.cs new file mode 100644 index 000000000..619f1340f --- /dev/null +++ b/tools/generator/SourceWriters/BoundClass.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundClass : ClassWriter + { + readonly CodeGenerationOptions opt; + readonly List sibling_types = new List (); + + public BoundClass (ClassGen klass, CodeGenerationOptions opt, CodeGeneratorContext context, GenerationInfo generationInfo) + { + context.ContextTypes.Push (klass); + context.ContextGeneratedMethods = new List (); + + generationInfo.TypeRegistrations.Add (new KeyValuePair (klass.RawJniName, klass.AssemblyQualifiedName)); + + var is_enum = klass.base_symbol != null && klass.base_symbol.FullName == "Java.Lang.Enum"; + + if (is_enum) + generationInfo.Enums.Add (klass.RawJniName.Replace ('/', '.') + ":" + klass.Namespace + ":" + klass.JavaSimpleName); + + this.opt = opt; + + Name = klass.Name; + + SetVisibility (klass.Visibility); + IsShadow = klass.NeedsNew; + IsAbstract = klass.IsAbstract; + IsSealed = klass.IsFinal; + IsPartial = true; + + UsePriorityOrder = true; + + AddImplementedInterfaces (klass); + + Comments.Add ($"// Metadata.xml XPath class reference: path=\"{klass.MetadataXPathReference}\""); + + if (klass.IsDeprecated) + Attributes.Add (new ObsoleteAttr (klass.DeprecatedComment) { WriteAttributeSuffix = true }); + + Attributes.Add (new RegisterAttr (klass.RawJniName, null, null, true, klass.AdditionalAttributeString ()) { UseGlobal = true, UseShortForm = true }); + + if (klass.TypeParameters != null && klass.TypeParameters.Any ()) + Attributes.Add (new CustomAttr (klass.TypeParameters.ToGeneratedAttributeString ())); + + // Figure out our base class + string obj_type = null; + + if (klass.base_symbol != null) + obj_type = klass.base_symbol is GenericSymbol gs && + gs.IsConcrete ? gs.GetGenericType (null) : opt.GetOutputName (klass.base_symbol.FullName); + + if (klass.InheritsObject && obj_type != null) + Inherits = obj_type; + + // Handle fields + var seen = new HashSet (); + + SourceWriterExtensions.AddFields (this, klass, klass.Fields, seen, opt, context); + + var ic = new InterfaceConstsClass (klass, seen, opt, context); + + if (ic.ShouldGenerate) + NestedTypes.Add (ic); + + // Sibling classes + if (!klass.AssemblyQualifiedName.Contains ('/')) { + foreach (InterfaceExtensionInfo nestedIface in klass.GetNestedInterfaceTypes ()) + if (nestedIface.Type.Methods.Any (m => m.CanHaveStringOverload) || nestedIface.Type.Methods.Any (m => m.Asyncify)) + sibling_types.Add (new InterfaceExtensionsClass (nestedIface.Type, nestedIface.DeclaringType, opt)); + } + + if (klass.IsAbstract) + sibling_types.Add (new ClassInvokerClass (klass, opt)); + + AddNestedTypes (klass, opt, context, generationInfo); + AddBindingInfrastructure (klass); + AddConstructors (klass, opt, context); + AddProperties (klass, opt); + AddMethods (klass, opt, context); + AddAbstractMembers (klass, opt, context); + AddExplicitGenericInterfaceMembers (klass, opt); + AddCharSequenceEnumerator (klass); + + context.ContextGeneratedMethods.Clear (); + context.ContextTypes.Pop (); + } + + void AddBindingInfrastructure (ClassGen klass) + { + // @class.InheritsObject is true unless @class refers to java.lang.Object or java.lang.Throwable. (see ClassGen constructor) + // If @class's base class is defined in the same api.xml file, then it requires the new keyword to overshadow the internal + // members of its baseclass since the two classes will be defined in the same assembly. If the base class is not from the + // same api.xml file, the new keyword is not needed because the internal access modifier already prevents it from being seen. + var baseFromSameAssembly = klass?.BaseGen?.FromXml ?? false; + var requireNew = klass.InheritsObject && baseFromSameAssembly; + + Fields.Add (new PeerMembersField (opt, klass.RawJniName, klass.Name, false)); + Properties.Add (new ClassHandleGetter (requireNew)); + + if (klass.BaseGen != null && klass.InheritsObject) { + Properties.Add (new JniPeerMembersGetter ()); + Properties.Add (new ClassThresholdClassGetter ()); + Properties.Add (new ThresholdTypeGetter ()); + } + } + + void AddConstructors (ClassGen klass, CodeGenerationOptions opt, CodeGeneratorContext context) + { + // Add required constructor for all JLO inheriting classes + if (klass.FullName != "Java.Lang.Object" && klass.InheritsObject) + Constructors.Add (new JavaLangObjectConstructor (klass)); + + foreach (var ctor in klass.Ctors) { + // Don't bind final or protected constructors + if (klass.IsFinal && ctor.Visibility == "protected") + continue; + + // Bind Java declared constructor + Constructors.Add (new BoundConstructor(klass, ctor, klass.InheritsObject, opt, context)); + + // If the constructor takes ICharSequence, create an overload constructor that takes a string + if (ctor.Parameters.HasCharSequence && !klass.ContainsCtor (ctor.JniSignature.Replace ("java/lang/CharSequence", "java/lang/String"))) + Constructors.Add (new StringOverloadConstructor(klass, ctor, klass.InheritsObject, opt, context)); + } + } + + void AddCharSequenceEnumerator (ClassGen klass) + { + if (klass.Interfaces.Any (p => p.FullName == "Java.Lang.ICharSequence")) { + Methods.Add (new CharSequenceEnumeratorMethod ()); + Methods.Add (new CharSequenceGenericEnumeratorMethod ()); + } + } + + void AddImplementedInterfaces (ClassGen klass) + { + foreach (var isym in klass.Interfaces) { + if ((!(isym is GenericSymbol gs) ? isym : gs.Gen) is InterfaceGen gen && (gen.IsConstSugar || gen.RawVisibility != "public")) + continue; + + Implements.Add (opt.GetOutputName (isym.FullName)); + } + } + + void AddExplicitGenericInterfaceMembers (ClassGen klass, CodeGenerationOptions opt) + { + foreach (var gs in klass.Interfaces.Where (sym => sym is GenericSymbol).Cast ().Where (sym => sym.IsConcrete)) { + + // FIXME: not sure if excluding default methods is a valid idea... + foreach (var m in gs.Gen.Methods) { + if (m.IsInterfaceDefaultMethod || m.IsStatic) + continue; + if (m.IsGeneric) + Methods.Add (new GenericExplicitInterfaceImplementationMethod (m, gs, opt)); + } + + foreach (var p in gs.Gen.Properties) { + if (p.Getter?.IsInterfaceDefaultMethod == true || p.Getter?.IsStatic == true) + continue; + + if (p.Setter?.IsInterfaceDefaultMethod == true || p.Setter?.IsStatic == true) + continue; + + if (p.IsGeneric) { + var mappings = new Dictionary (); + for (var i = 0; i < gs.TypeParams.Length; i++) + mappings [gs.Gen.TypeParameters [i].Name] = gs.TypeParams [i].FullName; + + //If the property type is Java.Lang.Object, we don't need to generate an explicit implementation + if (p.Getter?.RetVal.GetGenericType (mappings) == "Java.Lang.Object") + return; + if (p.Setter?.Parameters [0].GetGenericType (mappings) == "Java.Lang.Object") + return; + + Properties.Add (new GenericExplicitInterfaceImplementationProperty (p, gs, gs.Gen.AssemblyQualifiedName + "Invoker", mappings, opt)); + } + } + } + + } + + void AddAbstractMembers (ClassGen klass, CodeGenerationOptions opt, CodeGeneratorContext context) + { + if (!klass.IsAbstract) + return; + + foreach (var gen in klass.GetAllDerivedInterfaces ()) + AddInterfaceAbstractMembers(klass, gen, opt, context); + } + + // For each interface, generate either an abstract method or an explicit implementation method. + void AddInterfaceAbstractMembers (ClassGen klass, InterfaceGen iface, CodeGenerationOptions opt, CodeGeneratorContext context) + { + foreach (var method in iface.Methods.Where (m => !m.IsInterfaceDefaultMethod && !m.IsStatic)) { + var mapped = false; + var sig = method.GetSignature (); + + if (context.ContextGeneratedMethods.Any (_ => _.Name == method.Name && _.JniSignature == method.JniSignature)) + continue; + + for (var cls = klass; cls != null; cls = cls.BaseGen) + if (cls.ContainsMethod (method, false) || cls != klass && klass.ExplicitlyImplementedInterfaceMethods.Contains (sig)) { + mapped = true; + break; + } + + if (mapped) + continue; + + if (klass.ExplicitlyImplementedInterfaceMethods.Contains (sig)) + Methods.Add (new MethodExplicitInterfaceImplementation(iface, method, opt)); + else + AddAbstractMethodDeclaration (klass, method, iface); + + context.ContextGeneratedMethods.Add (method); + } + + foreach (var prop in iface.Properties.Where (p => !p.Getter.IsInterfaceDefaultMethod && !p.Getter.IsStatic)) { + if (klass.ContainsProperty (prop.Name, false)) + continue; + + AddAbstractPropertyDeclaration (klass, prop, opt); + } + } + + void AddMethods (ClassGen klass, CodeGenerationOptions opt, CodeGeneratorContext context) + { + var methodsToDeclare = klass.Methods.AsEnumerable (); + + // This does not exclude overrides (unlike virtual methods) because we're not sure + // if calling the base interface default method via JNI expectedly dispatches to + // the derived method. + var defaultMethods = klass.GetAllDerivedInterfaces () + .SelectMany (i => i.Methods) + .Where (m => m.IsInterfaceDefaultMethod) + .Where (m => !klass.ContainsMethod (m, false, false)); + + var overrides = defaultMethods.Where (m => m.OverriddenInterfaceMethod != null); + + var overridens = defaultMethods.Where (m => overrides.Where (_ => _.Name == m.Name && _.JniSignature == m.JniSignature) + .Any (mm => mm.DeclaringType.GetAllDerivedInterfaces ().Contains (m.DeclaringType))); + + methodsToDeclare = opt.SupportDefaultInterfaceMethods ? methodsToDeclare : methodsToDeclare.Concat (defaultMethods.Except (overridens)).Where (m => m.DeclaringType.IsGeneratable); + + foreach (var m in methodsToDeclare) { + var virt = m.IsVirtual; + m.IsVirtual = !klass.IsFinal && virt; + + if (m.IsAbstract && m.OverriddenInterfaceMethod == null && (opt.SupportDefaultInterfaceMethods || !m.IsInterfaceDefaultMethod)) + AddAbstractMethodDeclaration (klass, m, null); + else + AddMethod (klass, m, opt); + + context.ContextGeneratedMethods.Add (m); + m.IsVirtual = virt; + } + + var methods = klass.Methods.Concat (klass.Properties.Where (p => p.Setter != null).Select (p => p.Setter)); + + foreach (var type in methods.Where (m => m.IsListenerConnector && m.EventName != string.Empty).Select (m => m.ListenerType).Distinct ()) { + AddInlineComment ($"#region \"Event implementation for {type.FullName}\""); + SourceWriterExtensions.AddInterfaceListenerEventsAndProperties (this, type, klass, opt); + AddInlineComment ("#endregion"); + } + + } + + void AddAbstractMethodDeclaration (GenBase klass, Method method, InterfaceGen iface) + { + Methods.Add (new BoundMethodAbstractDeclaration (iface, method, opt, klass)); + + if (method.IsReturnCharSequence || method.Parameters.HasCharSequence) + Methods.Add (new BoundMethodStringOverload (method, opt)); + + if (method.Asyncify) + Methods.Add (new MethodAsyncWrapper (method, opt)); + } + + void AddMethod (GenBase klass, Method method, CodeGenerationOptions opt) + { + if (!method.IsValid) + return; + + Methods.Add (new BoundMethod(klass, method, opt, true)); + + var name_and_jnisig = method.JavaName + method.JniSignature.Replace ("java/lang/CharSequence", "java/lang/String"); + var gen_string_overload = !method.IsOverride && method.Parameters.HasCharSequence && !klass.ContainsMethod (name_and_jnisig); + + if (gen_string_overload || method.IsReturnCharSequence) + Methods.Add (new BoundMethodStringOverload (method, opt)); + + if (method.Asyncify) + Methods.Add (new MethodAsyncWrapper (method, opt)); + } + + void AddProperties (ClassGen klass, CodeGenerationOptions opt) + { + foreach (var prop in klass.Properties) { + var get_virt = prop.Getter.IsVirtual; + var set_virt = prop.Setter == null ? false : prop.Setter.IsVirtual; + + prop.Getter.IsVirtual = !klass.IsFinal && get_virt; + + if (prop.Setter != null) + prop.Setter.IsVirtual = !klass.IsFinal && set_virt; + + if (prop.Getter.IsAbstract) + AddAbstractPropertyDeclaration (klass, prop, opt); + else + AddProperty (klass, prop, opt); + + prop.Getter.IsVirtual = get_virt; + + if (prop.Setter != null) + prop.Setter.IsVirtual = set_virt; + } + } + + void AddProperty (ClassGen klass, Property property, CodeGenerationOptions opt) + { + var bound_property = new BoundProperty (klass, property, opt, true, false); + Properties.Add (bound_property); + + if (property.Type.StartsWith ("Java.Lang.ICharSequence") && !bound_property.IsOverride) + Properties.Add (new BoundPropertyStringVariant (property, opt)); + } + + void AddAbstractPropertyDeclaration (ClassGen klass, Property property, CodeGenerationOptions opt) + { + var baseProp = klass.BaseSymbol?.GetPropertyByName (property.Name, true); + + if (baseProp != null) { + if (baseProp.Type != property.Getter.Return) { + // This may not be required if we can change generic parameter support to return constrained type (not just J.L.Object). + AddInlineComment ($"// skipped generating property {property.Name} because its Java method declaration is variant that we cannot represent in C#"); + return; + } + } + + Properties.Add (new BoundAbstractProperty (klass, property, opt)); + + if (property.Type.StartsWith ("Java.Lang.ICharSequence")) + Properties.Add (new BoundPropertyStringVariant (property, opt)); + } + + void AddNestedTypes (ClassGen klass, CodeGenerationOptions opt, CodeGeneratorContext context, GenerationInfo genInfo) + { + foreach (var nest in klass.NestedTypes) { + if (klass.BaseGen?.ContainsNestedType (nest) == true && nest is ClassGen c) + c.NeedsNew = true; + + NestedTypes.Add (SourceWriterExtensions.BuildManagedTypeModel (nest, opt, context, genInfo)); + } + } + + public override void Write (CodeWriter writer) + { + base.Write (writer); + + WriteSiblingClasses (writer); + } + + public void WriteSiblingClasses (CodeWriter writer) + { + foreach (var sibling in sibling_types) + sibling.Write (writer); + } + } +} diff --git a/tools/generator/SourceWriters/BoundConstructor.cs b/tools/generator/SourceWriters/BoundConstructor.cs new file mode 100644 index 000000000..d52f52ef5 --- /dev/null +++ b/tools/generator/SourceWriters/BoundConstructor.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.Android.Binder; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundConstructor : ConstructorWriter + { + protected Ctor constructor; + protected CodeGenerationOptions opt; + protected CodeGeneratorContext context; + readonly string context_this; + + public BoundConstructor (ClassGen klass, Ctor constructor, bool useBase, CodeGenerationOptions opt, CodeGeneratorContext context) + { + this.constructor = constructor; + this.opt = opt; + this.context = context; + + Name = klass.Name; + + Comments.Add (string.Format ("// Metadata.xml XPath constructor reference: path=\"{0}/constructor[@name='{1}'{2}]\"", klass.MetadataXPathReference, klass.JavaSimpleName, constructor.Parameters.GetMethodXPathPredicate ())); + + Attributes.Add (new RegisterAttr (".ctor", constructor.JniSignature, string.Empty, additionalProperties: constructor.AdditionalAttributeString ())); + + if (constructor.Deprecated != null) + Attributes.Add (new ObsoleteAttr (constructor.Deprecated.Replace ("\"", "\"\""))); + + if (constructor.CustomAttributes != null) + Attributes.Add (new CustomAttr (constructor.CustomAttributes)); + + if (constructor.Annotation != null) + Attributes.Add (new CustomAttr (constructor.Annotation)); + + SetVisibility (constructor.Visibility); + IsUnsafe = true; + + BaseCall = $"{(useBase ? "base" : "this")} (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)"; + context_this = context.ContextType.GetObjectHandleProperty ("this"); + + this.AddMethodParameters (constructor.Parameters, opt); + } + + protected override void WriteBody (CodeWriter writer) + { + writer.WriteLine ("{0}string __id = \"{1}\";", + constructor.IsNonStaticNestedType ? "" : "const ", + constructor.IsNonStaticNestedType + ? "(" + constructor.Parameters.GetJniNestedDerivedSignature (opt) + ")V" + : constructor.JniSignature); + writer.WriteLine (); + writer.WriteLine ($"if ({context_this} != IntPtr.Zero)"); + writer.WriteLine ("\treturn;"); + writer.WriteLine (); + + foreach (var prep in constructor.Parameters.GetCallPrep (opt)) + writer.WriteLine (prep); + + writer.WriteLine ("try {"); + + writer.Indent (); + WriteParamterListCallArgs (writer, constructor.Parameters, false, opt); + writer.WriteLine ("var __r = _members.InstanceMethods.StartCreateInstance (__id, ((object) this).GetType (){0});", constructor.Parameters.GetCallArgs (opt, invoker: false)); + writer.WriteLine ("SetHandle (__r.Handle, JniHandleOwnership.TransferLocalRef);"); + writer.WriteLine ("_members.InstanceMethods.FinishCreateInstance (__id, this{0});", constructor.Parameters.GetCallArgs (opt, invoker: false)); + writer.Unindent (); + + writer.WriteLine ("} finally {"); + + writer.Indent (); + var call_cleanup = constructor.Parameters.GetCallCleanup (opt); + foreach (string cleanup in call_cleanup) + writer.WriteLine (cleanup); + writer.Unindent (); + + writer.WriteLine ("}"); + } + + void WriteParamterListCallArgs (CodeWriter writer, ParameterList parameters, bool invoker, CodeGenerationOptions opt) + { + if (parameters.Count == 0) + return; + + string JValue = "JValue"; + + switch (opt.CodeGenerationTarget) { + case CodeGenerationTarget.XAJavaInterop1: + case CodeGenerationTarget.JavaInterop1: + JValue = invoker ? JValue : "JniArgumentValue"; + break; + } + + writer.WriteLine ("{0}* __args = stackalloc {0} [{1}];", JValue, parameters.Count); + + for (var i = 0; i < parameters.Count; ++i) { + var p = parameters [i]; + writer.WriteLine ("__args [{0}] = new {1} ({2});", i, JValue, p.GetCall (opt)); + } + } + } + + public class StringOverloadConstructor : BoundConstructor + { + public StringOverloadConstructor (ClassGen klass, Ctor constructor, bool useBase, CodeGenerationOptions opt, CodeGeneratorContext context) : + base (klass, constructor, useBase, opt, context) + { + Comments.Clear (); + Parameters.Clear (); + + // TODO: This is likely incorrect but done for compat with old generator. + // A string overload for an obsolete ctor will not be marked obsolete. + var obsolete_attr = Attributes.OfType ().FirstOrDefault (); + + if (obsolete_attr != null) + Attributes.Remove (obsolete_attr); + + this.AddMethodParametersStringOverloads (constructor.Parameters, opt); + } + } +} diff --git a/tools/generator/SourceWriters/BoundField.cs b/tools/generator/SourceWriters/BoundField.cs new file mode 100644 index 000000000..fb89716b1 --- /dev/null +++ b/tools/generator/SourceWriters/BoundField.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundField : FieldWriter + { + // // Metadata.xml XPath field reference: path="/api/package[@name='android.os']/class[@name='Vibrator']/field[@name='VIBRATION_EFFECT_SUPPORT_UNKNOWN']" + // [Register ("VIBRATION_EFFECT_SUPPORT_UNKNOWN", ApiSince = 30)] + // [Obsolete ("This constant will be removed in the future version. Use Android.OS.VibrationEffectSupport enum directly instead of this field.", error: true)] + // public const Android.OS.VibrationEffectSupport VibrationEffectSupportUnknown = (Android.OS.VibrationEffectSupport) 0; + public BoundField (GenBase type, Field field, CodeGenerationOptions opt) + { + Name = field.Name; + Type = new TypeReferenceWriter (opt.GetOutputName (field.Symbol.FullName)); + + Comments.Add ($"// Metadata.xml XPath field reference: path=\"{type.MetadataXPathReference}/field[@name='{field.JavaName}']\""); + + Attributes.Add (new RegisterAttr (field.JavaName, additionalProperties: field.AdditionalAttributeString ())); + + if (field.IsEnumified) + Attributes.Add (new GeneratedEnumAttr ()); + if (field.IsDeprecated) + Attributes.Add (new ObsoleteAttr (field.DeprecatedComment, field.IsDeprecatedError) { NoAtSign = true, WriteEmptyString = true }); + if (field.Annotation.HasValue ()) + Attributes.Add (new CustomAttr (field.Annotation)); + + SetVisibility (field.Visibility); + IsConst = true; + + // the Value complication is due to constant enum from negative integer value (C# compiler requires explicit parenthesis). + Value = $"({opt.GetOutputName (field.Symbol.FullName)}) {(field.Value.Contains ('-') && field.Symbol.FullName.Contains ('.') ? '(' + field.Value + ')' : field.Value)}"; + } + } +} diff --git a/tools/generator/SourceWriters/BoundFieldAsProperty.cs b/tools/generator/SourceWriters/BoundFieldAsProperty.cs new file mode 100644 index 000000000..dcb332fae --- /dev/null +++ b/tools/generator/SourceWriters/BoundFieldAsProperty.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + // This is a field that is not a constant, and thus we need to generate it as a + // property so it can access the Java field. + public class BoundFieldAsProperty : PropertyWriter + { + readonly Field field; + readonly CodeGenerationOptions opt; + + public BoundFieldAsProperty (GenBase type, Field field, CodeGenerationOptions opt) + { + this.field = field; + this.opt = opt; + + Name = field.Name; + + var fieldType = field.Symbol.IsArray ? "IList<" + field.Symbol.ElementType + ">" + opt.NullableOperator : opt.GetTypeReferenceName (field); + PropertyType = new TypeReferenceWriter (fieldType); + + Comments.Add ($"// Metadata.xml XPath field reference: path=\"{type.MetadataXPathReference}/field[@name='{field.JavaName}']\""); + + if (field.IsEnumified) + Attributes.Add (new GeneratedEnumAttr ()); + + Attributes.Add (new RegisterAttr (field.JavaName, additionalProperties: field.AdditionalAttributeString ())); + + if (field.IsDeprecated) + Attributes.Add (new ObsoleteAttr (field.DeprecatedComment, field.IsDeprecatedError) { NoAtSign = true }); + + SetVisibility (field.Visibility); + UseExplicitPrivateKeyword = true; + + IsStatic = field.IsStatic; + + HasGet = true; + + if (!field.IsConst) + HasSet = true; + } + + public override void Write (CodeWriter writer) + { + // This is just a temporary hack to write the [GeneratedEnum] attribute before the // Metadata.xml + // comment so that we are 100% equal to pre-refactor. + var generated_attr = Attributes.OfType ().FirstOrDefault (); + + generated_attr?.WriteAttribute (writer); + writer.WriteLine (); + + base.Write (writer); + } + + public override void WriteAttributes (CodeWriter writer) + { + // Part of above hack ^^ + foreach (var att in Attributes.Where (p => !(p is GeneratedEnumAttr))) + att.WriteAttribute (writer); + } + + protected override void WriteGetterBody (CodeWriter writer) + { + writer.WriteLine ($"const string __id = \"{field.JavaName}.{field.Symbol.JniName}\";"); + writer.WriteLine (); + + var invokeType = SourceWriterExtensions.GetInvokeType (field.GetMethodPrefix); + var indirect = field.IsStatic ? "StaticFields" : "InstanceFields"; + var invoke = "Get{0}Value"; + + invoke = string.Format (invoke, invokeType); + + writer.WriteLine ($"var __v = {field.Symbol.ReturnCast}_members.{indirect}.{invoke} (__id{(field.IsStatic ? "" : ", this")});"); + + if (field.Symbol.IsArray) { + writer.WriteLine ($"return global::Android.Runtime.JavaArray<{opt.GetOutputName (field.Symbol.ElementType)}>.FromJniHandle (__v.Handle, JniHandleOwnership.TransferLocalRef);"); + } else if (field.Symbol.NativeType != field.Symbol.FullName) { + writer.WriteLine ($"return {field.Symbol.ReturnCast}{(field.Symbol.FromNative (opt, invokeType != "Object" ? "__v" : "__v.Handle", true) + opt.GetNullForgiveness (field))};"); + } else { + writer.WriteLine ("return __v;"); + } + } + + protected override void WriteSetterBody (CodeWriter writer) + { + writer.WriteLine ($"const string __id = \"{field.JavaName}.{field.Symbol.JniName}\";"); + writer.WriteLine (); + + var invokeType = SourceWriterExtensions.GetInvokeType (field.GetMethodPrefix); + var indirect = field.IsStatic ? "StaticFields" : "InstanceFields"; + + string arg; + bool have_prep = false; + + if (field.Symbol.IsArray) { + arg = opt.GetSafeIdentifier (TypeNameUtilities.GetNativeName ("value")); + writer.WriteLine ($"IntPtr {arg} = global::Android.Runtime.JavaArray<{opt.GetOutputName (field.Symbol.ElementType)}>.ToLocalJniHandle (value);"); + } else { + foreach (var prep in field.SetParameters.GetCallPrep (opt)) { + have_prep = true; + writer.WriteLine (prep); + } + + arg = field.SetParameters [0].ToNative (opt); + + if (field.SetParameters.HasCleanup && !have_prep) { + arg = opt.GetSafeIdentifier (TypeNameUtilities.GetNativeName ("value")); + writer.WriteLine ($"IntPtr {arg} = global::Android.Runtime.JNIEnv.ToLocalJniHandle (value);"); + } + } + + writer.WriteLine ("try {"); + + writer.WriteLine ($"\t_members.{indirect}.SetValue (__id{(field.IsStatic ? "" : ", this")}, {(invokeType != "Object" ? arg : "new JniObjectReference (" + arg + ")")});"); + + writer.WriteLine ("} finally {"); + writer.Indent (); + + if (field.Symbol.IsArray) { + writer.WriteLine ($"global::Android.Runtime.JNIEnv.DeleteLocalRef ({arg});"); + } else { + foreach (var cleanup in field.SetParameters.GetCallCleanup (opt)) + writer.WriteLine (cleanup); + if (field.SetParameters.HasCleanup && !have_prep) { + writer.WriteLine ($"global::Android.Runtime.JNIEnv.DeleteLocalRef ({arg});"); + } + } + + writer.Unindent (); + writer.WriteLine ("}"); + } + } +} diff --git a/tools/generator/SourceWriters/BoundInterface.cs b/tools/generator/SourceWriters/BoundInterface.cs new file mode 100644 index 000000000..d0e94f5c8 --- /dev/null +++ b/tools/generator/SourceWriters/BoundInterface.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundInterface : InterfaceWriter + { + readonly List pre_sibling_types = new List (); + readonly List post_sibling_types = new List (); + readonly bool dont_generate; + + public BoundInterface (InterfaceGen iface, CodeGenerationOptions opt, CodeGeneratorContext context, GenerationInfo genInfo) + { + context.ContextTypes.Push (iface); + + Name = iface.Name; + + AddNestedSiblingTypes (iface, opt, context, genInfo); + AddAlternativesClass (iface, opt, context); + + // If this interface is just fields and we can't generate any of them + // then we don't need to write the interface. We still keep this type + // because it may have nested types or need an InterfaceMemberAlternativeClass. + if (iface.IsConstSugar && iface.GetGeneratableFields (opt).Count () == 0) { + dont_generate = true; + return; + } + + IsPartial = true; + + UsePriorityOrder = true; + + SetVisibility (iface.Visibility); + + Comments.Add ($"// Metadata.xml XPath interface reference: path=\"{iface.MetadataXPathReference}\""); + + if (iface.IsDeprecated) + Attributes.Add (new ObsoleteAttr (iface.DeprecatedComment) { WriteAttributeSuffix = true, WriteEmptyString = true }); + + if (!iface.IsConstSugar) { + var signature = string.IsNullOrWhiteSpace (iface.Namespace) + ? iface.FullName.Replace ('.', '/') + : iface.Namespace + "." + iface.FullName.Substring (iface.Namespace.Length + 1).Replace ('.', '/'); + + Attributes.Add (new RegisterAttr (iface.RawJniName, string.Empty, signature + "Invoker", additionalProperties: iface.AdditionalAttributeString ())); + } + + if (iface.TypeParameters != null && iface.TypeParameters.Any ()) + Attributes.Add (new CustomAttr (iface.TypeParameters.ToGeneratedAttributeString ())); + + AddInheritedInterfaces (iface, opt); + + AddClassHandle (iface, opt); + AddFields (iface, opt, context); + AddProperties (iface, opt); + AddMethods (iface, opt); + AddNestedTypes (iface, opt, context, genInfo); + + // If this interface is just constant fields we don't need to add all the invoker bits + if (iface.IsConstSugar) + return; + + if (!iface.AssemblyQualifiedName.Contains ('/')) { + if (iface.Methods.Any (m => m.CanHaveStringOverload) || iface.Methods.Any (m => m.Asyncify)) + post_sibling_types.Add (new InterfaceExtensionsClass (iface, null, opt)); + } + + post_sibling_types.Add (new InterfaceInvokerClass (iface, opt, context)); + + AddInterfaceEventHandler (iface, opt, context); + + context.ContextTypes.Pop (); + } + + + void AddNestedSiblingTypes (InterfaceGen iface, CodeGenerationOptions opt, CodeGeneratorContext context, GenerationInfo genInfo) + { + // Generate sibling types for nested types we don't want to nest + foreach (var nest in iface.NestedTypes.Where (t => t.Unnest)) + pre_sibling_types.Add (SourceWriterExtensions.BuildManagedTypeModel (nest, opt, context, genInfo)); + } + + + void AddAlternativesClass (InterfaceGen iface, CodeGenerationOptions opt, CodeGeneratorContext context) + { + if (iface.NoAlternatives) + return; + + var staticMethods = iface.Methods.Where (m => m.IsStatic); + + if (iface.Fields.Any () || staticMethods.Any ()) + pre_sibling_types.Add (new InterfaceMemberAlternativeClass (iface, opt, context)); + } + + void AddInterfaceEventHandler (InterfaceGen iface, CodeGenerationOptions opt, CodeGeneratorContext context) + { + if (!iface.IsListener) + return; + + foreach (var method in iface.Methods.Where (m => m.EventName != string.Empty)) { + if (method.RetVal.IsVoid || method.IsEventHandlerWithHandledProperty) { + if (!method.IsSimpleEventHandler || method.IsEventHandlerWithHandledProperty) + post_sibling_types.Add (new InterfaceEventArgsClass (iface, method, opt, context)); + } else { + var del = new DelegateWriter { + Name = iface.GetEventDelegateName (method), + Type = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal)), + IsPublic = true + }; + + SourceWriterExtensions.AddMethodParameters (del, method.Parameters, opt); + + post_sibling_types.Add (del); + } + } + + post_sibling_types.Add (new InterfaceEventHandlerImplClass (iface, opt, context)); + } + + void AddInheritedInterfaces (InterfaceGen iface, CodeGenerationOptions opt) + { + foreach (var isym in iface.Interfaces) { + var igen = (isym is GenericSymbol ? (isym as GenericSymbol).Gen : isym) as InterfaceGen; + + if (igen.IsConstSugar || igen.RawVisibility != "public") + continue; + + Implements.Add (opt.GetOutputName (isym.FullName)); + } + + if (Implements.Count == 0 && !iface.IsConstSugar) + Implements.AddRange (new [] { "IJavaObject", "IJavaPeerable" }); + } + + void AddClassHandle (InterfaceGen iface, CodeGenerationOptions opt) + { + if (opt.SupportDefaultInterfaceMethods && (iface.HasDefaultMethods || iface.HasStaticMethods)) + Fields.Add (new PeerMembersField (opt, iface.RawJniName, iface.Name, true)); + } + + void AddFields (InterfaceGen iface, CodeGenerationOptions opt, CodeGeneratorContext context) + { + // Interface fields are only supported with DIM + if (!opt.SupportInterfaceConstants) + return; + + var seen = new HashSet (); + var fields = iface.GetGeneratableFields (opt).ToList (); + + SourceWriterExtensions.AddFields (this, iface, fields, seen, opt, context); + } + + void AddProperties (InterfaceGen iface, CodeGenerationOptions opt) + { + foreach (var prop in iface.Properties.Where (p => !p.Getter.IsStatic && !p.Getter.IsInterfaceDefaultMethod)) + Properties.Add (new BoundInterfacePropertyDeclaration(iface, prop, iface.AssemblyQualifiedName + "Invoker", opt)); + + if (!opt.SupportDefaultInterfaceMethods) + return; + + var dim_properties = iface.Properties.Where (p => p.Getter.IsInterfaceDefaultMethod || p.Getter.IsStatic); + + foreach (var prop in dim_properties) { + if (prop.Getter.IsAbstract) { + var baseProp = iface.BaseSymbol != null ? iface.BaseSymbol.GetPropertyByName (prop.Name, true) : null; + if (baseProp != null) { + if (baseProp.Type != prop.Getter.Return) { + // This may not be required if we can change generic parameter support to return constrained type (not just J.L.Object). + //writer.WriteLine ("{0}// skipped generating property {1} because its Java method declaration is variant that we cannot represent in C#", indent, property.Name); + return; + } + } + + var bound_property = new BoundAbstractProperty (iface, prop, opt); + Properties.Add (bound_property); + + if (prop.Type.StartsWith ("Java.Lang.ICharSequence") && !bound_property.IsOverride) + Properties.Add (new BoundPropertyStringVariant (prop, opt)); + } else { + var bound_property = new BoundProperty (iface, prop, opt, true, false); + Properties.Add (bound_property); + + if (prop.Type.StartsWith ("Java.Lang.ICharSequence") && !bound_property.IsOverride) + Properties.Add (new BoundPropertyStringVariant (prop, opt)); + } + } + } + + void AddMethods (InterfaceGen iface, CodeGenerationOptions opt) + { + foreach (var m in iface.Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod)) { + if (m.Name == iface.Name || iface.ContainsProperty (m.Name, true)) + m.Name = "Invoke" + m.Name; + + Methods.Add (new BoundInterfaceMethodDeclaration(m, iface.AssemblyQualifiedName + "Invoker", opt)); + } + + if (!opt.SupportDefaultInterfaceMethods) + return; + + foreach (var method in iface.Methods.Where (m => m.IsInterfaceDefaultMethod || m.IsStatic)) { + if (!method.IsValid) + continue; + + Methods.Add (new BoundMethod(iface, method, opt, true)); + + var name_and_jnisig = method.JavaName + method.JniSignature.Replace ("java/lang/CharSequence", "java/lang/String"); + var gen_string_overload = !method.IsOverride && method.Parameters.HasCharSequence && !iface.ContainsMethod (name_and_jnisig); + + if (gen_string_overload || method.IsReturnCharSequence) + Methods.Add (new BoundMethodStringOverload (method, opt)); + + if (method.Asyncify) + Methods.Add (new MethodAsyncWrapper (method, opt)); + } + } + + void AddNestedTypes (InterfaceGen iface, CodeGenerationOptions opt, CodeGeneratorContext context, GenerationInfo genInfo) + { + // Generate nested types for supported nested types. This is a new addition in C#8. + // Prior to this, types nested in an interface had to be generated as sibling types. + // The "Unnest" property is used to support backwards compatibility with pre-C#8 bindings. + foreach (var nest in iface.NestedTypes.Where (t => !t.Unnest)) + NestedTypes.Add (SourceWriterExtensions.BuildManagedTypeModel (nest, opt, context, genInfo)); + } + + public override void Write (CodeWriter writer) + { + WritePreSiblingClasses (writer); + + if (!dont_generate) + base.Write (writer); + + WritePostSiblingClasses (writer); + } + + public void WritePreSiblingClasses (CodeWriter writer) + { + foreach (var sibling in pre_sibling_types) + sibling.Write (writer); + } + + public void WritePostSiblingClasses (CodeWriter writer) + { + foreach (var sibling in post_sibling_types) + sibling.Write (writer); + } + } +} diff --git a/tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs b/tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs new file mode 100644 index 000000000..98817006f --- /dev/null +++ b/tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundInterfaceMethodDeclaration : MethodWriter + { + readonly Method method; + readonly CodeGenerationOptions opt; + + public BoundInterfaceMethodDeclaration (Method method, string adapter, CodeGenerationOptions opt) + { + this.method = method; + this.opt = opt; + + Name = method.AdjustedName; + ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal)); + IsDeclaration = true; + + if (method.DeclaringType.IsGeneratable) + Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\""); + if (method.Deprecated != null) + Attributes.Add (new ObsoleteAttr (method.Deprecated.Replace ("\"", "\"\""))); + if (method.IsReturnEnumified) + Attributes.Add (new GeneratedEnumAttr (true)); + if (method.IsInterfaceDefaultMethod) + Attributes.Add (new CustomAttr ("[global::Java.Interop.JavaInterfaceDefaultMethod]")); + + Attributes.Add (new RegisterAttr (method.JavaName, method.JniSignature, method.ConnectorName + ":" + method.GetAdapterName (opt, adapter), additionalProperties: method.AdditionalAttributeString ())); + + SourceWriterExtensions.AddMethodCustomAttributes (Attributes, method); + this.AddMethodParameters (method.Parameters, opt); + } + } +} diff --git a/tools/generator/SourceWriters/BoundInterfacePropertyDeclaration.cs b/tools/generator/SourceWriters/BoundInterfacePropertyDeclaration.cs new file mode 100644 index 000000000..112e25653 --- /dev/null +++ b/tools/generator/SourceWriters/BoundInterfacePropertyDeclaration.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundInterfacePropertyDeclaration : PropertyWriter + { + public BoundInterfacePropertyDeclaration (GenBase gen, Property property, string adapter, CodeGenerationOptions opt) + { + Name = property.AdjustedName; + + PropertyType = new TypeReferenceWriter (opt.GetTypeReferenceName (property)); + IsAutoProperty = true; + + if (property.Getter != null) { + HasGet = true; + + if (gen.IsGeneratable) + GetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.MetadataXPathReference}/method[@name='{property.Getter.JavaName}'{property.Getter.Parameters.GetMethodXPathPredicate ()}]\""); + if (property.Getter.GenericArguments?.Any () == true) + GetterAttributes.Add (new CustomAttr (property.Getter.GenericArguments.ToGeneratedAttributeString ())); + + GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.ConnectorName + ":" + property.Getter.GetAdapterName (opt, adapter), additionalProperties: property.Getter.AdditionalAttributeString ())); + } + + if (property.Setter != null) { + HasSet = true; + + if (gen.IsGeneratable) + SetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.MetadataXPathReference}/method[@name='{property.Setter.JavaName}'{property.Setter.Parameters.GetMethodXPathPredicate ()}]\""); + if (property.Setter.GenericArguments?.Any () == true) + SetterAttributes.Add (new CustomAttr (property.Setter.GenericArguments.ToGeneratedAttributeString ())); + + SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.ConnectorName + ":" + property.Setter.GetAdapterName (opt, adapter), additionalProperties: property.Setter.AdditionalAttributeString ())); + } + } + } +} diff --git a/tools/generator/SourceWriters/BoundMethod.cs b/tools/generator/SourceWriters/BoundMethod.cs new file mode 100644 index 000000000..4a92b20fd --- /dev/null +++ b/tools/generator/SourceWriters/BoundMethod.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundMethod : MethodWriter + { + readonly MethodCallback callback; + + public BoundMethod (GenBase type, Method method, CodeGenerationOptions opt, bool generateCallbacks) + { + if (generateCallbacks && method.IsVirtual) + callback = new MethodCallback (type, method, opt, null, method.IsReturnCharSequence); + + Name = method.AdjustedName; + + IsStatic = method.IsStatic; + IsSealed = method.IsOverride && method.IsFinal; + IsUnsafe = true; + + SetVisibility (type is InterfaceGen && !IsStatic ? string.Empty : method.Visibility); + + // TODO: Clean up this logic + var is_explicit = opt.SupportDefaultInterfaceMethods && type is InterfaceGen && method.OverriddenInterfaceMethod != null; + var virt_ov = is_explicit ? string.Empty : method.IsOverride ? (opt.SupportDefaultInterfaceMethods && method.OverriddenInterfaceMethod != null ? " virtual" : " override") : method.IsVirtual ? " virtual" : string.Empty; + + IsVirtual = virt_ov.Trim () == "virtual"; + IsOverride = virt_ov.Trim () == "override"; + + // When using DIM, don't generate "virtual sealed" methods, remove both modifiers instead + if (opt.SupportDefaultInterfaceMethods && method.OverriddenInterfaceMethod != null && IsVirtual && IsSealed) { + IsVirtual = false; + IsSealed = false; + } + + if (is_explicit) + ExplicitInterfaceImplementation = GetDeclaringTypeOfExplicitInterfaceMethod (method.OverriddenInterfaceMethod); + + if ((IsVirtual || !IsOverride) && type.RequiresNew (method.AdjustedName, method)) + IsShadow = true; + + ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal)); + + if (method.DeclaringType.IsGeneratable) + Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\""); + + if (method.Deprecated.HasValue ()) + Attributes.Add (new ObsoleteAttr (method.Deprecated.Replace ("\"", "\"\""))); + + if (method.IsReturnEnumified) + Attributes.Add (new GeneratedEnumAttr (true)); + + Attributes.Add (new RegisterAttr (method.JavaName, method.JniSignature, method.IsVirtual ? method.GetConnectorNameFull (opt) : string.Empty, additionalProperties: method.AdditionalAttributeString ())); + + SourceWriterExtensions.AddMethodCustomAttributes (Attributes, method); + this.AddMethodParameters (method.Parameters, opt); + + SourceWriterExtensions.AddMethodBody (Body, method, opt); + } + + static string GetDeclaringTypeOfExplicitInterfaceMethod (Method method) + { + return method.OverriddenInterfaceMethod != null ? + GetDeclaringTypeOfExplicitInterfaceMethod (method.OverriddenInterfaceMethod) : + method.DeclaringType.FullName; + } + + public override void Write (CodeWriter writer) + { + callback?.Write (writer); + + base.Write (writer); + } + } +} diff --git a/tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs b/tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs new file mode 100644 index 000000000..03a060666 --- /dev/null +++ b/tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundMethodAbstractDeclaration : MethodWriter + { + readonly Method method; + readonly CodeGenerationOptions opt; + readonly MethodCallback method_callback; + + public BoundMethodAbstractDeclaration (GenBase gen, Method method, CodeGenerationOptions opt, GenBase impl) + { + this.method = method; + this.opt = opt; + + if (method.RetVal.IsGeneric && gen != null) { + Name = method.Name; + ExplicitInterfaceImplementation = opt.GetOutputName (gen.FullName); + + SourceWriterExtensions.AddMethodCustomAttributes (Attributes, method); + + Body.Add ("throw new NotImplementedException ();"); + + return; + } + + Name = method.AdjustedName; + + IsAbstract = true; + IsShadow = impl.RequiresNew (method.Name, method); + SetVisibility (method.Visibility); + ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal)); + + NewFirst = true; + + method_callback = new MethodCallback (impl, method, opt, null, method.IsReturnCharSequence); + + if (method.DeclaringType.IsGeneratable) + Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\""); + + Attributes.Add (new RegisterAttr (method.JavaName, method.JniSignature, method.ConnectorName, additionalProperties: method.AdditionalAttributeString ())); + + SourceWriterExtensions.AddMethodCustomAttributes (Attributes, method); + this.AddMethodParameters (method.Parameters, opt); + } + + public override void Write (CodeWriter writer) + { + // Need to write our property callback first + method_callback?.Write (writer); + + base.Write (writer); + } + } +} diff --git a/tools/generator/SourceWriters/BoundMethodExtensionStringOverload.cs b/tools/generator/SourceWriters/BoundMethodExtensionStringOverload.cs new file mode 100644 index 000000000..7e0907192 --- /dev/null +++ b/tools/generator/SourceWriters/BoundMethodExtensionStringOverload.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundMethodExtensionStringOverload : MethodWriter + { + readonly Method method; + readonly CodeGenerationOptions opt; + readonly string self_type; + + public BoundMethodExtensionStringOverload (Method method, CodeGenerationOptions opt, string selfType) + { + this.method = method; + this.opt = opt; + self_type = selfType; + + Name = method.Name; + IsStatic = true; + + SetVisibility (method.Visibility); + ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string")); + + if (method.Deprecated != null) + Attributes.Add (new ObsoleteAttr (method.Deprecated.Replace ("\"", "\"\"").Trim ())); + + Parameters.Add (new MethodParameterWriter ("self", new TypeReferenceWriter (selfType)) { IsExtension = true }); + this.AddMethodParametersStringOverloads (method.Parameters, opt); + } + + protected override void WriteBody (CodeWriter writer) + { + SourceWriterExtensions.WriteMethodStringOverloadBody (writer, method, opt, true); + } + } +} diff --git a/tools/generator/SourceWriters/BoundMethodStringOverload.cs b/tools/generator/SourceWriters/BoundMethodStringOverload.cs new file mode 100644 index 000000000..47dea8e1d --- /dev/null +++ b/tools/generator/SourceWriters/BoundMethodStringOverload.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundMethodStringOverload : MethodWriter + { + readonly Method method; + readonly CodeGenerationOptions opt; + + public BoundMethodStringOverload (Method method, CodeGenerationOptions opt) + { + this.method = method; + this.opt = opt; + + Name = method.Name; + IsStatic = method.IsStatic; + + SetVisibility (method.Visibility); + ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string")); + + if (method.Deprecated != null) + Attributes.Add (new ObsoleteAttr (method.Deprecated.Replace ("\"", "\"\"").Trim ())); + + this.AddMethodParametersStringOverloads (method.Parameters, opt); + } + + protected override void WriteBody (CodeWriter writer) + { + SourceWriterExtensions.WriteMethodStringOverloadBody (writer, method, opt, false); + } + } +} diff --git a/tools/generator/SourceWriters/BoundProperty.cs b/tools/generator/SourceWriters/BoundProperty.cs new file mode 100644 index 000000000..ce1896e79 --- /dev/null +++ b/tools/generator/SourceWriters/BoundProperty.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class BoundProperty : PropertyWriter + { + readonly MethodCallback getter_callback; + readonly MethodCallback setter_callback; + + public BoundProperty (GenBase gen, Property property, CodeGenerationOptions opt, bool withCallbacks = true, bool forceOverride = false) + { + Name = property.AdjustedName; + PropertyType = new TypeReferenceWriter (opt.GetTypeReferenceName (property.Getter.RetVal)); + + SetVisibility (gen is InterfaceGen ? string.Empty : property.Getter.IsAbstract && property.Getter.RetVal.IsGeneric ? "protected" : (property.Setter ?? property.Getter).Visibility); + + IsUnsafe = true; + HasGet = true; + + var is_virtual = property.Getter.IsVirtual && (property.Setter == null || property.Setter.IsVirtual); + + if (is_virtual && withCallbacks) { + IsVirtual = true; + IsShadow = gen.RequiresNew (property); + + getter_callback = new MethodCallback (gen, property.Getter, opt, property.AdjustedName, false); + + if (property.Setter != null) + setter_callback = new MethodCallback (gen, property.Setter, opt, property.AdjustedName, false); + } + + if (forceOverride || ShouldForceOverride (property)) { + IsVirtual = false; + IsOverride = true; + } + + if ((property.Getter ?? property.Setter).IsStatic) { + IsStatic = true; + IsVirtual = false; + IsOverride = false; + } else if (gen.BaseSymbol != null) { + // It should be using AdjustedName instead of Name, but ICharSequence ("Formatted") properties are not caught by this... + var base_prop = gen.BaseSymbol.GetPropertyByName (property.Name, true); + + // If the matching base getter we found is a DIM, we do not override it, it should stay virtual + if (base_prop != null && !base_prop.Getter.IsInterfaceDefaultMethod) { + IsVirtual = false; + IsOverride = true; + } + } + + // Unlike [Register], [Obsolete] cannot be put on property accessors, so we can apply them only under limited condition... + if (property.Getter.Deprecated != null && (property.Setter == null || property.Setter.Deprecated != null)) + Attributes.Add (new ObsoleteAttr (property.Getter.Deprecated.Replace ("\"", "\"\"").Trim () + (property.Setter != null && property.Setter.Deprecated != property.Getter.Deprecated ? " " + property.Setter.Deprecated.Replace ("\"", "\"\"").Trim () : null))); + + SourceWriterExtensions.AddMethodCustomAttributes (GetterAttributes, property.Getter); + + if (gen.IsGeneratable) + GetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.MetadataXPathReference}/method[@name='{property.Getter.JavaName}'{property.Getter.Parameters.GetMethodXPathPredicate ()}]\""); + + GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.IsVirtual ? property.Getter.GetConnectorNameFull (opt) : string.Empty, additionalProperties: property.Getter.AdditionalAttributeString ())); + + SourceWriterExtensions.AddMethodBody (GetBody, property.Getter, opt); + + if (property.Setter != null) { + HasSet = true; + + if (gen.IsGeneratable) + SetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.MetadataXPathReference}/method[@name='{property.Setter.JavaName}'{property.Setter.Parameters.GetMethodXPathPredicate ()}]\""); + + SourceWriterExtensions.AddMethodCustomAttributes (SetterAttributes, property.Setter); + SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.IsVirtual ? property.Setter.GetConnectorNameFull (opt) : string.Empty, additionalProperties: property.Setter.AdditionalAttributeString ())); + + + var pname = property.Setter.Parameters [0].Name; + property.Setter.Parameters [0].Name = "value"; + SourceWriterExtensions.AddMethodBody (SetBody, property.Setter, opt); + property.Setter.Parameters [0].Name = pname; + + } else if (property.GenerateDispatchingSetter) { + HasSet = true; + SetterComments.Add ("// This is a dispatching setter"); + SetBody.Add ($"Set{property.Name} (value);"); + } + } + + public override void Write (CodeWriter writer) + { + // Need to write our property callbacks first + getter_callback?.Write (writer); + setter_callback?.Write (writer); + + base.Write (writer); + } + + bool ShouldForceOverride (Property property) + { + // + // This is a special workaround for AdapterView inheritance. + // (How it is special? They have hand-written bindings AND brings generic + // version of AdapterView in the inheritance, also added by metadata!) + // + // They are on top of fragile hand-bound code, and when we are making changes + // in generator, they bite. Since we are not going to bring API breakage + // right now, we need special workarounds to get things working. + // + // So far, what we need here is to have AbsSpinner.Adapter compile. + // + // > platforms/*/src/generated/Android.Widget.AbsSpinner.cs(156,56): error CS0533: + // > `Android.Widget.AbsSpinner.Adapter' hides inherited abstract member + // > `Android.Widget.AdapterView.Adapter + // + // It is because the AdapterView.Adapter is hand-bound and cannot be + // detected by generator! + // + // So, we explicitly treat it as a special-case. + // + // Then, Spinner, ListView and GridView instantiate them, so they are also special cases. + // + if (property.Name == "Adapter" && + (property.Getter.DeclaringType.BaseGen.FullName == "Android.Widget.AdapterView" || + property.Getter.DeclaringType.BaseGen.BaseGen != null && property.Getter.DeclaringType.BaseGen.BaseGen.FullName == "Android.Widget.AdapterView")) + return true; + // ... and the above breaks generator tests... + if (property.Name == "Adapter" && + (property.Getter.DeclaringType.BaseGen.FullName == "Xamarin.Test.AdapterView" || + property.Getter.DeclaringType.BaseGen.BaseGen != null && property.Getter.DeclaringType.BaseGen.BaseGen.FullName == "Xamarin.Test.AdapterView")) + return true; + + return false; + } + } +} diff --git a/tools/generator/SourceWriters/BoundPropertyStringVariant.cs b/tools/generator/SourceWriters/BoundPropertyStringVariant.cs new file mode 100644 index 000000000..96a4899ad --- /dev/null +++ b/tools/generator/SourceWriters/BoundPropertyStringVariant.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + // When a property has a type of 'Java.Lang.ICharSequence' we usually generate + // an overload with type 'string' as a convenience for the user. + public class BoundPropertyStringVariant : PropertyWriter + { + public BoundPropertyStringVariant (Property property, CodeGenerationOptions opt) + { + var is_array = property.Getter.RetVal.IsArray; + + Name = property.Name; + + PropertyType = new TypeReferenceWriter ("string" + (is_array ? "[]" : string.Empty)) { + Nullable = opt.SupportNullableReferenceTypes + }; + + SetVisibility ((property.Setter ?? property.Getter).Visibility); + + HasGet = true; + + if (is_array) + GetBody.Add ($"return CharSequence.ArrayToStringArray ({property.AdjustedName});"); + else + GetBody.Add ($"return {property.AdjustedName} == null ? null : {property.AdjustedName}.ToString ();"); + + if (property.Setter is null) + return; + + HasSet = true; + + if (is_array) { + SetBody.Add ($"global::Java.Lang.ICharSequence[] jlsa = CharSequence.ArrayFromStringArray (value);"); + SetBody.Add ($"{property.AdjustedName} = jlsa;"); + SetBody.Add ($"foreach (var jls in jlsa) if (jls != null) jls.Dispose ();"); + } else { + SetBody.Add ($"var jls = value == null ? null : new global::Java.Lang.String (value);"); + SetBody.Add ($"{property.AdjustedName} = jls;"); + SetBody.Add ($"if (jls != null) jls.Dispose ();"); + } + } + } +} diff --git a/tools/generator/SourceWriters/CharSequenceEnumeratorMethod.cs b/tools/generator/SourceWriters/CharSequenceEnumeratorMethod.cs new file mode 100644 index 000000000..11554454b --- /dev/null +++ b/tools/generator/SourceWriters/CharSequenceEnumeratorMethod.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class CharSequenceEnumeratorMethod : MethodWriter + { + // System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () + // { + // return GetEnumerator (); + // } + public CharSequenceEnumeratorMethod () + { + Name = "System.Collections.IEnumerable.GetEnumerator"; + ReturnType = new TypeReferenceWriter ("System.Collections.IEnumerator"); + + Body.Add ("return GetEnumerator ();"); + } + } + + public class CharSequenceGenericEnumeratorMethod : MethodWriter + { + // public System.Collections.Generic.IEnumerator GetEnumerator () + // { + // for (int i = 0; i < Length(); i++) + // yield return CharAt (i); + // } + public CharSequenceGenericEnumeratorMethod () + { + Name = "GetEnumerator"; + ReturnType = new TypeReferenceWriter ("System.Collections.Generic.IEnumerator"); + + IsPublic = true; + + Body.Add ("for (int i = 0; i < Length (); i++)"); + Body.Add ("\tyield return CharAt (i);"); + } + } +} diff --git a/tools/generator/SourceWriters/ClassInvokerClass.cs b/tools/generator/SourceWriters/ClassInvokerClass.cs new file mode 100644 index 000000000..d20bd63c5 --- /dev/null +++ b/tools/generator/SourceWriters/ClassInvokerClass.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Schema; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class ClassInvokerClass : ClassWriter + { + public ClassInvokerClass (ClassGen klass, CodeGenerationOptions opt) + { + Name = $"{klass.Name}Invoker"; + + IsInternal = true; + IsPartial = true; + UsePriorityOrder = true; + + Inherits = klass.Name; + + foreach (var igen in klass.GetAllDerivedInterfaces ().Where (i => i.IsGeneric)) + Implements.Add (opt.GetOutputName (igen.FullName)); + + Attributes.Add (new RegisterAttr (klass.RawJniName, noAcw: true, additionalProperties: klass.AdditionalAttributeString ()) { UseGlobal = true }); + + var ctor = new ConstructorWriter { + Name = Name, + IsPublic = true, + BaseCall = "base (handle, transfer)" + }; + + ctor.Parameters.Add (new MethodParameterWriter ("handle", TypeReferenceWriter.IntPtr)); + ctor.Parameters.Add (new MethodParameterWriter ("transfer", new TypeReferenceWriter ("JniHandleOwnership"))); + + Constructors.Add (ctor); + + // ClassInvokerHandle + Fields.Add (new PeerMembersField (opt, klass.RawJniName, $"{klass.Name}Invoker", false)); + Properties.Add (new JniPeerMembersGetter ()); + Properties.Add (new ThresholdTypeGetter ()); + + AddMemberInvokers (klass, opt, new HashSet ()); + } + + void AddMemberInvokers (ClassGen klass, CodeGenerationOptions opt, HashSet members) + { + AddPropertyInvokers (klass, klass.Properties, members, opt); + AddMethodInvokers (klass, klass.Methods, members, null, opt); + + foreach (var iface in klass.GetAllDerivedInterfaces ()) { + AddPropertyInvokers (klass, iface.Properties.Where (p => !klass.ContainsProperty (p.Name, false, false)), members, opt); + AddMethodInvokers (klass, iface.Methods.Where (m => (opt.SupportDefaultInterfaceMethods || !m.IsInterfaceDefaultMethod) && !klass.ContainsMethod (m, false, false) && !klass.IsCovariantMethod (m) && !klass.ExplicitlyImplementedInterfaceMethods.Contains (m.GetSignature ())), members, iface, opt); + } + + if (klass.BaseGen != null && klass.BaseGen.FullName != "Java.Lang.Object") + AddMemberInvokers (klass.BaseGen, opt, members); + } + + void AddPropertyInvokers (ClassGen klass, IEnumerable properties, HashSet members, CodeGenerationOptions opt) + { + foreach (var prop in properties) { + if (members.Contains (prop.Name)) + continue; + + members.Add (prop.Name); + + if ((prop.Getter != null && !prop.Getter.IsAbstract) || (prop.Setter != null && !prop.Setter.IsAbstract)) + continue; + + var bound_property = new BoundProperty (klass, prop, opt, false, true); + Properties.Add (bound_property); + + if (prop.Type.StartsWith ("Java.Lang.ICharSequence") && !bound_property.IsOverride) + Properties.Add (new BoundPropertyStringVariant (prop, opt)); + } + } + + void AddMethodInvokers (ClassGen klass, IEnumerable methods, HashSet members, InterfaceGen gen, CodeGenerationOptions opt) + { + foreach (var m in methods) { + var sig = m.GetSignature (); + + if (members.Contains (sig)) + continue; + + members.Add (sig); + + if (!m.IsAbstract) + continue; + + if (klass.IsExplicitlyImplementedMethod (sig)) { + Methods.Add (new ExplicitInterfaceInvokerMethod (gen, m, opt)); + } else { + m.IsOverride = true; + Methods.Add (new BoundMethod (klass, m, opt, false)); + + if (m.Asyncify) + Methods.Add (new MethodAsyncWrapper (m, opt)); + + m.IsOverride = false; + } + } + } + } +} diff --git a/tools/generator/SourceWriters/ClassInvokers.cs b/tools/generator/SourceWriters/ClassInvokers.cs new file mode 100644 index 000000000..cd80699e8 --- /dev/null +++ b/tools/generator/SourceWriters/ClassInvokers.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class ClassHandleGetter : PropertyWriter + { + // internal static new IntPtr class_ref { + // get { return _members.JniPeerType.PeerReference.Handle; } + // } + public ClassHandleGetter (bool requireNew) + { + Name = "class_ref"; + PropertyType = TypeReferenceWriter.IntPtr; + + IsInternal = true; + IsStatic = true; + IsShadow = requireNew; + + HasGet = true; + GetBody.Add ("return _members.JniPeerType.PeerReference.Handle;"); + } + } + + public class InterfaceHandleGetter : PropertyWriter + { + // static IntPtr java_class_ref { + // get { return _members.JniPeerType.PeerReference.Handle; } + // } + public InterfaceHandleGetter () + { + Name = "java_class_ref"; + PropertyType = TypeReferenceWriter.IntPtr; + + IsStatic = true; + + HasGet = true; + GetBody.Add ("return _members.JniPeerType.PeerReference.Handle;"); + } + } + + public class JniPeerMembersGetter : PropertyWriter + { + // public override global::Java.Interop.JniPeerMembers JniPeerMembers { + // get { return _members; } + // } + public JniPeerMembersGetter () + { + Name = "JniPeerMembers"; + PropertyType = new TypeReferenceWriter ("global::Java.Interop.JniPeerMembers"); + + IsPublic = true; + IsOverride = true; + + HasGet = true; + GetBody.Add ("return _members;"); + } + } + + public class ClassThresholdClassGetter : PropertyWriter + { + // protected override IntPtr ThresholdClass { + // get { return _members.JniPeerType.PeerReference.Handle; } + // } + public ClassThresholdClassGetter () + { + Name = "ThresholdClass"; + PropertyType = TypeReferenceWriter.IntPtr; + + IsProtected = true; + IsOverride = true; + + HasGet = true; + GetBody.Add ("return _members.JniPeerType.PeerReference.Handle;"); + } + } + + public class InterfaceThresholdClassGetter : PropertyWriter + { + // protected override IntPtr ThresholdClass { + // get { return class_ref; } + // } + public InterfaceThresholdClassGetter () + { + Name = "ThresholdClass"; + PropertyType = TypeReferenceWriter.IntPtr; + + IsProtected = true; + IsOverride = true; + + HasGet = true; + GetBody.Add ("return class_ref;"); + } + } + + public class ThresholdTypeGetter : PropertyWriter + { + // protected override global::System.Type ThresholdType { + // get { return _members.ManagedPeerType; } + // } + public ThresholdTypeGetter () + { + Name = "ThresholdType"; + PropertyType = new TypeReferenceWriter ("global::System.Type"); + + IsProtected = true; + IsOverride = true; + + HasGet = true; + GetBody.Add ("return _members.ManagedPeerType;"); + } + } +} diff --git a/tools/generator/SourceWriters/CreateImplementorMethod.cs b/tools/generator/SourceWriters/CreateImplementorMethod.cs new file mode 100644 index 000000000..4d87793f7 --- /dev/null +++ b/tools/generator/SourceWriters/CreateImplementorMethod.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class CreateImplementorMethod : MethodWriter + { + public CreateImplementorMethod (InterfaceGen iface, CodeGenerationOptions opt) + { + Name = $"__Create{iface.Name}Implementor"; + + ReturnType = new TypeReferenceWriter ($"{opt.GetOutputName (iface.FullName)}Implementor"); + + Body.Add ($"return new {opt.GetOutputName (iface.FullName)}Implementor ({(iface.NeedsSender ? "this" : "")});"); + } + } +} diff --git a/tools/generator/SourceWriters/ExplicitInterfaceInvokerMethod.cs b/tools/generator/SourceWriters/ExplicitInterfaceInvokerMethod.cs new file mode 100644 index 000000000..d6db5d484 --- /dev/null +++ b/tools/generator/SourceWriters/ExplicitInterfaceInvokerMethod.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class ExplicitInterfaceInvokerMethod : MethodWriter + { + readonly Method method; + readonly CodeGenerationOptions opt; + + public ExplicitInterfaceInvokerMethod (GenBase iface, Method method, CodeGenerationOptions opt) + { + this.method = method; + this.opt = opt; + + Name = method.Name; + + IsUnsafe = true; + + ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal)); + ExplicitInterfaceImplementation = opt.GetOutputName (iface.FullName); + + this.AddMethodParameters (method.Parameters, opt); + SourceWriterExtensions.AddMethodBody (Body, method, opt); + } + } +} diff --git a/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs b/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs new file mode 100644 index 000000000..a03f5db6f --- /dev/null +++ b/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public static class SourceWriterExtensions + { + public static void AddField (TypeWriter tw, GenBase type, Field field, CodeGenerationOptions opt) + { + if (field.NeedsProperty) + tw.Properties.Add (new BoundFieldAsProperty (type, field, opt)); + else + tw.Fields.Add (new BoundField (type, field, opt)); + } + + public static bool AddFields (TypeWriter tw, GenBase gen, List fields, HashSet seen, CodeGenerationOptions opt, CodeGeneratorContext context) + { + var needsProperty = false; + + foreach (var f in fields) { + if (gen.ContainsName (f.Name)) { + Report.Warning (0, Report.WarningFieldNameCollision, "Skipping {0}.{1}, due to a duplicate field, method or nested type name. {2} (Java type: {3})", gen.FullName, f.Name, gen.HasNestedType (f.Name) ? "(Nested type)" : gen.ContainsProperty (f.Name, false) ? "(Property)" : "(Method)", gen.JavaName); + continue; + } + + if (seen != null && seen.Contains (f.Name)) { + Report.Warning (0, Report.WarningDuplicateField, "Skipping {0}.{1}, due to a duplicate field. (Field) (Java type: {2})", gen.FullName, f.Name, gen.JavaName); + continue; + } + + if (f.Validate (opt, gen.TypeParameters, context)) { + if (seen != null) + seen.Add (f.Name); + + needsProperty = needsProperty || f.NeedsProperty; + AddField (tw, gen, f, opt); + } + } + + return needsProperty; + } + + public static void AddInterfaceListenerEventsAndProperties (TypeWriter tw, InterfaceGen iface, ClassGen target, CodeGenerationOptions opt) + { + var methods = target.Methods.Concat (target.Properties.Where (p => p.Setter != null).Select (p => p.Setter)); + var props = new HashSet (); + var refs = new HashSet (); + var eventMethods = methods.Where (m => m.IsListenerConnector && m.EventName != string.Empty && m.ListenerType == iface).OrderBy (m => m.Parameters.Count).GroupBy (m => m.Name).Select (g => g.First ()).Distinct (); + + foreach (var method in eventMethods) { + var name = method.CalculateEventName (target.ContainsName); + + if (string.IsNullOrEmpty (name)) { + Report.Warning (0, Report.WarningInterfaceGen + 1, "empty event name in {0}.{1}.", iface.FullName, method.Name); + continue; + } + + if (opt.GetSafeIdentifier (name) != name) { + Report.Warning (0, Report.WarningInterfaceGen + 4, "event name for {0}.{1} is invalid. `eventName' or `argsType` can be used to assign a valid member name.", iface.FullName, method.Name); + continue; + } + + var prop = target.Properties.FirstOrDefault (p => p.Setter == method); + + if (prop != null) { + var setter = "__Set" + prop.Name; + props.Add (prop.Name); + refs.Add (setter); + + AddInterfaceListenerEventsAndProperties (tw, iface, target, name, setter, + string.Format ("__v => {0} = __v", prop.Name), + string.Format ("__v => {0} = null", prop.Name), opt); + } else { + refs.Add (method.Name); + string rm = null; + string remove; + + if (method.Name.StartsWith ("Set")) + remove = string.Format ("__v => {0} (null)", method.Name); + else if (method.Name.StartsWith ("Add") && + (rm = "Remove" + method.Name.Substring ("Add".Length)) != null && + methods.Where (m => m.Name == rm).Any ()) + remove = string.Format ("__v => {0} (__v)", rm); + else + remove = string.Format ("__v => {{throw new NotSupportedException (\"Cannot unregister from {0}.{1}\");}}", + iface.FullName, method.Name); + + AddInterfaceListenerEventsAndProperties (tw, iface, target, name, method.Name, + method.Name, + remove, opt); + } + } + + foreach (var r in refs) + tw.Fields.Add (new WeakImplementorField (r, opt)); + + tw.Methods.Add (new CreateImplementorMethod (iface, opt)); + } + + public static void AddInterfaceListenerEventsAndProperties (TypeWriter tw, InterfaceGen iface, ClassGen target, string name, string connector_fmt, string add, string remove, CodeGenerationOptions opt) + { + if (!iface.IsValid) + return; + + foreach (var method in iface.Methods) { + var nameSpec = iface.Methods.Count > 1 ? method.EventName ?? method.AdjustedName : string.Empty; + var nameUnique = string.IsNullOrEmpty (nameSpec) ? name : nameSpec; + + if (nameUnique.StartsWith ("On")) + nameUnique = nameUnique.Substring (2); + + if (target.ContainsName (nameUnique)) + nameUnique += "Event"; + + AddInterfaceListenerEventOrProperty (tw, iface, method, target, nameUnique, connector_fmt, add, remove, opt); + } + } + + public static void AddInterfaceListenerEventOrProperty (TypeWriter tw, InterfaceGen iface, Method method, ClassGen target, string name, string connector_fmt, string add, string remove, CodeGenerationOptions opt) + { + if (method.EventName == string.Empty) + return; + + var nameSpec = iface.Methods.Count > 1 ? method.AdjustedName : string.Empty; + var idx = iface.FullName.LastIndexOf ("."); + var start = iface.Name.StartsWith ("IOn") ? 3 : 1; + var full_delegate_name = iface.FullName.Substring (0, idx + 1) + iface.Name.Substring (start, iface.Name.Length - start - 8) + nameSpec; + + if (method.IsSimpleEventHandler) + full_delegate_name = "EventHandler"; + else if (method.RetVal.IsVoid || method.IsEventHandlerWithHandledProperty) + full_delegate_name = "EventHandler<" + iface.FullName.Substring (0, idx + 1) + iface.GetArgsName (method) + ">"; + else + full_delegate_name += "Handler"; + + if (method.RetVal.IsVoid || method.IsEventHandlerWithHandledProperty) { + if (opt.GetSafeIdentifier (name) != name) { + Report.Warning (0, Report.WarningInterfaceGen + 5, "event name for {0}.{1} is invalid. `eventName' or `argsType` can be used to assign a valid member name.", iface.FullName, name); + return; + } else { + var mt = target.Methods.Where (method => string.Compare (method.Name, connector_fmt, StringComparison.OrdinalIgnoreCase) == 0 && method.IsListenerConnector).FirstOrDefault (); + var hasHandlerArgument = mt != null && mt.IsListenerConnector && mt.Parameters.Count == 2 && mt.Parameters [1].Type == "Android.OS.Handler"; + + tw.Events.Add (new InterfaceListenerEvent (iface, name, nameSpec, full_delegate_name, connector_fmt, add, remove, hasHandlerArgument, opt)); + } + } else { + if (opt.GetSafeIdentifier (name) != name) { + Report.Warning (0, Report.WarningInterfaceGen + 6, "event property name for {0}.{1} is invalid. `eventName' or `argsType` can be used to assign a valid member name.", iface.FullName, name); + return; + } + + tw.Properties.Add (new InterfaceListenerPropertyImplementor (iface, name, opt)); + tw.Properties.Add (new InterfaceListenerProperty (iface, name, nameSpec, method.AdjustedName, full_delegate_name, opt)); + } + } + + public static void AddMethodCustomAttributes (List attributes, Method method) + { + if (method.GenericArguments != null && method.GenericArguments.Any ()) + attributes.Add (new CustomAttr (method.GenericArguments.ToGeneratedAttributeString ())); + if (method.CustomAttributes != null) + attributes.Add (new CustomAttr (method.CustomAttributes)); + if (method.Annotation != null) + attributes.Add (new CustomAttr (method.Annotation)); + } + + public static void AddMethodParameters (this ITakeParameters method, ParameterList parameters, CodeGenerationOptions opt) + { + foreach (var p in parameters) { + var para = new MethodParameterWriter (opt.GetSafeIdentifier (p.Name), new TypeReferenceWriter (opt.GetTypeReferenceName (p))); + + if (p.IsEnumified) + para.Attributes.Add (new GeneratedEnumAttr ()); + if (p.Annotation != null) + para.Attributes.Add (new CustomAttr (p.Annotation)); + + method.Parameters.Add (para); + } + } + + // This replaces any `Java.Lang.ICharSequence` parameters with `string`. + public static void AddMethodParametersStringOverloads (this MethodWriter method, ParameterList parameters, CodeGenerationOptions opt) + { + foreach (var p in parameters) { + var para = new MethodParameterWriter (opt.GetSafeIdentifier (p.Name), new TypeReferenceWriter (opt.GetTypeReferenceName (p).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string"))); + + if (p.IsEnumified) + para.Attributes.Add (new GeneratedEnumAttr ()); + if (p.Annotation != null) + para.Attributes.Add (new CustomAttr (p.Annotation)); + + method.Parameters.Add (para); + } + } + + public static string GetInvokeType (string type) + { + switch (type) { + case "Bool": return "Boolean"; + case "Byte": return "SByte"; + case "Int": return "Int32"; + case "Short": return "Int16"; + case "Long": return "Int64"; + case "Float": return "Single"; + case "UInt": return "Int32"; + case "UShort": return "Int16"; + case "ULong": return "Int64"; + case "UByte": return "SByte"; + default: return type; + } + } + + public static void AddMethodBody (List body, Method method, CodeGenerationOptions opt) + { + body.Add ($"const string __id = \"{method.JavaName}.{method.JniSignature}\";"); + + foreach (string prep in method.Parameters.GetCallPrep (opt)) + body.Add (prep); + + body.Add ("try {"); + + AddParameterListCallArgs (body, method.Parameters, opt, false); + + var invokeType = JavaInteropCodeGenerator.GetInvokeType (method.RetVal.CallMethodPrefix); + + var return_var = method.IsVoid ? string.Empty : "var __rm = "; + var method_type = method.IsStatic ? "StaticMethods" : "InstanceMethods"; + var virt_type = method switch + { + { IsStatic: true } => string.Empty, + { IsFinal: true } => "Nonvirtual", + { IsVirtual: true, IsAbstract: false } => "Virtual", + { IsInterfaceDefaultMethod: true } => "Virtual", + _ => "Abstract" + }; + var call_args = method.Parameters.GetCallArgs (opt, invoker: false); + var this_param = method.IsStatic ? $"__id{call_args}" : $"__id, this{call_args}"; + + // Example: var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, __args); + body.Add ($"\t{return_var}_members.{method_type}.Invoke{virt_type}{invokeType}Method ({this_param});"); + + if (!method.IsVoid) { + var r = invokeType == "Object" ? "__rm.Handle" : "__rm"; + body.Add ($"\treturn {method.RetVal.ReturnCast}{method.RetVal.FromNative (opt, r, true) + opt.GetNullForgiveness (method.RetVal)};"); + } + + body.Add ("} finally {"); + + foreach (string cleanup in method.Parameters.GetCallCleanup (opt)) + body.Add ("\t" + cleanup); + + body.Add ("}"); + } + + public static void AddParameterListCallArgs (List body, ParameterList parameters, CodeGenerationOptions opt, bool invoker) + { + if (parameters.Count == 0) + return; + + var JValue = invoker ? "JValue" : "JniArgumentValue"; + + body.Add ($"\t{JValue}* __args = stackalloc {JValue} [{parameters.Count}];"); + + for (var i = 0; i < parameters.Count; ++i) { + var p = parameters [i]; + body.Add ($"\t__args [{i}] = new {JValue} ({p.GetCall (opt)});"); + } + } + + public static void WriteMethodInvokerBody (CodeWriter writer, Method method, CodeGenerationOptions opt, string contextThis) + { + writer.WriteLine ($"if ({method.EscapedIdName} == IntPtr.Zero)"); + writer.WriteLine ($"\t{method.EscapedIdName} = JNIEnv.GetMethodID (class_ref, \"{method.JavaName}\", \"{method.JniSignature}\");"); + + foreach (var prep in method.Parameters.GetCallPrep (opt)) + writer.WriteLine (prep); + + WriteParameterListCallArgs (writer, method.Parameters, opt, invoker: true); + + var env_method = $"Call{method.RetVal.CallMethodPrefix}Method"; + var call = $"{method.RetVal.ReturnCast}JNIEnv.{env_method} ({contextThis}, {method.EscapedIdName}{method.Parameters.GetCallArgs (opt, invoker: true)})"; + + if (method.IsVoid) + writer.WriteLine (call + ";"); + else + writer.WriteLine ($"{(method.Parameters.HasCleanup ? "var __ret = " : "return ")}{method.RetVal.FromNative (opt, call, true) + opt.GetNullForgiveness (method.RetVal)};"); + + foreach (var cleanup in method.Parameters.GetCallCleanup (opt)) + writer.WriteLine (cleanup); + + if (!method.IsVoid && method.Parameters.HasCleanup) + writer.WriteLine ("return __ret;"); + } + + public static void WriteParameterListCallArgs (CodeWriter writer, ParameterList parameters, CodeGenerationOptions opt, bool invoker) + { + if (parameters.Count == 0) + return; + + var JValue = invoker ? "JValue" : "JniArgumentValue"; + + writer.WriteLine ($"{JValue}* __args = stackalloc {JValue} [{parameters.Count}];"); + + for (var i = 0; i < parameters.Count; ++i) { + var p = parameters [i]; + writer.WriteLine ($"__args [{i}] = new {JValue} ({p.GetCall (opt)});"); + } + } + + public static void WriteMethodStringOverloadBody (CodeWriter writer, Method method, CodeGenerationOptions opt, bool haveSelf) + { + var call = new StringBuilder (); + + foreach (var p in method.Parameters) { + var pname = p.Name; + if (p.Type == "Java.Lang.ICharSequence") { + pname = p.GetName ("jls_"); + writer.WriteLine ($"var {pname} = {p.Name} == null ? null : new global::Java.Lang.String ({p.Name});"); + } else if (p.Type == "Java.Lang.ICharSequence[]" || p.Type == "params Java.Lang.ICharSequence[]") { + pname = p.GetName ("jlca_"); + writer.WriteLine ($"var {pname} = CharSequence.ArrayFromStringArray({p.Name});"); + } + + if (call.Length > 0) + call.Append (", "); + + call.Append (pname + (p.Type == "Java.Lang.ICharSequence" ? opt.GetNullForgiveness (p) : string.Empty)); + } + + writer.WriteLine ($"{(method.RetVal.IsVoid ? string.Empty : opt.GetTypeReferenceName (method.RetVal) + " __result = ")}{(haveSelf ? "self." : "")}{method.AdjustedName} ({call.ToString ()});"); + + switch (method.RetVal.FullName) { + case "void": + break; + case "Java.Lang.ICharSequence[]": + writer.WriteLine ("var __rsval = CharSequence.ArrayToStringArray (__result);"); + break; + case "Java.Lang.ICharSequence": + writer.WriteLine ("var __rsval = __result?.ToString ();"); + break; + default: + writer.WriteLine ("var __rsval = __result;"); + break; + } + + foreach (var p in method.Parameters) { + if (p.Type == "Java.Lang.ICharSequence") + writer.WriteLine ($"{p.GetName ("jls_")}?.Dispose ();"); + else if (p.Type == "Java.Lang.ICharSequence[]") + writer.WriteLine ($"if ({p.GetName ("jlca_")} != null) foreach (var s in {p.GetName ("jlca_")}) s?.Dispose ();"); + } + + if (!method.RetVal.IsVoid) + writer.WriteLine ($"return __rsval{opt.GetNullForgiveness (method.RetVal)};"); + } + + public static TypeWriter BuildManagedTypeModel (GenBase gen, CodeGenerationOptions opt, CodeGeneratorContext context, GenerationInfo genInfo) + { + if (gen is ClassGen klass) + return new BoundClass (klass, opt, context, genInfo); + else if (gen is InterfaceGen iface) + return new BoundInterface (iface, opt, context, genInfo); + + throw new InvalidOperationException ("Unknown GenBase type"); + } + } +} diff --git a/tools/generator/SourceWriters/GenericExplicitInterfaceImplementationMethod.cs b/tools/generator/SourceWriters/GenericExplicitInterfaceImplementationMethod.cs new file mode 100644 index 000000000..869fb5020 --- /dev/null +++ b/tools/generator/SourceWriters/GenericExplicitInterfaceImplementationMethod.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + // This is supposed to generate instantiated generic method output, but I don't think it is done yet. + public class GenericExplicitInterfaceImplementationMethod : MethodWriter + { + readonly Method method; + readonly CodeGenerationOptions opt; + readonly GenericSymbol gen; + + public GenericExplicitInterfaceImplementationMethod (Method method, GenericSymbol gen, CodeGenerationOptions opt) + { + this.method = method; + this.opt = opt; + this.gen = gen; + + Name = method.Name; + + ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal)); + ExplicitInterfaceImplementation = opt.GetOutputName (gen.Gen.FullName); + + Comments.Add ($"// This method is explicitly implemented as a member of an instantiated {gen.FullName}"); + + SourceWriterExtensions.AddMethodCustomAttributes (Attributes, method); + this.AddMethodParameters (method.Parameters, opt); + } + + protected override void WriteBody (CodeWriter writer) + { + var mappings = new Dictionary (); + + for (var i = 0; i < gen.TypeParams.Length; i++) + mappings [gen.Gen.TypeParameters [i].Name] = gen.TypeParams [i].FullName; + + var call = method.Name + " (" + method.Parameters.GetGenericCall (opt, mappings) + ")"; + writer.WriteLine ($"{(method.IsVoid ? string.Empty : "return ")}{method.RetVal.GetGenericReturn (opt, call, mappings)};"); + } + } +} diff --git a/tools/generator/SourceWriters/GenericExplicitInterfaceImplementationProperty.cs b/tools/generator/SourceWriters/GenericExplicitInterfaceImplementationProperty.cs new file mode 100644 index 000000000..d34dd55dc --- /dev/null +++ b/tools/generator/SourceWriters/GenericExplicitInterfaceImplementationProperty.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class GenericExplicitInterfaceImplementationProperty : PropertyWriter + { + public GenericExplicitInterfaceImplementationProperty (Property property, GenericSymbol gen, string adapter, Dictionary mappings, CodeGenerationOptions opt) + { + Name = property.AdjustedName; + + PropertyType = new TypeReferenceWriter (opt.GetTypeReferenceName (property)); + ExplicitInterfaceImplementation = opt.GetOutputName (gen.Gen.FullName); + + Comments.Add ($"// This method is explicitly implemented as a member of an instantiated {gen.FullName}"); + + if (property.Getter != null) { + HasGet = true; + + if (gen.Gen.IsGeneratable) + GetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.Gen.MetadataXPathReference}/method[@name='{property.Getter.JavaName}'{property.Getter.Parameters.GetMethodXPathPredicate ()}]\""); + if (property.Getter.GenericArguments != null && property.Getter.GenericArguments.Any ()) + GetterAttributes.Add (new CustomAttr (property.Getter.GenericArguments.ToGeneratedAttributeString ())); + + GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.ConnectorName + ":" + property.Getter.GetAdapterName (opt, adapter), additionalProperties: property.Getter.AdditionalAttributeString ())); + + GetBody.Add ($"return {property.Name};"); + } + + if (property.Setter != null) { + HasSet = true; + + if (gen.Gen.IsGeneratable) + SetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.Gen.MetadataXPathReference}/method[@name='{property.Setter.JavaName}'{property.Setter.Parameters.GetMethodXPathPredicate ()}]\""); + if (property.Setter.GenericArguments != null && property.Setter.GenericArguments.Any ()) + SetterAttributes.Add (new CustomAttr (property.Setter.GenericArguments.ToGeneratedAttributeString ())); + + SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.ConnectorName + ":" + property.Setter.GetAdapterName (opt, adapter), additionalProperties: property.Setter.AdditionalAttributeString ())); + + // Temporarily rename the parameter to "value" + var pname = property.Setter.Parameters [0].Name; + property.Setter.Parameters [0].Name = "value"; + SetBody.Add ($"{property.Name} = {property.Setter.Parameters.GetGenericCall (opt, mappings)};"); + property.Setter.Parameters [0].Name = pname; + } + } + } +} diff --git a/tools/generator/SourceWriters/InterfaceConstsClass.cs b/tools/generator/SourceWriters/InterfaceConstsClass.cs new file mode 100644 index 000000000..1689f4e11 --- /dev/null +++ b/tools/generator/SourceWriters/InterfaceConstsClass.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class InterfaceConstsClass : ClassWriter + { + public InterfaceConstsClass (ClassGen klass, HashSet seen, CodeGenerationOptions opt, CodeGeneratorContext context) + { + Name = "InterfaceConsts"; + + IsPublic = true; + IsStatic = true; + + UsePriorityOrder = true; + + foreach (var iface in klass.GetAllImplementedInterfaces () + .Except (klass.BaseGen?.GetAllImplementedInterfaces () ?? new InterfaceGen [0]) + .Where (i => i.Fields.Count > 0)) { + + AddInlineComment ($"// The following are fields from: {iface.JavaName}"); + + SourceWriterExtensions.AddFields (this, iface, iface.Fields, seen, opt, context); + } + } + + public bool ShouldGenerate => Fields.Any () || Properties.Any (); + } +} diff --git a/tools/generator/SourceWriters/InterfaceEventArgsClass.cs b/tools/generator/SourceWriters/InterfaceEventArgsClass.cs new file mode 100644 index 000000000..b76df1d33 --- /dev/null +++ b/tools/generator/SourceWriters/InterfaceEventArgsClass.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class InterfaceEventArgsClass : ClassWriter + { + public InterfaceEventArgsClass (InterfaceGen iface, Method method, CodeGenerationOptions opt, CodeGeneratorContext context) + { + Name = iface.GetArgsName (method); + Inherits = "global::System.EventArgs"; + + IsPublic = true; + IsPartial = true; + + UsePriorityOrder = true; + + Comments.Add ($"// event args for {iface.JavaName}.{method.JavaName}"); + + AddConstructor (iface, method, opt); + + if (method.IsEventHandlerWithHandledProperty) + Properties.Add (new HandledProperty ()); + + AddProperties (method, opt); + } + + void AddConstructor (InterfaceGen iface, Method method, CodeGenerationOptions opt) + { + var ctor = new ConstructorWriter { + Name = iface.GetArgsName (method), + IsPublic = true + }; + + if (method.IsEventHandlerWithHandledProperty) { + ctor.Parameters.Add (new MethodParameterWriter ("handled", TypeReferenceWriter.Bool)); + ctor.Body.Add ("this.handled = handled;"); + } + + foreach (var p in method.Parameters) { + if (p.IsSender) + continue; + + ctor.Parameters.Add (new MethodParameterWriter (p.Name, new TypeReferenceWriter (opt.GetTypeReferenceName (p)))); + ctor.Body.Add ($"this.{opt.GetSafeIdentifier (p.Name)} = {opt.GetSafeIdentifier (p.Name)};"); + } + + Constructors.Add (ctor); + } + + void AddProperties (Method method, CodeGenerationOptions opt) + { + foreach (var p in method.Parameters) { + if (p.IsSender) + continue; + + Fields.Add (new FieldWriter { + Name = opt.GetSafeIdentifier (p.Name), + Type = new TypeReferenceWriter (opt.GetTypeReferenceName (p)) + }); + + var prop = new PropertyWriter { + Name = p.PropertyName, + PropertyType = new TypeReferenceWriter (opt.GetTypeReferenceName (p)), + IsPublic = true, + HasGet = true + }; + + prop.GetBody.Add ($"return {opt.GetSafeIdentifier (p.Name)};"); + + Properties.Add (prop); + } + } + } + + public class HandledProperty : PropertyWriter + { + public HandledProperty () + { + Name = "Handled"; + PropertyType = TypeReferenceWriter.Bool; + + IsPublic = true; + + HasGet = true; + GetBody.Add ("return handled;"); + + HasSet = true; + SetBody.Add ("handled = value;"); + } + + public override void Write (CodeWriter writer) + { + writer.Write ("bool handled;"); + + base.Write (writer); + } + } +} diff --git a/tools/generator/SourceWriters/InterfaceEventHandlerImplClass.cs b/tools/generator/SourceWriters/InterfaceEventHandlerImplClass.cs new file mode 100644 index 000000000..e988661c5 --- /dev/null +++ b/tools/generator/SourceWriters/InterfaceEventHandlerImplClass.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class InterfaceEventHandlerImplClass : ClassWriter + { + public InterfaceEventHandlerImplClass (InterfaceGen iface, CodeGenerationOptions opt, CodeGeneratorContext context) + { + var jni_class = "mono/" + iface.RawJniName.Replace ('$', '_') + "Implementor"; + + Name = iface.Name + "Implementor"; + Inherits = "global::Java.Lang.Object"; + Implements.Add (iface.Name); + + IsInternal = true; + IsSealed = true; + IsPartial = true; + + Attributes.Add (new RegisterAttr (jni_class, additionalProperties: iface.AdditionalAttributeString ()) { UseGlobal = true }); + + if (iface.NeedsSender) + Fields.Add (new FieldWriter { Name = "sender", Type = TypeReferenceWriter.Object }); + + AddConstructor (iface, jni_class, opt); + AddMethods (iface, opt); + } + + void AddConstructor (InterfaceGen iface, string jniClass, CodeGenerationOptions opt) + { + var ctor = new ConstructorWriter { + Name = iface.Name + "Implementor", + IsPublic = true + }; + + if (iface.NeedsSender) + ctor.Parameters.Add (new MethodParameterWriter ("sender", TypeReferenceWriter.Object)); + + ctor.BaseCall = $"base (global::Android.Runtime.JNIEnv.StartCreateInstance (\"{jniClass}\", \"()V\"), JniHandleOwnership.TransferLocalRef)"; + + ctor.Body.Add ($"global::Android.Runtime.JNIEnv.FinishCreateInstance ({iface.GetObjectHandleProperty ("this")}, \"()V\");"); + + if (iface.NeedsSender) + ctor.Body.Add ("this.sender = sender;"); + + Constructors.Add (ctor); + } + + void AddMethods (InterfaceGen iface, CodeGenerationOptions opt) + { + var handlers = new List (); + + foreach (var m in iface.Methods) + Methods.Add (new InterfaceEventHandlerImplMethod (iface, m, handlers, opt)); + + var is_empty_method = new MethodWriter { + Name = "__IsEmpty", + IsInternal = true, + IsStatic = true, + ReturnType = TypeReferenceWriter.Bool + }; + + is_empty_method.Parameters.Add (new MethodParameterWriter ("value", new TypeReferenceWriter (iface.Name + "Implementor"))); + + if (!iface.Methods.Any (m => m.EventName != string.Empty) || handlers.Count == 0) + is_empty_method.Body.Add ("return true;"); + else + is_empty_method.Body.Add ($"return {string.Join (" && ", handlers.Select (e => string.Format ("value.{0}Handler == null", e)))};"); + + Methods.Add (is_empty_method); + } + } + + public class InterfaceEventHandlerImplMethod : MethodWriter + { + readonly InterfaceGen iface; + readonly Method method; + readonly CodeGenerationOptions opt; + readonly bool needs_sender; + readonly string method_spec; + readonly string args_name; + + public InterfaceEventHandlerImplMethod (InterfaceGen iface, Method method, List handlers, CodeGenerationOptions opt) + { + this.iface = iface; + this.method = method; + this.opt = opt; + needs_sender = iface.NeedsSender; + + method_spec = iface.Methods.Count > 1 ? method.AdjustedName : string.Empty; + args_name = iface.GetArgsName (method); + + handlers.Add (method_spec); + + Name = method.Name; + ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal)); + + IsPublic = true; + + this.AddMethodParameters (method.Parameters, opt); + } + + protected override void WriteBody (CodeWriter writer) + { + // generate nothing + if (method.EventName == string.Empty) + return; + + if (method.IsVoid) { + writer.WriteLine ($"var __h = {method_spec}Handler;"); + writer.WriteLine ($"if (__h != null)"); + writer.WriteLine ($"\t__h ({(needs_sender ? "sender" : method.Parameters.SenderName)}, new {args_name} ({method.Parameters.CallDropSender}));"); + return; + } + + if (method.IsEventHandlerWithHandledProperty) { + writer.WriteLine ($"var __h = {method_spec}Handler;"); + writer.WriteLine ($"if (__h == null)"); + writer.WriteLine ($"\treturn {method.RetVal.DefaultValue};"); + + var call = method.Parameters.CallDropSender; + writer.WriteLine ($"var __e = new {args_name} (true{(call.Length != 0 ? ", " : "")}{call});"); + writer.WriteLine ($"__h ({(needs_sender ? "sender" : method.Parameters.SenderName)}, __e);"); + writer.WriteLine ($"return __e.Handled;"); + return; + } + + writer.WriteLine ($"var __h = {method_spec}Handler;"); + writer.WriteLine ($"return __h != null ? __h ({method.Parameters.GetCall (opt)}) : default ({opt.GetTypeReferenceName (method.RetVal)});"); + } + + public override void Write (CodeWriter writer) + { + if (method.EventName != string.Empty) { + writer.WriteLine ("#pragma warning disable 0649"); + writer.WriteLine ($"public {iface.GetEventDelegateName (method)}{opt.NullableOperator} {method_spec}Handler;"); + writer.WriteLine ("#pragma warning restore 0649"); + writer.WriteLine (); + } + + base.Write (writer); + } + } +} diff --git a/tools/generator/SourceWriters/InterfaceExtensionsClass.cs b/tools/generator/SourceWriters/InterfaceExtensionsClass.cs new file mode 100644 index 000000000..edf5ac0d4 --- /dev/null +++ b/tools/generator/SourceWriters/InterfaceExtensionsClass.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class InterfaceExtensionsClass : ClassWriter + { + public InterfaceExtensionsClass (InterfaceGen iface, string declaringTypeName, CodeGenerationOptions opt) + { + Name = $"{declaringTypeName}{iface.Name}Extensions"; + + IsPublic = true; + IsStatic = true; + IsPartial = true; + + foreach (var method in iface.Methods.Where (m => !m.IsStatic)) { + if (method.CanHaveStringOverload) { + // TODO: Don't allow obsolete here to match old generator. + // Probably should allow this in the future. + var deprecated = method.Deprecated; + method.Deprecated = null; + Methods.Add (new BoundMethodExtensionStringOverload (method, opt, iface.FullName)); + method.Deprecated = deprecated; + } + + if (method.Asyncify) + Methods.Add (new MethodExtensionAsyncWrapper (method, opt, iface.FullName)); + } + } + + public bool ShouldGenerate => Methods.Any (); + } +} diff --git a/tools/generator/SourceWriters/InterfaceInvokerClass.cs b/tools/generator/SourceWriters/InterfaceInvokerClass.cs new file mode 100644 index 000000000..4c6db530c --- /dev/null +++ b/tools/generator/SourceWriters/InterfaceInvokerClass.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Schema; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class InterfaceInvokerClass : ClassWriter + { + public InterfaceInvokerClass (InterfaceGen iface, CodeGenerationOptions opt, CodeGeneratorContext context) + { + Name = $"{iface.Name}Invoker"; + + IsInternal = true; + IsPartial = true; + UsePriorityOrder = true; + + Inherits = "global::Java.Lang.Object"; + Implements.Add (iface.Name); + + Attributes.Add (new RegisterAttr (iface.RawJniName, noAcw: true, additionalProperties: iface.AdditionalAttributeString ()) { UseGlobal = true }); + + Fields.Add (new PeerMembersField (opt, iface.RawJniName, $"{iface.Name}Invoker", false)); + + Properties.Add (new InterfaceHandleGetter ()); + Properties.Add (new JniPeerMembersGetter ()); + Properties.Add (new InterfaceThresholdClassGetter ()); + Properties.Add (new ThresholdTypeGetter ()); + + Fields.Add (new FieldWriter { Name = "class_ref", Type = TypeReferenceWriter.IntPtr, IsShadow = opt.BuildingCoreAssembly }); + + Methods.Add (new GetObjectMethod (iface, opt)); + Methods.Add (new ValidateMethod (iface)); + Methods.Add (new DisposeMethod ()); + + Constructors.Add (new InterfaceInvokerConstructor (iface, context)); + + AddMemberInvokers (iface, new HashSet (), opt, context); + } + + void AddMemberInvokers (InterfaceGen iface, HashSet members, CodeGenerationOptions opt, CodeGeneratorContext context) + { + AddPropertyInvokers (iface, iface.Properties.Where (p => !p.Getter.IsStatic && !p.Getter.IsInterfaceDefaultMethod), members, opt, context); + AddMethodInvokers (iface, iface.Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod), members, opt, context); + AddCharSequenceEnumerators (iface); + + foreach (var i in iface.GetAllDerivedInterfaces ()) { + AddPropertyInvokers (iface, i.Properties.Where (p => !p.Getter.IsStatic && !p.Getter.IsInterfaceDefaultMethod), members, opt, context); + AddMethodInvokers (iface, i.Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod && !iface.IsCovariantMethod (m) && !(i.FullName.StartsWith ("Java.Lang.ICharSequence") && m.Name.EndsWith ("Formatted"))), members, opt, context); + AddCharSequenceEnumerators (i); + } + } + + void AddCharSequenceEnumerators (InterfaceGen iface) + { + if (iface.FullName == "Java.Lang.ICharSequence") { + Methods.Add (new CharSequenceEnumeratorMethod ()); + Methods.Add (new CharSequenceGenericEnumeratorMethod ()); + } + } + + void AddPropertyInvokers (InterfaceGen iface, IEnumerable properties, HashSet members, CodeGenerationOptions opt, CodeGeneratorContext context) + { + foreach (var prop in properties) { + if (members.Contains (prop.Name)) + continue; + + members.Add (prop.Name); + + Properties.Add (new InterfaceInvokerProperty (iface, prop, opt, context)); + } + } + + void AddMethodInvokers (InterfaceGen iface, IEnumerable methods, HashSet members, CodeGenerationOptions opt, CodeGeneratorContext context) + { + foreach (var m in methods) { + var sig = m.GetSignature (); + + if (members.Contains (sig)) + continue; + + members.Add (sig); + + Methods.Add (new InterfaceInvokerMethod (iface, m, opt, context)); + } + } + } + + public class GetObjectMethod : MethodWriter + { + // public static IInterface? GetObject (IntPtr handle, JniHandleOwnership transfer) + // { + // return global::Java.Lang.Object.GetObject (handle, transfer); + // } + public GetObjectMethod (InterfaceGen iface, CodeGenerationOptions opt) + { + Name = "GetObject"; + + ReturnType = new TypeReferenceWriter (iface.Name) { Nullable = opt.SupportNullableReferenceTypes }; + + IsPublic = true; + IsStatic = true; + + Parameters.Add (new MethodParameterWriter ("handle", TypeReferenceWriter.IntPtr)); + Parameters.Add (new MethodParameterWriter ("transfer", new TypeReferenceWriter ("JniHandleOwnership"))); + + Body.Add ($"return global::Java.Lang.Object.GetObject<{iface.Name}> (handle, transfer);"); + } + } + + public class ValidateMethod : MethodWriter + { + // 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), \"{iface.JavaName}\")); + // + // return handle; + // } + public ValidateMethod (InterfaceGen iface) + { + Name = "Validate"; + + ReturnType = TypeReferenceWriter.IntPtr; + + IsStatic = true; + + Parameters.Add (new MethodParameterWriter ("handle", TypeReferenceWriter.IntPtr)); + + Body.Add ("if (!JNIEnv.IsInstanceOf (handle, java_class_ref))"); + Body.Add ($"\tthrow new InvalidCastException (string.Format (\"Unable to convert instance of type '{{0}}' to type '{{1}}'.\", JNIEnv.GetClassNameFromInstance (handle), \"{iface.JavaName}\"));"); + Body.Add ("return handle;"); + } + } + + public class DisposeMethod : MethodWriter + { + // 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 DisposeMethod () + { + Name = "Dispose"; + + IsProtected = true; + IsOverride = true; + + Parameters.Add (new MethodParameterWriter ("disposing", TypeReferenceWriter.Bool)); + ReturnType = TypeReferenceWriter.Void; + + Body.Add ("if (this.class_ref != IntPtr.Zero)"); + Body.Add ("\tJNIEnv.DeleteGlobalRef (this.class_ref);"); + Body.Add ("this.class_ref = IntPtr.Zero;"); + Body.Add ("base.Dispose (disposing);"); + } + } + + public class InterfaceInvokerConstructor : ConstructorWriter + { + // public IfaceInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer) + // { + // IntPtr local_ref = JNIEnv.GetObjectClass (this) + // this.class_ref = JNIEnv.NewGlobalRef (local_ref); + // JNIEnv.DeleteLocalRef (local_ref); + // } + public InterfaceInvokerConstructor (InterfaceGen iface, CodeGeneratorContext context) + { + Name = iface.Name + "Invoker"; + + IsPublic = true; + + Parameters.Add (new MethodParameterWriter ("handle", TypeReferenceWriter.IntPtr)); + Parameters.Add (new MethodParameterWriter ("transfer", new TypeReferenceWriter ("JniHandleOwnership"))); + + BaseCall = "base (Validate (handle), transfer)"; + + Body.Add ($"IntPtr local_ref = JNIEnv.GetObjectClass ({context.ContextType.GetObjectHandleProperty ("this")});"); + Body.Add ("this.class_ref = JNIEnv.NewGlobalRef (local_ref);"); + Body.Add ("JNIEnv.DeleteLocalRef (local_ref);"); + } + } +} diff --git a/tools/generator/SourceWriters/InterfaceInvokerMethod.cs b/tools/generator/SourceWriters/InterfaceInvokerMethod.cs new file mode 100644 index 000000000..153ef3736 --- /dev/null +++ b/tools/generator/SourceWriters/InterfaceInvokerMethod.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class InterfaceInvokerMethod : MethodWriter + { + readonly MethodCallback method_callback; + readonly Method method; + readonly CodeGenerationOptions opt; + readonly string context_this; + + public InterfaceInvokerMethod (InterfaceGen iface, Method method, CodeGenerationOptions opt, CodeGeneratorContext context) + { + this.method = method; + this.opt = opt; + + Name = method.AdjustedName; + ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal)); + + IsPublic = true; + IsUnsafe = true; + IsStatic = method.IsStatic; + + method_callback = new MethodCallback (iface, method, opt, null, method.IsReturnCharSequence); + context_this = context.ContextType.GetObjectHandleProperty ("this"); + + this.AddMethodParameters (method.Parameters, opt); + } + + public override void Write (CodeWriter writer) + { + method_callback?.Write (writer); + + writer.WriteLine ($"IntPtr {method.EscapedIdName};"); + + base.Write (writer); + } + + protected override void WriteBody (CodeWriter writer) + { + SourceWriterExtensions.WriteMethodInvokerBody (writer, method, opt, context_this); + } + } +} diff --git a/tools/generator/SourceWriters/InterfaceInvokerProperty.cs b/tools/generator/SourceWriters/InterfaceInvokerProperty.cs new file mode 100644 index 000000000..f21dc9ac9 --- /dev/null +++ b/tools/generator/SourceWriters/InterfaceInvokerProperty.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class InterfaceInvokerProperty : PropertyWriter + { + readonly MethodCallback getter_callback; + readonly MethodCallback setter_callback; + readonly Property property; + readonly CodeGenerationOptions opt; + readonly string context_this; + + public InterfaceInvokerProperty (InterfaceGen iface, Property property, CodeGenerationOptions opt, CodeGeneratorContext context) + { + this.property = property; + this.opt = opt; + + Name = property.AdjustedName; + PropertyType = new TypeReferenceWriter (opt.GetTypeReferenceName (property)); + + IsPublic = true; + IsUnsafe = true; + + HasGet = property.Getter != null; + + if (property.Getter != null) { + HasGet = true; + getter_callback = new MethodCallback (iface, property.Getter, opt, property.AdjustedName, false); + } + + if (property.Setter != null) { + HasSet = true; + setter_callback = new MethodCallback (iface, property.Setter, opt, property.AdjustedName, false); + } + + context_this = context.ContextType.GetObjectHandleProperty ("this"); + } + + public override void Write (CodeWriter writer) + { + getter_callback?.Write (writer); + setter_callback?.Write (writer); + + if (property.Getter != null) + writer.WriteLine ($"IntPtr {property.Getter.EscapedIdName};"); + + if (property.Setter != null) + writer.WriteLine ($"IntPtr {property.Setter.EscapedIdName};"); + + base.Write (writer); + } + + protected override void WriteGetterBody (CodeWriter writer) + { + SourceWriterExtensions.WriteMethodInvokerBody (writer, property.Getter, opt, context_this); + } + + protected override void WriteSetterBody (CodeWriter writer) + { + var pname = property.Setter.Parameters [0].Name; + property.Setter.Parameters [0].Name = "value"; + + SourceWriterExtensions.WriteMethodInvokerBody (writer, property.Setter, opt, context_this); + + property.Setter.Parameters [0].Name = pname; + } + } +} diff --git a/tools/generator/SourceWriters/InterfaceListenerEvent.cs b/tools/generator/SourceWriters/InterfaceListenerEvent.cs new file mode 100644 index 000000000..a1b1af076 --- /dev/null +++ b/tools/generator/SourceWriters/InterfaceListenerEvent.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class InterfaceListenerEvent : EventWriter + { + readonly InterfaceListenerEventHandlerHelper helper_method; + + public InterfaceListenerEvent (InterfaceGen iface, string name, string nameSpec, string fullDelegateName, string wrefSuffix, string add, string remove, bool hasHandlerArgument, CodeGenerationOptions opt) + { + Name = name; + EventType = new TypeReferenceWriter (opt.GetOutputName (fullDelegateName)); + + IsPublic = true; + + HasAdd = true; + + AddBody.Add ($"global::Java.Interop.EventHelper.AddEventHandler<{opt.GetOutputName (iface.FullName)}, {opt.GetOutputName (iface.FullName)}Implementor>("); + AddBody.Add ($"ref weak_implementor_{wrefSuffix},"); + AddBody.Add ($"__Create{iface.Name}Implementor,"); + AddBody.Add ($"{add + (hasHandlerArgument ? "_Event_With_Handler_Helper" : null)},"); + AddBody.Add ($"__h => __h.{nameSpec}Handler += value);"); + + HasRemove = true; + + RemoveBody.Add ($"global::Java.Interop.EventHelper.RemoveEventHandler<{opt.GetOutputName (iface.FullName)}, {opt.GetOutputName (iface.FullName)}Implementor>("); + RemoveBody.Add ($"ref weak_implementor_{wrefSuffix},"); + RemoveBody.Add ($"{opt.GetOutputName (iface.FullName)}Implementor.__IsEmpty,"); + RemoveBody.Add ($"{remove},"); + RemoveBody.Add ($"__h => __h.{nameSpec}Handler -= value);"); + + if (hasHandlerArgument) + helper_method = new InterfaceListenerEventHandlerHelper (iface, add, opt); + } + + public override void Write (CodeWriter writer) + { + base.Write (writer); + + helper_method?.Write (writer); + } + } + + public class InterfaceListenerEventHandlerHelper : MethodWriter + { + public InterfaceListenerEventHandlerHelper (InterfaceGen iface, string add, CodeGenerationOptions opt) + { + Name = add + "_Event_With_Handler_Helper"; + Parameters.Add (new MethodParameterWriter ("value", new TypeReferenceWriter (opt.GetOutputName (iface.FullName)))); + ReturnType = TypeReferenceWriter.Void; + + Body.Add ($"{add} (value, null);"); + } + } +} diff --git a/tools/generator/SourceWriters/InterfaceListenerProperty.cs b/tools/generator/SourceWriters/InterfaceListenerProperty.cs new file mode 100644 index 000000000..985328602 --- /dev/null +++ b/tools/generator/SourceWriters/InterfaceListenerProperty.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class InterfaceListenerProperty : PropertyWriter + { + public InterfaceListenerProperty (InterfaceGen iface, string name, string nameSpec, string methodName, string fullDelegateName, CodeGenerationOptions opt) + { + Name = name; + PropertyType = new TypeReferenceWriter (opt.GetOutputName (fullDelegateName)) { Nullable = opt.SupportNullableReferenceTypes }; + + IsPublic = true; + + HasGet = true; + + var handlerPrefix = iface.Methods.Count > 1 ? methodName : string.Empty; + + GetBody.Add ($"{opt.GetOutputName (iface.FullName)}Implementor{opt.NullableOperator} impl = Impl{name};"); + GetBody.Add ($"return impl == null ? null : impl.{handlerPrefix}Handler;"); + + HasSet = true; + + SetBody.Add ($"{opt.GetOutputName (iface.FullName)}Implementor{opt.NullableOperator} impl = Impl{name};"); + SetBody.Add ($"if (impl == null) {{"); + SetBody.Add ($"\timpl = new {opt.GetOutputName (iface.FullName)}Implementor ({(iface.NeedsSender ? "this" : string.Empty)});"); + SetBody.Add ($"\tImpl{name} = impl;"); + SetBody.Add ($"}} else"); + SetBody.Add ($"impl.{nameSpec}Handler = value;"); + } + } +} diff --git a/tools/generator/SourceWriters/InterfaceListenerPropertyImplementor.cs b/tools/generator/SourceWriters/InterfaceListenerPropertyImplementor.cs new file mode 100644 index 000000000..b74e87f77 --- /dev/null +++ b/tools/generator/SourceWriters/InterfaceListenerPropertyImplementor.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class InterfaceListenerPropertyImplementor : PropertyWriter + { + readonly string name; + readonly CodeGenerationOptions opt; + + public InterfaceListenerPropertyImplementor (InterfaceGen iface, string name, CodeGenerationOptions opt) + { + this.name = name; + this.opt = opt; + + Name = "Impl" + name; + PropertyType = new TypeReferenceWriter (opt.GetOutputName (iface.FullName) + "Implementor") { Nullable = opt.SupportNullableReferenceTypes }; + + HasGet = true; + + GetBody.Add ($"if (weak_implementor_{name} == null || !weak_implementor_{name}.IsAlive)"); + GetBody.Add ($"\treturn null;"); + GetBody.Add ($"return weak_implementor_{name}.Target as {opt.GetOutputName (iface.FullName)}Implementor;"); + + HasSet = true; + + SetBody.Add ($"weak_implementor_{name} = new WeakReference (value, true);"); + } + + public override void Write (CodeWriter writer) + { + // Write our backing field first + writer.WriteLine ($"WeakReference{opt.NullableOperator} weak_implementor_{name};"); + writer.WriteLine (); + + base.Write (writer); + } + } +} diff --git a/tools/generator/SourceWriters/InterfaceMemberAlternativeClass.cs b/tools/generator/SourceWriters/InterfaceMemberAlternativeClass.cs new file mode 100644 index 000000000..9567cee9b --- /dev/null +++ b/tools/generator/SourceWriters/InterfaceMemberAlternativeClass.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class InterfaceMemberAlternativeClass : ClassWriter + { + readonly List sibling_classes = new List (); + + // Historically .NET has not allowed interface implemented fields or constants, so we + // initially worked around that by moving them to an abstract class, generally + // IMyInterface -> MyInterfaceConsts + // This was later expanded to accomodate static interface methods, creating a more appropriately named class + // IMyInterface -> MyInterface + // In this case the XXXConsts class is [Obsolete]'d and simply inherits from the newer class + // in order to maintain backward compatibility. + // If we're creating a binding that supports DIM, we remove the XXXConsts class as they've been + // [Obsolete:iserror] for a long time, and we add [Obsolete] to the interface "class". + public InterfaceMemberAlternativeClass (InterfaceGen iface, CodeGenerationOptions opt, CodeGeneratorContext context) + { + var should_obsolete = opt.SupportInterfaceConstants && opt.SupportDefaultInterfaceMethods; + + Name = iface.HasManagedName + ? iface.Name.Substring (1) + "Consts" + : iface.Name.Substring (1); + + Inherits = "Java.Lang.Object"; + + IsPublic = true; + IsAbstract = true; + + UsePriorityOrder = true; + + Attributes.Add (new RegisterAttr (iface.RawJniName, noAcw: true, additionalProperties: iface.AdditionalAttributeString ()) { AcwLast = true }); + + if (should_obsolete) + Attributes.Add (new ObsoleteAttr ($"Use the '{iface.FullName}' type. This class will be removed in a future release.") { WriteGlobal = true, NoAtSign = true }); + + Constructors.Add (new ConstructorWriter { Name = Name, IsInternal = true }); + + var needs_class_ref = AddFields (iface, should_obsolete, opt, context); + AddMethods (iface, should_obsolete, opt); + + if (needs_class_ref || iface.Methods.Where (m => m.IsStatic).Any ()) + Fields.Add (new PeerMembersField (opt, iface.RawJniName, Name, false)); + + if (!iface.HasManagedName && !opt.SupportInterfaceConstants) + sibling_classes.Add (new InterfaceConstsForwardClass (iface)); + } + + void AddMethods (InterfaceGen iface, bool shouldObsolete, CodeGenerationOptions opt) + { + foreach (var method in iface.Methods.Where (m => m.IsStatic)) { + var original = method.Deprecated; + + if (shouldObsolete && string.IsNullOrWhiteSpace (method.Deprecated)) + method.Deprecated = $"Use '{iface.FullName}.{method.AdjustedName}'. This class will be removed in a future release."; + + Methods.Add (new BoundMethod (iface, method, opt, true)); + + var name_and_jnisig = method.JavaName + method.JniSignature.Replace ("java/lang/CharSequence", "java/lang/String"); + var gen_string_overload = !method.IsOverride && method.Parameters.HasCharSequence && !iface.ContainsMethod (name_and_jnisig); + + if (gen_string_overload || method.IsReturnCharSequence) + Methods.Add (new BoundMethodStringOverload (method, opt)); + + if (method.Asyncify) + Methods.Add (new MethodAsyncWrapper (method, opt)); + + method.Deprecated = original; + } + } + + bool AddFields (InterfaceGen iface, bool shouldObsolete, CodeGenerationOptions opt, CodeGeneratorContext context) + { + var seen = new HashSet (); + + var original_fields = DeprecateFields (iface, shouldObsolete); + var needs_class_ref = AddInterfaceFields (iface, iface.Fields, seen, opt, context); + RestoreDeprecatedFields (original_fields); + + foreach (var i in iface.GetAllImplementedInterfaces ().OfType ()) { + AddInlineComment ($"// The following are fields from: {i.JavaName}"); + + original_fields = DeprecateFields (i, shouldObsolete); + needs_class_ref = AddInterfaceFields (i, i.Fields, seen, opt, context) || needs_class_ref; + RestoreDeprecatedFields (original_fields); + } + + return needs_class_ref; + } + + bool AddInterfaceFields (InterfaceGen iface, List fields, HashSet seen, CodeGenerationOptions opt, CodeGeneratorContext context) + { + var needs_property = false; + + foreach (var f in fields) { + if (iface.ContainsName (f.Name)) { + Report.Warning (0, Report.WarningFieldNameCollision, "Skipping {0}.{1}, due to a duplicate field, method or nested type name. {2} (Java type: {3})", iface.FullName, f.Name, iface.HasNestedType (f.Name) ? "(Nested type)" : iface.ContainsProperty (f.Name, false) ? "(Property)" : "(Method)", iface.JavaName); + continue; + } + + if (seen.Contains (f.Name)) { + Report.Warning (0, Report.WarningDuplicateField, "Skipping {0}.{1}, due to a duplicate field. (Field) (Java type: {2})", iface.FullName, f.Name, iface.JavaName); + continue; + } + + if (f.Validate (opt, iface.TypeParameters, context)) { + seen.Add (f.Name); + needs_property = needs_property || f.NeedsProperty; + + if (f.NeedsProperty) + Properties.Add (new BoundFieldAsProperty (iface, f, opt)); + else + Fields.Add (new BoundField (iface, f, opt)); + } + } + + return needs_property; + } + + List<(Field field, bool deprecated, string comment)> DeprecateFields (InterfaceGen iface, bool shouldObsolete) + { + var original_fields = iface.Fields.Select (f => (f, f.IsDeprecated, f.DeprecatedComment)).ToList (); + + if (!shouldObsolete) + return original_fields; + + foreach (var f in iface.Fields) { + // Only use this derprecation if it's not already deprecated for another reason + if (!f.IsDeprecated) { + f.IsDeprecated = true; + f.DeprecatedComment = $"Use '{iface.FullName}.{f.Name}'. This class will be removed in a future release."; ; + } + } + + return original_fields; + } + + void RestoreDeprecatedFields (List<(Field field, bool deprecated, string comment)> fields) + { + foreach (var (field, deprecated, comment) in fields) { + field.IsDeprecated = deprecated; + field.DeprecatedComment = comment; + } + } + + public override void Write (CodeWriter writer) + { + base.Write (writer); + + WriteSiblingClasses (writer); + } + + public void WriteSiblingClasses (CodeWriter writer) + { + foreach (var sibling in sibling_classes) + sibling.Write (writer); + } + } + + public class InterfaceConstsForwardClass : ClassWriter + { + public InterfaceConstsForwardClass (InterfaceGen iface) + { + Name = iface.Name.Substring (1) + "Consts"; + Inherits = iface.Name.Substring (1); + + IsPublic = true; + IsAbstract = true; + + Attributes.Add (new RegisterAttr (iface.RawJniName, noAcw: true, additionalProperties: iface.AdditionalAttributeString ())); + Attributes.Add (new ObsoleteAttr ($"Use the '{iface.Name.Substring (1)}' type. This type will be removed in a future release.", true) { NoAtSign = true, WriteGlobal = true }); + + Constructors.Add (new ConstructorWriter { + Name = Name, + IsPrivate = true + }); + } + } +} diff --git a/tools/generator/SourceWriters/JavaLangObjectConstructor.cs b/tools/generator/SourceWriters/JavaLangObjectConstructor.cs new file mode 100644 index 000000000..a7cac874f --- /dev/null +++ b/tools/generator/SourceWriters/JavaLangObjectConstructor.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.Android.Binder; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class JavaLangObjectConstructor : ConstructorWriter + { + public JavaLangObjectConstructor (ClassGen klass) + { + Name = klass.Name; + + if (klass.IsFinal) + IsInternal = true; + else + IsProtected = true; + + Parameters.Add (new MethodParameterWriter ("javaReference", TypeReferenceWriter.IntPtr)); + Parameters.Add (new MethodParameterWriter ("transfer", new TypeReferenceWriter ("JniHandleOwnership"))); + + BaseCall = "base (javaReference, transfer)"; + } + } +} diff --git a/tools/generator/SourceWriters/MethodAsyncWrapper.cs b/tools/generator/SourceWriters/MethodAsyncWrapper.cs new file mode 100644 index 000000000..89b55ede5 --- /dev/null +++ b/tools/generator/SourceWriters/MethodAsyncWrapper.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class MethodAsyncWrapper : MethodWriter + { + readonly Method method; + readonly CodeGenerationOptions opt; + + public MethodAsyncWrapper (Method method, CodeGenerationOptions opt) + { + this.method = method; + this.opt = opt; + + Name = method.AdjustedName + "Async"; + IsStatic = method.IsStatic; + + SetVisibility (method.Visibility); + + ReturnType = new TypeReferenceWriter ("global::System.Threading.Tasks.Task"); + + if (!method.IsVoid) + ReturnType.Name += "<" + opt.GetTypeReferenceName (method.RetVal) + ">"; + + Body.Add ($"return global::System.Threading.Tasks.Task.Run (() => {method.AdjustedName} ({method.Parameters.GetCall (opt)}));"); + + this.AddMethodParameters (method.Parameters, opt); + } + } +} diff --git a/tools/generator/SourceWriters/MethodCallback.cs b/tools/generator/SourceWriters/MethodCallback.cs new file mode 100644 index 000000000..4d6531c2b --- /dev/null +++ b/tools/generator/SourceWriters/MethodCallback.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.Android.Binder; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class MethodCallback : MethodWriter + { + readonly GenBase type; + readonly Method method; + readonly string property_name; + readonly bool is_formatted; + readonly CodeGenerationOptions opt; + + readonly FieldWriter delegate_field; + readonly MethodWriter delegate_getter; + + // static sbyte n_ByteValueExact (IntPtr jnienv, IntPtr native__this) + // { + // var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + // return __this.ByteValueExact (); + // } + public MethodCallback (GenBase type, Method method, CodeGenerationOptions options, string propertyName, bool isFormatted) + { + this.type = type; + this.method = method; + + property_name = propertyName; + is_formatted = isFormatted; + opt = options; + + delegate_field = new MethodCallbackDelegateField (method, options); + delegate_getter = new GetDelegateHandlerMethod (method, options); + + Name = "n_" + method.Name + method.IDSignature; + ReturnType = new TypeReferenceWriter (method.RetVal.NativeType); + + IsStatic = true; + IsPrivate = method.IsInterfaceDefaultMethod; + + if (!string.IsNullOrWhiteSpace (method.Deprecated)) + Attributes.Add (new ObsoleteAttr ()); + + Parameters.Add (new MethodParameterWriter ("jnienv", TypeReferenceWriter.IntPtr)); + Parameters.Add (new MethodParameterWriter ("native__this", TypeReferenceWriter.IntPtr)); + + foreach (var p in method.Parameters) + Parameters.Add (new MethodParameterWriter (options.GetSafeIdentifier (p.UnsafeNativeName), new TypeReferenceWriter (p.NativeType))); + } + + protected override void WriteBody (CodeWriter writer) + { + writer.WriteLine ($"var __this = global::Java.Lang.Object.GetObject<{opt.GetOutputName (type.FullName)}> (jnienv, native__this, JniHandleOwnership.DoNotTransfer){opt.NullForgivingOperator};"); + + foreach (var s in method.Parameters.GetCallbackPrep (opt)) + writer.WriteLine (s); + + if (string.IsNullOrEmpty (property_name)) { + var call = "__this." + method.Name + (is_formatted ? "Formatted" : string.Empty) + " (" + method.Parameters.GetCall (opt) + ")"; + if (method.IsVoid) + writer.WriteLine (call + ";"); + else + writer.WriteLine ("{0} {1};", method.Parameters.HasCleanup ? method.RetVal.NativeType + " __ret =" : "return", method.RetVal.ToNative (opt, call)); + } else { + if (method.IsVoid) + writer.WriteLine ("__this.{0} = {1};", property_name, method.Parameters.GetCall (opt)); + else + writer.WriteLine ("{0} {1};", method.Parameters.HasCleanup ? method.RetVal.NativeType + " __ret =" : "return", method.RetVal.ToNative (opt, "__this." + property_name)); + } + + foreach (var cleanup in method.Parameters.GetCallbackCleanup (opt)) + writer.WriteLine (cleanup); + + if (!method.IsVoid && method.Parameters.HasCleanup) + writer.WriteLine ("return __ret;"); + } + + public override void Write (CodeWriter writer) + { + delegate_field.Write (writer); + writer.WriteLine ("#pragma warning disable 0169"); + + delegate_getter.Write (writer); + base.Write (writer); + + writer.WriteLine ("#pragma warning restore 0169"); + } + } + + public class MethodCallbackDelegateField : FieldWriter + { + // static Delegate cb_byteValueExact; + public MethodCallbackDelegateField (Method method, CodeGenerationOptions options) + { + Name = method.EscapedCallbackName; + Type = TypeReferenceWriter.Delegate; + + IsStatic = true; + IsPrivate = method.IsInterfaceDefaultMethod; + + if (!string.IsNullOrEmpty (options.NullableOperator)) + Type.Nullable = true; + } + } + + public class GetDelegateHandlerMethod : MethodWriter + { + readonly Method method; + readonly CodeGenerationOptions opt; + + // static Delegate GetByteValueExactHandler () + // { + // if (cb_byteValueExact == null) + // cb_byteValueExact = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_B) n_ByteValueExact); + // return cb_byteValueExact; + // } + public GetDelegateHandlerMethod (Method method, CodeGenerationOptions opt) + { + this.method = method; + this.opt = opt; + + Name = method.ConnectorName; + ReturnType = TypeReferenceWriter.Delegate; + + IsStatic = true; + IsPrivate = method.IsInterfaceDefaultMethod; + + if (!string.IsNullOrWhiteSpace (method.Deprecated)) + Attributes.Add (new ObsoleteAttr ()); + } + + protected override void WriteBody (CodeWriter writer) + { + var callback_name = method.EscapedCallbackName; + + writer.WriteLine ($"if ({callback_name} == null)"); + writer.WriteLine ($"\t{callback_name} = JNINativeWrapper.CreateDelegate (({method.GetDelegateType (opt)}) n_{method.Name + method.IDSignature});"); + writer.WriteLine ($"return {callback_name};"); + } + } +} diff --git a/tools/generator/SourceWriters/MethodExplicitInterfaceImplementation.cs b/tools/generator/SourceWriters/MethodExplicitInterfaceImplementation.cs new file mode 100644 index 000000000..7b8fe53b0 --- /dev/null +++ b/tools/generator/SourceWriters/MethodExplicitInterfaceImplementation.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class MethodExplicitInterfaceImplementation : MethodWriter + { + readonly Method method; + readonly CodeGenerationOptions opt; + + public MethodExplicitInterfaceImplementation (GenBase iface, Method method, CodeGenerationOptions opt) + { + this.method = method; + this.opt = opt; + + Name = method.Name; + + ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal)); + ExplicitInterfaceImplementation = opt.GetOutputName (iface.FullName); + + SourceWriterExtensions.AddMethodCustomAttributes (Attributes, method); + + this.AddMethodParameters (method.Parameters, opt); + } + + protected override void WriteBody (CodeWriter writer) + { + writer.WriteLine ($"return {Name} ({method.Parameters.GetCall (opt)})"); + } + } +} diff --git a/tools/generator/SourceWriters/MethodExtensionAsyncWrapper.cs b/tools/generator/SourceWriters/MethodExtensionAsyncWrapper.cs new file mode 100644 index 000000000..d62fa6cc6 --- /dev/null +++ b/tools/generator/SourceWriters/MethodExtensionAsyncWrapper.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class MethodExtensionAsyncWrapper : MethodWriter + { + public MethodExtensionAsyncWrapper (Method method, CodeGenerationOptions opt, string selfType) + { + Name = method.AdjustedName + "Async"; + IsStatic = true; + + SetVisibility (method.Visibility); + + ReturnType = new TypeReferenceWriter ("global::System.Threading.Tasks.Task"); + + if (!method.IsVoid) + ReturnType.Name += "<" + opt.GetTypeReferenceName (method.RetVal) + ">"; + + Body.Add ($"return global::System.Threading.Tasks.Task.Run (() => self.{method.AdjustedName} ({method.Parameters.GetCall (opt)}));"); + + Parameters.Add (new MethodParameterWriter ("self", new TypeReferenceWriter (selfType)) { IsExtension = true }); + + this.AddMethodParameters (method.Parameters, opt); + } + } +} diff --git a/tools/generator/SourceWriters/PeerMembersField.cs b/tools/generator/SourceWriters/PeerMembersField.cs new file mode 100644 index 000000000..6e19bb152 --- /dev/null +++ b/tools/generator/SourceWriters/PeerMembersField.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class PeerMembersField : FieldWriter + { + // static readonly JniPeerMembers _members = new XAPeerMembers ("android/provider/ContactsContract$AggregationExceptions", typeof (AggregationExceptions)); + public PeerMembersField (CodeGenerationOptions opt, string rawJniType, string declaringType, bool isInterface) + { + Name = "_members"; + Type = new TypeReferenceWriter ("JniPeerMembers"); + + IsPrivate = isInterface; + IsStatic = true; + IsReadonly = true; + + var peer = opt.CodeGenerationTarget == Xamarin.Android.Binder.CodeGenerationTarget.XAJavaInterop1 ? "XAPeerMembers" : "JniPeerMembers"; + + Value = $"new {peer} (\"{rawJniType}\", typeof ({declaringType}){(isInterface ? ", isInterface: true" : string.Empty)})"; + } + } +} diff --git a/tools/generator/SourceWriters/WeakImplementorField.cs b/tools/generator/SourceWriters/WeakImplementorField.cs new file mode 100644 index 000000000..e46e00c10 --- /dev/null +++ b/tools/generator/SourceWriters/WeakImplementorField.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MonoDroid.Generation; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class WeakImplementorField : FieldWriter + { + public WeakImplementorField (string name, CodeGenerationOptions opt) + { + Name = "weak_implementor_" + name; + Type = new TypeReferenceWriter (opt.GetOutputName ("WeakReference")) { Nullable = opt.SupportNullableReferenceTypes }; + } + } +} diff --git a/tools/generator/generator.csproj b/tools/generator/generator.csproj index 12065dc94..9a7fa7cb3 100644 --- a/tools/generator/generator.csproj +++ b/tools/generator/generator.csproj @@ -47,6 +47,7 @@ + diff --git a/tools/generator/generator.slnf b/tools/generator/generator.slnf index 187ea69f5..2c3036c98 100644 --- a/tools/generator/generator.slnf +++ b/tools/generator/generator.slnf @@ -8,7 +8,9 @@ "src\\Java.Interop.Tools.Diagnostics\\Java.Interop.Tools.Diagnostics.csproj", "src\\Xamarin.Android.Tools.AnnotationSupport\\Xamarin.Android.Tools.AnnotationSupport.csproj", "src\\Xamarin.Android.Tools.ApiXmlAdjuster\\Xamarin.Android.Tools.ApiXmlAdjuster.csproj", + "src\\Xamarin.SourceWriter\\Xamarin.SourceWriter.csproj", "tests\\generator-Tests\\generator-Tests.csproj", + "tests\\Xamarin.SourceWriter-Tests\\Xamarin.SourceWriter-Tests.csproj", "tools\\generator\\generator.csproj", ] }