diff --git a/Java.Interop.sln b/Java.Interop.sln index 22fc435d7..1a63f1196 100644 --- a/Java.Interop.sln +++ b/Java.Interop.sln @@ -17,7 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{4C173212-3 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop", "src\Java.Interop\Java.Interop.csproj", "{94BD81F7-B06F-4295-9636-F8A3B6BDC762}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "java-interop", "src\java-interop\java-interop.csproj", "{BB0AB9F7-0979-41A7-B7A9-877260655F94}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "java-interop", "src\java-interop\java-interop.csproj", "{BB0AB9F7-0979-41A7-B7A9-877260655F94}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Dynamic", "src\Java.Interop.Dynamic\Java.Interop.Dynamic.csproj", "{AD4468F8-8883-434B-9D4C-E1801BB3B52A}" EndProject @@ -99,6 +99,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.GenericMarshal EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.BootstrapTasks", "src\Java.Interop.BootstrapTasks\Java.Interop.BootstrapTasks.csproj", "{3E8E5C8C-59A6-4A9A-B55D-46AB14431B2A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "binding-integration-Tests", "tests\binding-integrated-Tests\binding-integration-Tests.csproj", "{D5CE4B09-C1D3-4647-B78B-6D2E89FE883E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -349,6 +351,14 @@ Global {3E8E5C8C-59A6-4A9A-B55D-46AB14431B2A}.XAIntegrationDebug|Any CPU.Build.0 = Debug|Any CPU {3E8E5C8C-59A6-4A9A-B55D-46AB14431B2A}.XAIntegrationRelease|Any CPU.ActiveCfg = Release|Any CPU {3E8E5C8C-59A6-4A9A-B55D-46AB14431B2A}.XAIntegrationRelease|Any CPU.Build.0 = Release|Any CPU + {D5CE4B09-C1D3-4647-B78B-6D2E89FE883E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5CE4B09-C1D3-4647-B78B-6D2E89FE883E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5CE4B09-C1D3-4647-B78B-6D2E89FE883E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5CE4B09-C1D3-4647-B78B-6D2E89FE883E}.Release|Any CPU.Build.0 = Release|Any CPU + {D5CE4B09-C1D3-4647-B78B-6D2E89FE883E}.XAIntegrationDebug|Any CPU.ActiveCfg = Debug|Any CPU + {D5CE4B09-C1D3-4647-B78B-6D2E89FE883E}.XAIntegrationDebug|Any CPU.Build.0 = Debug|Any CPU + {D5CE4B09-C1D3-4647-B78B-6D2E89FE883E}.XAIntegrationRelease|Any CPU.ActiveCfg = Release|Any CPU + {D5CE4B09-C1D3-4647-B78B-6D2E89FE883E}.XAIntegrationRelease|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {0C001D50-4176-45AE-BDC8-BA626508B0CC} = {C8F58966-94BF-407F-914A-8654F8B8AE3B} @@ -392,5 +402,6 @@ Global {C0487169-8F81-497F-919E-EB42B1D0243F} = {C8F58966-94BF-407F-914A-8654F8B8AE3B} {D1243BAB-23CA-4566-A2A3-3ADA2C2DC3AF} = {4C173212-371D-45D8-BA83-9226194F48DC} {3E8E5C8C-59A6-4A9A-B55D-46AB14431B2A} = {172B608B-E6F3-41CC-9949-203A76BA247C} + {D5CE4B09-C1D3-4647-B78B-6D2E89FE883E} = {271C9F30-F679-4793-942B-0D9527CB3E2F} EndGlobalSection EndGlobal diff --git a/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt b/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt index 5e5ae6994..0cfd669b5 100644 --- a/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt +++ b/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt @@ -44,7 +44,7 @@ namespace Java.Interop { JniPeerMembers.AssertSelf (self); var declaringType = DeclaringType; - if (Members.ShouldUseVirtualDispatch (self, declaringType)) { + if (Members.UsesVirtualDispatch (self, declaringType)) { var m = GetMethodInfo (encodedMember); <#= returnType.ReturnType != "void" ? "return " : "" #>JniEnvironment.InstanceMethods.Call<#= returnType.JniCallType #>Method (self.PeerReference, m, parameters); <#= returnType.ReturnType == "void" ? "return;" : "" #> diff --git a/tests/binding-integrated-Tests/BindingBuilder.cs b/tests/binding-integrated-Tests/BindingBuilder.cs new file mode 100644 index 000000000..a22232075 --- /dev/null +++ b/tests/binding-integrated-Tests/BindingBuilder.cs @@ -0,0 +1,292 @@ +using NUnit.Framework; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Xamarin.Android.Tools.Bytecode; +using Xamarin.Android.Tools.ApiXmlAdjuster; + +namespace BindingIntegrationTests +{ + public class BindingBuilder + { + // This is required to get generator.exe deployed next to this assembly + // so that the default GeneratorPath can probe that it is right there. + static readonly Type dummy = typeof (MonoDroid.Generation.GenBase); + + [Flags] + public enum Steps + { + Javac = 1, + Jar = 2, + ClassParse = 4, + ApiXmlAdjuster = 8, + Generator = 16, + Csc = 32, + All = Javac | Jar | ClassParse | ApiXmlAdjuster | Generator | Csc + } + + public const string JavaSourcesSubDir = "java-sources"; + public const string ClassesSubDir = "classes"; + public const string ClassParseSubDir = "class-parse-xml"; + public const string ApiXmlSubDir = "api-xml"; + public const string MetadataXmlSubDir = "metadata"; + public const string CSharpSourcesSubDir = "csharp"; + + public Steps ProcessSteps { get; set; } = Steps.All; + + // entire work (intermediate output) directory + public string IntermediateOutputPathRelative { get; set; } = "intermediate-output"; + + // Used to resolve javac and rt.jar + public string JdkPath { get; set; } + + public string GeneratorPath { get; set; } = Path.Combine (Path.GetDirectoryName (new Uri (typeof (BindingBuilder).Assembly.CodeBase).LocalPath), "generator.exe"); + + static string ProbeJavaHome () + { + var env = Environment.GetEnvironmentVariable ("JAVA_HOME"); + if (!string.IsNullOrEmpty (env)) + return env; + return "/usr/lib/jvm/java-8-openjdk-amd64/"; + } + + public static BindingBuilder CreateBestBetDefault (BindingProject project) + { + return new BindingBuilder (project) { JdkPath = ProbeJavaHome () }; + } + + public BindingBuilder (BindingProject project) + { + this.project = project; + } + + readonly BindingProject project; + + public string IntermediateOutputPathAbsolute => Path.Combine (Path.GetDirectoryName (new Uri (GetType ().Assembly.CodeBase).LocalPath), IntermediateOutputPathRelative, project.Id); + + public void Clean () + { + if (Directory.Exists (IntermediateOutputPathAbsolute)) + Directory.Delete (IntermediateOutputPathAbsolute, true); + } + + public void Build () + { + Javac (); + Jar (); + ClassParse (); + AdjustApiXml (); + GenerateBindingSources (); + CompileBindings (); + } + + void EnsureDirectory (string dir) + { + var parent = Path.GetDirectoryName (dir); + if (parent != Path.GetPathRoot (parent)) + EnsureDirectory (parent); + if (!Directory.Exists (dir)) + Directory.CreateDirectory (dir); + } + + void Javac () + { + if ((ProcessSteps & Steps.Javac) == 0) + return; + + if (JdkPath == null) + throw new InvalidOperationException ("JdkPath is not set."); + + var objDir = IntermediateOutputPathAbsolute; + EnsureDirectory (objDir); + + string sourcesSaved = Path.Combine (objDir, JavaSourcesSubDir); + EnsureDirectory (sourcesSaved); + foreach (var item in project.JavaSourceStrings) + File.WriteAllText (Path.Combine (sourcesSaved, item.FileName), item.Content); + var sourceFiles = project.JavaSourceFiles.Concat (project.JavaSourceStrings.Select (i => Path.Combine (sourcesSaved, i.FileName))); + + if (project.CompiledClassesDirectory == null) + project.CompiledClassesDirectory = Path.Combine (objDir, ClassesSubDir); + EnsureDirectory (project.CompiledClassesDirectory); + + var psi = new ProcessStartInfo () { + UseShellExecute = false, + FileName = JdkPath != null ? Path.Combine (JdkPath, "bin", "javac") : "javac", + Arguments = $"{project.JavacOptions} -d \"{project.CompiledClassesDirectory}\" {string.Join (" ", sourceFiles.Select (s => '"' + s + '"'))}", + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + if (project.CustomRuntimeJar != null) + psi.Arguments += $" -bootclasspath {project.CustomRuntimeJar} -classpath {project.CustomRuntimeJar}"; + + project.JavacExecutionOutput = $"Execute javac as: {psi.FileName} {psi.Arguments}\n"; + + var proc = new Process () { StartInfo = psi }; + proc.OutputDataReceived += (sender, e) => project.JavacExecutionOutput += e.Data; + proc.ErrorDataReceived += (sender, e) => project.JavacExecutionOutput += e.Data; + proc.Start (); + proc.BeginOutputReadLine (); + proc.BeginErrorReadLine (); + proc.WaitForExit (); + if (proc.ExitCode != 0) + throw new Exception ("Javac failed: " + project.JavacExecutionOutput); + } + + void Jar () + { + if ((ProcessSteps & Steps.Jar) == 0) + return; + + if (JdkPath == null) + throw new InvalidOperationException ("JdkPath is not set."); + + var objDir = IntermediateOutputPathAbsolute; + if (project.CompiledClassesDirectory == null) + project.CompiledClassesDirectory = Path.Combine (objDir, ClassesSubDir); + if (project.CompiledJarFile == null) + project.CompiledJarFile = Path.Combine (project.CompiledClassesDirectory, project.Id + ".jar"); + + var psi = new ProcessStartInfo () { + UseShellExecute = false, + FileName = JdkPath != null ? Path.Combine (JdkPath, "bin", "jar") : "jar", + Arguments = $"cvf \"{project.CompiledJarFile}\" -C \"{project.CompiledClassesDirectory}\" .", + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + + project.JarExecutionOutput = $"Execute jar as: {psi.FileName} {psi.Arguments}\n"; + + var proc = new Process () { StartInfo = psi }; + proc.OutputDataReceived += (sender, e) => project.JarExecutionOutput += e.Data; + proc.ErrorDataReceived += (sender, e) => project.JarExecutionOutput += e.Data; + proc.Start (); + proc.BeginOutputReadLine (); + proc.BeginErrorReadLine (); + proc.WaitForExit (); + if (proc.ExitCode != 0) + throw new Exception ("Jar failed: " + project.JarExecutionOutput); + } + + void ClassParse () + { + if ((ProcessSteps & Steps.ClassParse) == 0) + return; + + if (project.CompiledJarFile == null && !project.InputJarFiles.Any ()) + throw new InvalidOperationException ("Input Jar files are not set either at CompiledJarFile or InputJarFiles."); + + var objDir = IntermediateOutputPathAbsolute; + EnsureDirectory (objDir); + var cpDir = Path.Combine (objDir, ClassParseSubDir); + EnsureDirectory (cpDir); + if (project.GeneratedClassParseXmlFile == null) + project.GeneratedClassParseXmlFile = Path.Combine (cpDir, project.Id + ".class-parse"); + + // FIXME: logging + var cp = new ClassPath (); + cp.Load (project.CompiledJarFile); + foreach (var jar in project.InputJarFiles) + cp.Load (jar); + cp.SaveXmlDescription (project.GeneratedClassParseXmlFile); + } + + void AdjustApiXml () + { + if ((ProcessSteps & Steps.ApiXmlAdjuster) == 0) + return; + + var objDir = IntermediateOutputPathAbsolute; + var cpDir = Path.Combine (objDir, ClassParseSubDir); + EnsureDirectory (cpDir); + if (project.GeneratedApiXmlFile == null) + project.GeneratedApiXmlFile = Path.Combine (objDir, "api.xml"); + if (!File.Exists (project.GeneratedClassParseXmlFile) && !project.ClassParseXmlFiles.Any () && !project.ClassParseXmlStrings.Any ()) + throw new InvalidOperationException ("Input class-parse file does not exist."); + + foreach (var cpSource in project.ClassParseXmlStrings) + File.WriteAllText (Path.Combine (cpDir, cpSource.FileName), cpSource.Content); + var cpFiles = project.ClassParseXmlFiles.Concat (project.ClassParseXmlStrings.Select (i => Path.Combine (cpDir, i.FileName))); + + // FIXME: this does not scale for parallel tasking. + var writer = new StringWriter (); + Xamarin.Android.Tools.ApiXmlAdjuster.Log.DefaultWriter = writer; + + var api = new JavaApi (); + if (File.Exists (project.GeneratedClassParseXmlFile)) + api.Load (project.GeneratedClassParseXmlFile); + foreach (var apixml in cpFiles) + api.Load (apixml); + api.Resolve (); + api.CreateGenericInheritanceMapping (); + api.MarkOverrides (); + api.FindDefects (); + api.Save (project.GeneratedApiXmlFile); + project.ApiXmlAdjusterExecutionOutput = writer.ToString (); + if (project.ApiXmlAdjusterExecutionOutput != string.Empty) + throw new Exception ("api-xml-adjuster failed: " + project.ApiXmlAdjusterExecutionOutput); + } + + void GenerateBindingSources () + { + if ((ProcessSteps & Steps.Generator) == 0) + return; + + if (GeneratorPath == null) + throw new InvalidOperationException ("GeneratorPath is not set."); + + var objDir = IntermediateOutputPathAbsolute; + EnsureDirectory (objDir); + + if (project.GeneratedApiXmlFile == null) + project.GeneratedApiXmlFile = Path.Combine (objDir, "api.xml"); + if (!File.Exists (project.GeneratedApiXmlFile) && !project.ApiXmlFiles.Any () && !project.ApiXmlStrings.Any ()) + throw new InvalidOperationException ("Input api xml file does not exist."); + if (project.GeneratedCSharpSourceDirectory == null) + project.GeneratedCSharpSourceDirectory = Path.Combine (objDir, CSharpSourcesSubDir); + + string apiXmlSaved = Path.Combine (objDir, ApiXmlSubDir); + EnsureDirectory (apiXmlSaved); + foreach (var item in project.ApiXmlStrings) + File.WriteAllText (Path.Combine (apiXmlSaved, item.FileName), item.Content); + var apiXmlFiles = project.ApiXmlFiles.Concat (project.ApiXmlStrings.Select (i => Path.Combine (apiXmlSaved, i.FileName))); + + string metadataSaved = Path.Combine (objDir, MetadataXmlSubDir); + EnsureDirectory (metadataSaved); + foreach (var item in project.MetadataXmlStrings) + File.WriteAllText (Path.Combine (metadataSaved, item.FileName), item.Content); + var metadataFiles = project.MetadataXmlFiles.Concat (project.MetadataXmlStrings.Select (i => Path.Combine (metadataSaved, i.FileName))); + + var psi = new ProcessStartInfo () { + UseShellExecute = false, + FileName = GeneratorPath, + Arguments = $"{project.GeneratorOptions}" + + $" {(File.Exists (project.GeneratedApiXmlFile) ? project.GeneratedApiXmlFile : string.Empty)}" + + $" {string.Join (" ", apiXmlFiles.Select (s => '"' + s + '"'))}" + + $" {string.Join (" ", metadataFiles.Select (s => " --fixup=\"" + s + '"'))}" + + $" {string.Join (" ", project.ReferenceDlls.Select (s => " -r \"" + s + '"'))}" + + $" --csdir=\"{project.GeneratedCSharpSourceDirectory}\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + + project.GeneratorExecutionOutput = $"Execute generator as: {psi.FileName} {psi.Arguments}\n"; + + var proc = new Process () { StartInfo = psi }; + proc.OutputDataReceived += (sender, e) => project.GeneratorExecutionOutput += e.Data; + proc.ErrorDataReceived += (sender, e) => project.GeneratorExecutionOutput += e.Data; + proc.Start (); + proc.BeginOutputReadLine (); + proc.BeginErrorReadLine (); + proc.WaitForExit (); + if (proc.ExitCode != 0) + throw new Exception ("generator failed: " + project.GeneratorExecutionOutput); + } + + void CompileBindings () + { + } + } +} + diff --git a/tests/binding-integrated-Tests/BindingProject.cs b/tests/binding-integrated-Tests/BindingProject.cs new file mode 100644 index 000000000..206823d6f --- /dev/null +++ b/tests/binding-integrated-Tests/BindingProject.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; + +namespace BindingIntegrationTests +{ + public class SourceFile + { + public string FileName { get; set; } + public string Content { get; set; } + } + + public class BindingProject + { + public string Id { get; set; } + + public string JavacOptions { get; set; } = "-g"; + public string GeneratorOptions { get; internal set; } + + public IList JavaSourceFiles { get; set; } = new List (); + public IList JavaSourceStrings { get; set; } = new List (); + // rt.jar, android.jar etc. + // If it is specified, then javac will run with -bootclasspath and -cp + public string CustomRuntimeJar { get; set; } + + public IList InputJarFiles { get; set; } = new List (); + public IList ReferenceJarFiles { get; set; } = new List (); + + public IList ClassParseXmlFiles { get; set; } = new List (); + public IList ClassParseXmlStrings { get; set; } = new List (); + + public IList ApiXmlFiles { get; set; } = new List (); + public IList ApiXmlStrings { get; set; } = new List (); + public IList MetadataXmlFiles { get; set; } = new List (); + public IList MetadataXmlStrings { get; set; } = new List (); + public IList ReferenceDlls { get; set; } = new List (); + + public IList CSharpSourceFiles { get; set; } = new List (); + public IList CSharpSourceStrings { get; set; } = new List (); + + public string JavacExecutionOutput { get; internal set; } + public string JarExecutionOutput { get; internal set; } + public string ClassParseExecutionOutput { get; internal set; } + public string ApiXmlAdjusterExecutionOutput { get; internal set; } + public string GeneratorExecutionOutput { get; internal set; } + public string CscExecutionOutput { get; internal set; } + + // Java classes directory generated by javac (Javac() method) + public string CompiledClassesDirectory { get; set; } + + // Java libraries archived by jar + public string CompiledJarFile { get; internal set; } + + // API XML description generated by class-parse + public string GeneratedClassParseXmlFile { get; internal set; } + + // API XML description generated by api-xml-adjuster + public string GeneratedApiXmlFile { get; internal set; } + + public string GeneratedCSharpSourceDirectory { get; internal set; } + // C# sources generated by generator + public IList GeneratedCSharpSourceFiles { get; internal set; } + + // DLL built by C# compiler + public string GeneratedDllFile { get; internal set; } + } +} \ No newline at end of file diff --git a/tests/binding-integrated-Tests/README.md b/tests/binding-integrated-Tests/README.md new file mode 100644 index 000000000..5fbb17cf8 --- /dev/null +++ b/tests/binding-integrated-Tests/README.md @@ -0,0 +1,26 @@ +This directory contains a set of integration tests for binding infrastructure. + +The test fixtures make it possible to build binding Java library, export +API, generate binding sources, compile them and even execute it on JVM. + +### Input items + +- Java sources +- Metadata fixup +- input Java libraries +- reference Java libraries +- reference managed libraries + +### Tools + +- class-parse +- api-xml-adjuster +- generator +- csc + +### Build artifacts to test + +- jars +- dlls +- generated C# sources + diff --git a/tests/binding-integrated-Tests/SampleTest.cs b/tests/binding-integrated-Tests/SampleTest.cs new file mode 100644 index 000000000..60cc51404 --- /dev/null +++ b/tests/binding-integrated-Tests/SampleTest.cs @@ -0,0 +1,134 @@ +using System; +using System.IO; +using System.Xml.Linq; +using System.Xml.XPath; +using NUnit.Framework; + +namespace BindingIntegrationTests +{ + // Whenever we write complicated test foundation, we should ensure that it actually works. + public class SampleTest + { + [Test] + public void YouNeedJdkPath () + { + Assert.Throws (() => { + new BindingBuilder (new BindingProject ()).Build (); + }); + } + + [Test] + public void VerifyJavac () + { + // You don't even have to create a set of project files. They can be created on the fly. + var project = new BindingProject { Id = nameof (VerifyJavac) }; + string fooJavaFileName = "Foo.java"; + string fooJavaContent = "public class Foo {}"; + project.JavaSourceStrings.Add (new SourceFile { FileName = fooJavaFileName, Content = fooJavaContent }); + + // Set up builder. Hopefully you don't have to provide JDK path, it will be probed. + var builder = BindingBuilder.CreateBestBetDefault (project); + builder.ProcessSteps = BindingBuilder.Steps.Javac; + builder.Clean (); + builder.Build (); + + var savedFooJavaFile = Path.Combine (builder.IntermediateOutputPathAbsolute, BindingBuilder.JavaSourcesSubDir, fooJavaFileName); + Assert.IsTrue (File.Exists (savedFooJavaFile), "Java source not saved"); + Assert.AreEqual (fooJavaContent, File.ReadAllText (savedFooJavaFile), "Saved java content mismatch."); + var classesDir = Path.Combine (builder.IntermediateOutputPathAbsolute, BindingBuilder.ClassesSubDir); + Assert.AreEqual (classesDir, project.CompiledClassesDirectory, "classes directory mismatch."); + var fooClassFile = Path.Combine (classesDir, "Foo.class"); + Assert.IsTrue (File.Exists (fooClassFile), "Compiled Foo.class not found"); + } + + [Test] + public void VerifyJavacWithRtJar () + { + var project = new BindingProject { Id = nameof (VerifyJavacWithRtJar) }; + string fooJavaFileName = "Foo.java"; + string fooJavaContent = "public class Foo {}"; + project.JavaSourceStrings.Add (new SourceFile { FileName = fooJavaFileName, Content = fooJavaContent }); + + var builder = BindingBuilder.CreateBestBetDefault (project); + project.CustomRuntimeJar = Path.Combine (builder.JdkPath, "jre", "lib", "rt.jar"); + Assert.IsTrue (File.Exists (project.CustomRuntimeJar), "rt.jar exists"); + builder.ProcessSteps = BindingBuilder.Steps.Javac; + builder.Clean (); + builder.Build (); + } + + [Test] + public void VerifyJar () + { + var project = new BindingProject { Id = nameof (VerifyJar) }; + project.JavaSourceStrings.Add (new SourceFile { FileName = "Foo.java", Content = "public class Foo {}" }); + project.JavaSourceStrings.Add (new SourceFile { FileName = "Bar.java", Content = "public class Bar {}" }); + + var builder = BindingBuilder.CreateBestBetDefault (project); + builder.ProcessSteps = BindingBuilder.Steps.Javac | BindingBuilder.Steps.Jar; + builder.Clean (); + builder.Build (); + + var jar = Path.Combine (builder.IntermediateOutputPathAbsolute, BindingBuilder.ClassesSubDir, project.Id + ".jar"); + Assert.AreEqual (jar, project.CompiledJarFile, "jar file path mismatch."); + Assert.IsTrue (File.Exists (project.CompiledJarFile), "Compiled jar not found"); + } + + [Test] + public void VerifyClassParse () + { + var project = new BindingProject { Id = nameof (VerifyClassParse) }; + project.JavaSourceStrings.Add (new SourceFile { FileName = "Foo.java", Content = "package com.xamarin.test; public class Foo {}" }); + project.JavaSourceStrings.Add (new SourceFile { FileName = "Bar.java", Content = "package com.xamarin.test; public class Bar {}" }); + + var builder = BindingBuilder.CreateBestBetDefault (project); + builder.ProcessSteps = BindingBuilder.Steps.Javac | BindingBuilder.Steps.Jar | BindingBuilder.Steps.ClassParse; + builder.Clean (); + builder.Build (); + + var cpxml = Path.Combine (builder.IntermediateOutputPathAbsolute, BindingBuilder.ClassParseSubDir, project.Id + ".class-parse"); + Assert.AreEqual (cpxml, project.GeneratedClassParseXmlFile, "class-parse output file path mismatch."); + Assert.IsTrue (File.Exists (project.GeneratedClassParseXmlFile), "class-parse output file not found"); + var doc = XDocument.Load (project.GeneratedClassParseXmlFile); + Assert.IsNotNull (doc.XPathSelectElement ("//class[@name='Foo']"), "Foo node does not exist"); + } + + [Test] + [Ignore ("This won't run until we get a working partial Object.cs that exist only in xamarin-android.")] + public void VerifyApiXmlAdjuster () + { + var project = new BindingProject { Id = nameof (VerifyApiXmlAdjuster) }; + project.JavaSourceStrings.Add (new SourceFile { FileName = "Object.java", Content = "package java.lang; public class Object {}" }); + project.JavaSourceStrings.Add (new SourceFile { FileName = "Foo.java", Content = "package com.xamarin.test; public class Foo {}" }); + project.JavaSourceStrings.Add (new SourceFile { FileName = "Bar.java", Content = "package com.xamarin.test; public class Bar {}" }); + + var builder = BindingBuilder.CreateBestBetDefault (project); + builder.ProcessSteps = BindingBuilder.Steps.Javac | BindingBuilder.Steps.Jar | BindingBuilder.Steps.ClassParse | BindingBuilder.Steps.ApiXmlAdjuster; + builder.Clean (); + builder.Build (); + + var apixml = Path.Combine (builder.IntermediateOutputPathAbsolute, "api.xml"); + Assert.AreEqual (apixml, project.GeneratedApiXmlFile, "api.xml file path mismatch."); + Assert.IsTrue (File.Exists (project.GeneratedApiXmlFile), "api.xml file not found"); + var doc = XDocument.Load (project.GeneratedApiXmlFile); + Assert.IsNotNull (doc.XPathSelectElement ("//class[@name='Foo']"), "Foo node does not exist"); + } + + [Test] + [Ignore ("This won't run until we get a working partial Object.cs that exist only in xamarin-android.")] + public void VerifyGenerator () + { + var project = new BindingProject { Id = nameof (VerifyGenerator) }; + project.JavaSourceStrings.Add (new SourceFile { FileName = "Object.java", Content = "package java.lang; public class Object {}" }); + project.JavaSourceStrings.Add (new SourceFile { FileName = "Foo.java", Content = "package com.xamarin.test; public class Foo {}" }); + project.JavaSourceStrings.Add (new SourceFile { FileName = "Bar.java", Content = "package com.xamarin.test; public class Bar {}" }); + + var builder = BindingBuilder.CreateBestBetDefault (project); + builder.ProcessSteps = BindingBuilder.Steps.Javac | BindingBuilder.Steps.Jar | BindingBuilder.Steps.ClassParse | BindingBuilder.Steps.ApiXmlAdjuster | BindingBuilder.Steps.Generator; + builder.Clean (); + builder.Build (); + + var csDir = Path.Combine (builder.IntermediateOutputPathAbsolute, BindingBuilder.CSharpSourcesSubDir); + } + } +} diff --git a/tests/binding-integrated-Tests/binding-integration-Tests.csproj b/tests/binding-integrated-Tests/binding-integration-Tests.csproj new file mode 100644 index 000000000..677fed851 --- /dev/null +++ b/tests/binding-integrated-Tests/binding-integration-Tests.csproj @@ -0,0 +1,20 @@ + + + net461 + BindingIntegrationTests + + + ..\..\bin\TestDebug + + + ..\..\bin\TestRelease + + + + + + + + + + diff --git a/tools/generator/ClassGen.cs b/tools/generator/ClassGen.cs index 2cf25a106..474d09cd6 100644 --- a/tools/generator/ClassGen.cs +++ b/tools/generator/ClassGen.cs @@ -331,6 +331,8 @@ void GenerateAbstractMembers (StreamWriter sw, string indent, CodeGenerationOpti void GenMethods (StreamWriter sw, string indent, CodeGenerationOptions opt) { + var methodsToDeclare = 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. @@ -338,15 +340,17 @@ void GenMethods (StreamWriter sw, string indent, CodeGenerationOptions opt) .SelectMany (i => i.Methods) .Where (m => m.IsInterfaceDefaultMethod) .Where (m => !ContainsMethod (m, false, false)); - var overrides = defaultMethods.Where (m => m.IsInterfaceDefaultMethodOverride); + 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))); - foreach (Method m in Methods.Concat (defaultMethods.Except (overridens)).Where (m => m.DeclaringType.IsGeneratable)) { + methodsToDeclare = opt.SupportDefaultInterfaceMethods ? methodsToDeclare : methodsToDeclare.Concat (defaultMethods.Except (overridens)).Where (m => m.DeclaringType.IsGeneratable); + + foreach (Method m in methodsToDeclare) { bool virt = m.IsVirtual; m.IsVirtual = !IsFinal && virt; - if (m.IsAbstract && !m.IsInterfaceDefaultMethodOverride && !m.IsInterfaceDefaultMethod) + if (m.IsAbstract && m.OverriddenInterfaceMethod == null && (opt.SupportDefaultInterfaceMethods || !m.IsInterfaceDefaultMethod)) opt.CodeGenerator.WriteMethodAbstractDeclaration (m, sw, indent, opt, null, this); else opt.CodeGenerator.WriteMethod (m, sw, indent, opt, this, true); @@ -362,24 +366,6 @@ void GenMethods (StreamWriter sw, string indent, CodeGenerationOptions opt) } } - void GenProperties (StreamWriter sw, string indent, CodeGenerationOptions opt) - { - foreach (Property prop in Properties) { - bool get_virt = prop.Getter.IsVirtual; - bool set_virt = prop.Setter == null ? false : prop.Setter.IsVirtual; - prop.Getter.IsVirtual = !IsFinal && get_virt; - if (prop.Setter != null) - prop.Setter.IsVirtual = !IsFinal && set_virt; - if (prop.Getter.IsAbstract) - prop.GenerateAbstractDeclaration (sw, indent, opt, this); - else - prop.Generate (this, sw, indent, opt); - prop.Getter.IsVirtual = get_virt; - if (prop.Setter != null) - prop.Setter.IsVirtual = set_virt; - } - } - public override void Generate (StreamWriter sw, string indent, CodeGenerationOptions opt, GenerationInfo gen_info) { opt.ContextTypes.Push (this); @@ -476,7 +462,7 @@ public override void Generate (StreamWriter sw, string indent, CodeGenerationOpt GenConstructors (sw, indent + "\t", opt); - GenProperties (sw, indent + "\t", opt); + GenerateImplementedProperties (Properties, sw, indent + "\t", IsFinal, opt); GenMethods (sw, indent + "\t", opt); if (IsAbstract) @@ -587,7 +573,7 @@ void GenerateInvokerMembers (StreamWriter sw, string indent, CodeGenerationOptio // if (iface.IsGeneric) // continue; GenerateInvoker (sw, iface.Properties.Where (p => !ContainsProperty (p.Name, false, false)), indent, opt, members); - GenerateInvoker (sw, iface.Methods.Where (m => !m.IsInterfaceDefaultMethod && !ContainsMethod (m, false, false) && !IsCovariantMethod (m) && !explicitly_implemented_iface_methods.Contains (m.GetSignature ())), indent, opt, members, iface); + GenerateInvoker (sw, iface.Methods.Where (m => (opt.SupportDefaultInterfaceMethods || !m.IsInterfaceDefaultMethod) && !ContainsMethod (m, false, false) && !IsCovariantMethod (m) && !explicitly_implemented_iface_methods.Contains (m.GetSignature ())), indent, opt, members, iface); } if (BaseGen != null && BaseGen.FullName != "Java.Lang.Object") diff --git a/tools/generator/CodeGenerator.cs b/tools/generator/CodeGenerator.cs index 67a5e0ccf..e7f94c984 100644 --- a/tools/generator/CodeGenerator.cs +++ b/tools/generator/CodeGenerator.cs @@ -57,6 +57,7 @@ public CodeGeneratorOptions () public string MappingReportFile { get; set; } public bool OnlyRunApiXmlAdjuster { get; set; } public string ApiXmlAdjusterOutput { get; set; } + public bool SupportDefaultInterfaceMethods { get; set; } public static CodeGeneratorOptions Parse (string[] args) { @@ -103,6 +104,9 @@ public static CodeGeneratorOptions Parse (string[] args) { "sdk-platform|api-level=", "SDK Platform {VERSION}/API level.", v => opts.ApiLevel = v }, + { "default-interface-methods", + "For internal use.", + v => opts.SupportDefaultInterfaceMethods = v != null }, { "preserve-enums", "For internal use.", v => opts.PreserveEnums = v != null }, @@ -242,7 +246,8 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve UseGlobal = options.GlobalTypeNames, IgnoreNonPublicType = true, UseShortFileNames = options.UseShortFileNames, - ProductVersion = options.ProductVersion + ProductVersion = options.ProductVersion, + SupportDefaultInterfaceMethods = options.SupportDefaultInterfaceMethods, }; // Load reference libraries @@ -313,7 +318,7 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve // disable interface default methods here, especially before validation. gens = gens.Where (g => !g.IsObfuscated && g.Visibility != "private").ToList (); foreach (var gen in gens) { - gen.StripNonBindables (); + gen.StripNonBindables (opt); if (gen.IsGeneratable) AddTypeToTable (opt, gen); } @@ -624,6 +629,7 @@ public CodeGenerationTarget CodeGenerationTarget { public bool UseShortFileNames { get; set; } public IList Gens {get;set;} public int ProductVersion { get; set; } + public bool SupportDefaultInterfaceMethods { get; set; } public string GetOutputName (string s) { @@ -713,12 +719,14 @@ protected CodeGenerator () internal abstract void WriteConstructorBody (Ctor ctor, TextWriter writer, string indent, CodeGenerationOptions opt, StringCollection call_cleanup); internal abstract void WriteMethodIdField (Method method, TextWriter writer, string indent, CodeGenerationOptions opt); - internal abstract void WriteMethodBody (Method method, TextWriter writer, string indent, CodeGenerationOptions opt); + internal abstract void WriteMethodBody (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type); internal abstract void WriteFieldIdField (Field field, TextWriter writer, string indent, CodeGenerationOptions opt); internal abstract void WriteFieldGetBody (Field field, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type); internal abstract void WriteFieldSetBody (Field field, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type); + internal virtual string GetAllInterfaceImplements () => "IJavaObject"; + internal virtual void WriteField (Field field, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type) { if (field.IsEnumified) @@ -822,7 +830,7 @@ public void WriteMethodExplicitInterfaceInvoker (Method method, TextWriter write writer.WriteLine ("{0}unsafe {1} {2}.{3} ({4})", indent, opt.GetOutputName (method.RetVal.FullName), opt.GetOutputName (iface.FullName), method.Name, GenBase.GetSignature (method, opt)); writer.WriteLine ("{0}{{", indent); - WriteMethodBody (method, writer, indent + "\t", opt); + WriteMethodBody (method, writer, indent + "\t", opt, iface); writer.WriteLine ("{0}}}", indent); writer.WriteLine (); } @@ -1008,9 +1016,9 @@ public void WriteMethodExtensionOverload (Method method, TextWriter writer, stri string ret = opt.GetOutputName (method.RetVal.FullName.Replace ("Java.Lang.ICharSequence", "string")); writer.WriteLine (); - writer.WriteLine ("{0}public static {1} {2} (this {3} self, {4})", - indent, ret, method.Name, selfType, - GenBase.GetSignature (method, opt).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string")); + var parameters = GenBase.GetSignature (method, opt).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string"); + writer.WriteLine ("{0}public static {1} {2} (this {3} self{4}{5})", + indent, ret, method.Name, selfType, parameters.Length > 0 ? ", " : "", parameters); writer.WriteLine ("{0}{{", indent); WriteMethodStringOverloadBody (method, writer, indent + "\t", opt, true); writer.WriteLine ("{0}}}", indent); @@ -1055,6 +1063,13 @@ public void WriteMethodExtensionAsyncWrapper (Method method, TextWriter writer, writer.WriteLine (); } + static string GetDeclaringTypeOfExplicitInterfaceMethod (Method method) + { + return method.OverriddenInterfaceMethod != null ? + GetDeclaringTypeOfExplicitInterfaceMethod (method.OverriddenInterfaceMethod) : + method.DeclaringType.FullName; + } + public void WriteMethod (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type, bool generate_callbacks) { if (!method.IsValid) @@ -1068,7 +1083,8 @@ public void WriteMethod (Method method, TextWriter writer, string indent, CodeGe bool gen_string_overload = !method.IsOverride && method.Parameters.HasCharSequence && !type.ContainsMethod (name_and_jnisig); string static_arg = method.IsStatic ? " static" : String.Empty; - string virt_ov = method.IsOverride ? " override" : method.IsVirtual ? " virtual" : String.Empty; + bool is_explicit = opt.SupportDefaultInterfaceMethods && type.IsInterface && method.OverriddenInterfaceMethod != null; + string virt_ov = is_explicit ? string.Empty : method.IsOverride ? (opt.SupportDefaultInterfaceMethods && method.OverriddenInterfaceMethod != null ? "/*DIM override*/ virtual" : " override") : method.IsVirtual ? " virtual" : String.Empty; if ((string.IsNullOrEmpty (virt_ov) || virt_ov == " virtual") && type.RequiresNew (method.AdjustedName)) { virt_ov = " new" + virt_ov; } @@ -1084,9 +1100,19 @@ public void WriteMethod (Method method, TextWriter writer, string indent, CodeGe writer.WriteLine ("{0}[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", indent, method.JavaName, method.JniSignature, method.IsVirtual ? method.ConnectorName : String.Empty, method.AdditionalAttributeString ()); WriteMethodCustomAttributes (method, writer, indent); - writer.WriteLine ("{0}{1}{2}{3}{4} unsafe {5} {6} ({7})", indent, method.Visibility, static_arg, virt_ov, seal, ret, method.AdjustedName, GenBase.GetSignature (method, opt)); + string visibility = type.IsInterface && !method.IsStatic ? string.Empty : method.Visibility; + writer.WriteLine ("{0}{1}{2}{3}{4} unsafe {5} {6}{7} ({8})", + indent, + visibility, + static_arg, + virt_ov, + seal, + ret, + is_explicit ? GetDeclaringTypeOfExplicitInterfaceMethod (method.OverriddenInterfaceMethod) + '.' : string.Empty, + method.AdjustedName, + GenBase.GetSignature (method, opt)); writer.WriteLine ("{0}{{", indent); - WriteMethodBody (method, writer, indent + "\t", opt); + WriteMethodBody (method, writer, indent + "\t", opt, type); writer.WriteLine ("{0}}}", indent); writer.WriteLine (); diff --git a/tools/generator/GenBase.cs b/tools/generator/GenBase.cs index 4ee3c62a7..c29910d1a 100644 --- a/tools/generator/GenBase.cs +++ b/tools/generator/GenBase.cs @@ -64,6 +64,10 @@ public string DeprecatedComment { public bool IsGeneratable { get { return support.IsGeneratable; } } + + public virtual bool IsInterface { + get { return false; } + } public virtual ClassGen BaseGen { get { return null; } @@ -595,6 +599,24 @@ public List GetAllDerivedInterfaces () return result; } + protected void GenerateImplementedProperties (IEnumerable targetProperties, StreamWriter sw, string indent, bool isFinal, CodeGenerationOptions opt) + { + foreach (Property prop in targetProperties) { + bool get_virt = prop.Getter.IsVirtual; + bool set_virt = prop.Setter == null ? false : prop.Setter.IsVirtual; + prop.Getter.IsVirtual = !isFinal && get_virt; + if (prop.Setter != null) + prop.Setter.IsVirtual = !isFinal && set_virt; + if (prop.Getter.IsAbstract) + prop.GenerateAbstractDeclaration (sw, indent, opt, this); + else + prop.Generate (this, sw, indent, opt); + prop.Getter.IsVirtual = get_virt; + if (prop.Setter != null) + prop.Setter.IsVirtual = set_virt; + } + } + void GetAllDerivedInterfaces (List ifaces) { foreach (ISymbol isym in Interfaces) { @@ -716,15 +738,16 @@ protected virtual bool OnValidate (CodeGenerationOptions opt, GenericParameterDe bool property_filling; - public void StripNonBindables () + public void StripNonBindables (CodeGenerationOptions opt) { // As of now, if we generate bindings for interface default methods, that means users will // have to "implement" those methods because they are declared and you have to implement // any declared methods in C#. That is going to be problematic a lot. - methods = methods.Where (m => !m.IsInterfaceDefaultMethod).ToList (); + if (!opt.SupportDefaultInterfaceMethods) + methods = methods.Where (m => !m.IsInterfaceDefaultMethod).ToList (); nested_types = nested_types.Where (n => !n.IsObfuscated && n.Visibility != "private").ToList (); foreach (var n in nested_types) - n.StripNonBindables (); + n.StripNonBindables (opt); } public virtual void FixupAccessModifiers (CodeGenerationOptions opt) @@ -772,7 +795,7 @@ protected virtual bool GetEnumMappedMemberInfo () public void FixupMethodOverrides (CodeGenerationOptions opt) { - foreach (Method m in methods.Where (m => !m.IsInterfaceDefaultMethod)) { + foreach (Method m in methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod)) { for (var bt = this.GetBaseGen (opt); bt != null; bt = bt.GetBaseGen (opt)) { var bm = bt.Methods.FirstOrDefault (mm => mm.Name == m.Name && mm.Visibility == m.Visibility && ParameterList.Equals (mm.Parameters, m.Parameters)); if (bm != null && bm.RetVal.FullName == m.RetVal.FullName) { // if return type is different, it could be still "new", not "override". @@ -783,11 +806,18 @@ public void FixupMethodOverrides (CodeGenerationOptions opt) } // Interface default methods can be overriden. We want to process them differently. - foreach (Method m in methods.Where (m => m.IsInterfaceDefaultMethod)) { - foreach (var bt in this.GetAllDerivedInterfaces ()) { - var bm = bt.Methods.FirstOrDefault (mm => mm.Name == m.Name && ParameterList.Equals (mm.Parameters, m.Parameters)); + var checkDimOverrideTargets = opt.SupportDefaultInterfaceMethods ? methods : methods.Where (m => m.IsInterfaceDefaultMethod); + // We need to check all the implemented interfaces of all the base types. + var allIfaces = new List (); + for (var gen = this; gen != null; gen = gen.BaseGen) + gen.GetAllDerivedInterfaces (allIfaces); + foreach (Method m in checkDimOverrideTargets.Where (m => !m.IsStatic)) { + foreach (var bt in allIfaces.Distinct ()) { + // We mark a method as an override if (1) it is a DIM, or (2) if the base method is DIM + // (i.e. we don't mark as override if a class method "implements" normal iface method.) + var bm = bt.Methods.FirstOrDefault (mm => (m.IsInterfaceDefaultMethod || !mm.IsAbstract) && mm.Name == m.Name && ParameterList.Equals (mm.Parameters, m.Parameters)); if (bm != null) { - m.IsInterfaceDefaultMethodOverride = true; + m.OverriddenInterfaceMethod = bm; break; } } diff --git a/tools/generator/InterfaceGen.cs b/tools/generator/InterfaceGen.cs index dd58b64ad..76b7c07ac 100644 --- a/tools/generator/InterfaceGen.cs +++ b/tools/generator/InterfaceGen.cs @@ -9,6 +9,7 @@ using Xamarin.Android.Binder; using Xamarin.Android.Tools; +using System.Diagnostics; namespace MonoDroid.Generation { #if HAVE_CECIL @@ -105,6 +106,10 @@ public bool IsConstSugar { } } + public override bool IsInterface { + get { return true; } + } + public bool IsListener { // If there is a property it cannot generate valid implementor, so reject this at least so far. get { return Name.EndsWith ("Listener") && Properties.Count == 0 && Interfaces.Count == 0; } @@ -196,11 +201,13 @@ protected override bool OnValidate (CodeGenerationOptions opt, GenericParameterD void GenMethods (StreamWriter sw, string indent, CodeGenerationOptions opt) { - foreach (Method m in Methods.Where (m => !m.IsStatic)) { + foreach (Method m in Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod)) { if (m.Name == Name || ContainsProperty (m.Name, true)) m.Name = "Invoke" + m.Name; opt.CodeGenerator.WriteMethodDeclaration (m, sw, indent, opt, this, AssemblyQualifiedName + "Invoker"); } + foreach (Method m in Methods.Where (m => m.IsInterfaceDefaultMethod)) + opt.CodeGenerator.WriteMethod (m, sw, indent, opt, this, true); } void GenExtensionMethods (StreamWriter sw, string indent, CodeGenerationOptions opt) @@ -213,8 +220,9 @@ void GenExtensionMethods (StreamWriter sw, string indent, CodeGenerationOptions void GenProperties (StreamWriter sw, string indent, CodeGenerationOptions opt) { - foreach (Property prop in Properties.Where (p => !p.Getter.IsStatic)) + foreach (Property prop in Properties.Where (p => !p.Getter.IsStatic && !p.Getter.IsInterfaceDefaultMethod)) prop.GenerateDeclaration (sw, indent, opt, this, AssemblyQualifiedName + "Invoker"); + base.GenerateImplementedProperties (Properties.Where (p => p.Getter.IsInterfaceDefaultMethod), sw, indent, false, opt); } void GenerateInvoker (StreamWriter sw, string indent, CodeGenerationOptions opt) @@ -255,14 +263,14 @@ void GenerateInvoker (StreamWriter sw, string indent, CodeGenerationOptions opt) sw.WriteLine (); HashSet members = new HashSet (); - GenerateInvoker (sw, Properties.Where (p => !p.Getter.IsStatic), indent + "\t", opt, members); - GenerateInvoker (sw, Methods.Where (m => !m.IsStatic), indent + "\t", opt, members); + GenerateInvoker (sw, Properties.Where (p => !p.Getter.IsStatic && !p.Getter.IsInterfaceDefaultMethod), indent + "\t", opt, members); + GenerateInvoker (sw, Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod), indent + "\t", opt, members); if (FullName == "Java.Lang.ICharSequence") GenCharSequenceEnumerator (sw, indent + "\t", opt); foreach (InterfaceGen iface in GetAllDerivedInterfaces ()) { - GenerateInvoker (sw, iface.Properties.Where (p => !p.Getter.IsStatic), indent + "\t", opt, members); - GenerateInvoker (sw, iface.Methods.Where (m => !m.IsStatic && !IsCovariantMethod (m) && !(iface.FullName.StartsWith ("Java.Lang.ICharSequence") && m.Name.EndsWith ("Formatted"))), indent + "\t", opt, members); + GenerateInvoker (sw, iface.Properties.Where (p => !p.Getter.IsStatic && !p.Getter.IsInterfaceDefaultMethod), indent + "\t", opt, members); + GenerateInvoker (sw, iface.Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod && !IsCovariantMethod (m) && !(iface.FullName.StartsWith ("Java.Lang.ICharSequence") && m.Name.EndsWith ("Formatted"))), indent + "\t", opt, members); if (iface.FullName == "Java.Lang.ICharSequence") GenCharSequenceEnumerator (sw, indent + "\t", opt); } @@ -618,7 +626,7 @@ void GenerateProperty (StreamWriter sw, string indent, CodeGenerationOptions opt // For each interface, generate either an abstract method or an explicit implementation method. public void GenerateAbstractMembers (ClassGen gen, StreamWriter sw, string indent, CodeGenerationOptions opt) { - foreach (Method m in Methods.Where (m => !m.IsInterfaceDefaultMethod && !m.IsStatic)) { + foreach (Method m in Methods.Where (m => (!opt.SupportDefaultInterfaceMethods || !m.IsInterfaceDefaultMethod) && !m.IsStatic)) { bool mapped = false; string sig = m.GetSignature (); if (opt.ContextGeneratedMethods.Any (_ => _.Name == m.Name && _.JniSignature == m.JniSignature)) @@ -636,7 +644,7 @@ public void GenerateAbstractMembers (ClassGen gen, StreamWriter sw, string inden opt.CodeGenerator.WriteMethodAbstractDeclaration (m, sw, indent, opt, this, gen); opt.ContextGeneratedMethods.Add (m); } - foreach (Property prop in Properties.Where (p => !p.Getter.IsStatic)) { + foreach (Property prop in Properties.Where (p => (!opt.SupportDefaultInterfaceMethods || !p.Getter.IsInterfaceDefaultMethod) && !p.Getter.IsStatic)) { if (gen.ContainsProperty (prop.Name, false)) continue; prop.GenerateAbstractDeclaration (sw, indent, opt, gen); @@ -663,7 +671,7 @@ void GenerateDeclaration (StreamWriter sw, string indent, CodeGenerationOptions if (this.TypeParameters != null && this.TypeParameters.Any ()) sw.WriteLine ("{0}{1}", indent, TypeParameters.ToGeneratedAttributeString ()); sw.WriteLine ("{0}{1} partial interface {2} : {3} {{", indent, Visibility, Name, - Interfaces.Count == 0 || sb.Length == 0 ? "IJavaObject" : sb.ToString ()); + Interfaces.Count == 0 || sb.Length == 0 ? (opt.SupportDefaultInterfaceMethods ? opt.CodeGenerator.GetAllInterfaceImplements () : "IJavaObject") : sb.ToString ()); sw.WriteLine (); GenProperties (sw, indent + "\t", opt); GenMethods (sw, indent + "\t", opt); @@ -736,6 +744,16 @@ public override void Generate (StreamWriter sw, string indent, CodeGenerationOpt } } + var defaultInterfaceMethods = Methods.Where (m => m.IsInterfaceDefaultMethod); + var defaultInterfaceProperties = Properties.Where (p => p.Getter != null && p.Getter.IsInterfaceDefaultMethod || p.Setter != null && p.Setter.IsInterfaceDefaultMethod); + if (defaultInterfaceMethods.Any () || defaultInterfaceProperties.Any ()) { + sw.WriteLine ("{0}partial interface {1} {{", indent, Name); + sw.WriteLine (); + opt.CodeGenerator.WriteClassHandle (this, sw, indent + "\t", opt, Name); + sw.WriteLine ("{0}}}", indent); + sw.WriteLine (); + } + if (IsConstSugar) return; diff --git a/tools/generator/JavaInteropCodeGenerator.cs b/tools/generator/JavaInteropCodeGenerator.cs index 635bf56fd..d7365eae2 100644 --- a/tools/generator/JavaInteropCodeGenerator.cs +++ b/tools/generator/JavaInteropCodeGenerator.cs @@ -54,15 +54,21 @@ protected virtual string GetPeerMembersType () return "JniPeerMembers"; } + internal override string GetAllInterfaceImplements () + { + return "IJavaObject, IJavaPeerable"; + } + internal override void WriteClassHandle (InterfaceGen type, TextWriter writer, string indent, CodeGenerationOptions opt, string declaringType) { - writer.WriteLine ("{0}new static JniPeerMembers _members = new JniPeerMembers (\"{1}\", typeof ({2}));",indent, type.RawJniName, declaringType); + writer.WriteLine ("{0}new static JniPeerMembers _members = new {1} (\"{2}\", typeof ({3}));",indent, GetPeerMembersType (), type.RawJniName, declaringType); } internal override void WriteClassInvokerHandle (ClassGen type, TextWriter writer, string indent, CodeGenerationOptions opt, string declaringType) { - writer.WriteLine ("{0}internal new static readonly JniPeerMembers _members = new JniPeerMembers (\"{1}\", typeof ({2}));", + writer.WriteLine ("{0}internal new static readonly JniPeerMembers _members = new {1} (\"{2}\", typeof ({3}));", indent, + GetPeerMembersType (), type.RawJniName, declaringType); writer.WriteLine (); @@ -78,8 +84,9 @@ internal override void WriteClassInvokerHandle (ClassGen type, TextWriter writer internal override void WriteInterfaceInvokerHandle (InterfaceGen type, TextWriter writer, string indent, CodeGenerationOptions opt, string declaringType) { - writer.WriteLine ("{0}internal new static readonly JniPeerMembers _members = new JniPeerMembers (\"{1}\", typeof ({2}));", + writer.WriteLine ("{0}internal new static readonly JniPeerMembers _members = new {1} (\"{2}\", typeof ({3}));", indent, + GetPeerMembersType (), type.RawJniName, declaringType); writer.WriteLine (); @@ -139,7 +146,7 @@ internal override void WriteMethodIdField (Method method, TextWriter writer, str // No method id_ field required; it's now an `id` constant in the binding. } - internal override void WriteMethodBody (Method method, TextWriter writer, string indent, CodeGenerationOptions opt) + internal override void WriteMethodBody (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type) { writer.WriteLine ("{0}const string __id = \"{1}.{2}\";", indent, method.JavaName, method.JniSignature); foreach (string prep in method.Parameters.GetCallPrep (opt)) @@ -156,21 +163,26 @@ internal override void WriteMethodBody (Method method, TextWriter writer, string writer.Write ("var __rm = "); } + string castToPeerable = type.IsInterface ? "(IJavaPeerable) " : string.Empty; + 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});", + } else if (method.IsFinal || method.IsInterfaceDefaultMethod) { + writer.WriteLine ("_members.InstanceMethods.InvokeNonvirtual{0}Method (__id, {1}this{2});", invokeType, + castToPeerable, method.Parameters.GetCallArgs (opt, invoker: false)); } else if (method.IsVirtual && !method.IsAbstract) { - writer.WriteLine ("_members.InstanceMethods.InvokeVirtual{0}Method (__id, this{1});", + writer.WriteLine ("_members.InstanceMethods.InvokeVirtual{0}Method (__id, {1}this{2});", invokeType, + castToPeerable, method.Parameters.GetCallArgs (opt, invoker: false)); } else { - writer.WriteLine ("_members.InstanceMethods.InvokeAbstract{0}Method (__id, this{1});", + writer.WriteLine ("_members.InstanceMethods.InvokeAbstract{0}Method (__id, {1}this{2});", invokeType, + castToPeerable, method.Parameters.GetCallArgs (opt, invoker: false)); } diff --git a/tools/generator/Method.cs b/tools/generator/Method.cs index 29a1cf19e..5a900d552 100644 --- a/tools/generator/Method.cs +++ b/tools/generator/Method.cs @@ -140,7 +140,7 @@ public XmlMethod (GenBase declaringType, XElement elem) name = StringRocks.MemberToPascalCase (JavaName); is_abstract = elem.XGetAttribute ("abstract") == "true"; - if (declaringType is InterfaceGen) + if (declaringType.IsInterface) is_interface_default_method = !is_abstract && !is_static; GenerateDispatchingSetter = elem.Attribute ("generateDispatchingSetter") != null; @@ -363,7 +363,7 @@ public bool IsOverride { set { is_override = value; } } - public bool IsInterfaceDefaultMethodOverride { get; set; } + public Method OverriddenInterfaceMethod { get; set; } public bool IsVoid { get { return RetVal.JavaName == "void"; } diff --git a/tools/generator/MethodBase.cs b/tools/generator/MethodBase.cs index 5e64862bc..7914a551b 100644 --- a/tools/generator/MethodBase.cs +++ b/tools/generator/MethodBase.cs @@ -30,6 +30,10 @@ public abstract string Deprecated { get; } + // FIXME: this is semantically incorrect. A generic method should rather be evaluated as: + // GenericArguments != null && GenericArguments.Count > 0. + // However, changing this part like this causes various build errors especially with Java.Lang.Enum and other various generic parameter resolution, + // so we should leave this as is (maybe renaming this is an option, in case we want keep using this generator). public virtual bool IsGeneric { get { return parms.HasGeneric; } } diff --git a/tools/generator/Property.cs b/tools/generator/Property.cs index c896c1bff..bc2ac8394 100644 --- a/tools/generator/Property.cs +++ b/tools/generator/Property.cs @@ -264,7 +264,7 @@ public void Generate (GenBase gen, StreamWriter sw, string indent, CodeGeneratio opt.CodeGenerator.WriteMethodIdField (Getter, sw, indent, opt); if (Setter != null) opt.CodeGenerator.WriteMethodIdField (Setter, sw, indent, opt); - string visibility = Getter.IsAbstract && Getter.RetVal.IsGeneric ? "protected" : (Setter ?? Getter).Visibility; + string visibility = gen.IsInterface ? string.Empty : Getter.IsAbstract && Getter.RetVal.IsGeneric ? "protected" : (Setter ?? Getter).Visibility; // Unlike [Register], mcs does not allow applying [Obsolete] on property accessors, so we can apply them only under limited condition... if (Getter.Deprecated != null && (Setter == null || Setter.Deprecated != null)) sw.WriteLine ("{0}[Obsolete (@\"{1}\")]", indent, Getter.Deprecated.Replace ("\"", "\"\"").Trim () + (Setter != null && Setter.Deprecated != Getter.Deprecated ? " " + Setter.Deprecated.Replace ("\"", "\"\"").Trim () : null)); @@ -274,7 +274,7 @@ public void Generate (GenBase gen, StreamWriter sw, string indent, CodeGeneratio sw.WriteLine ("{0}\t// Metadata.xml XPath method reference: path=\"{1}/method[@name='{2}'{3}]\"", indent, gen.MetadataXPathReference, Getter.JavaName, Getter.Parameters.GetMethodXPathPredicate ()); sw.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", indent, Getter.JavaName, Getter.JniSignature, Getter.ConnectorName, Getter.AdditionalAttributeString ()); sw.WriteLine ("{0}\tget {{", indent); - opt.CodeGenerator.WriteMethodBody (Getter, sw, indent + "\t\t", opt); + opt.CodeGenerator.WriteMethodBody (Getter, sw, indent + "\t\t", opt, gen); sw.WriteLine ("{0}\t}}", indent); if (Setter != null) { if (gen.IsGeneratable) @@ -284,7 +284,7 @@ public void Generate (GenBase gen, StreamWriter sw, string indent, CodeGeneratio sw.WriteLine ("{0}\tset {{", indent); string pname = Setter.Parameters [0].Name; Setter.Parameters [0].Name = "value"; - opt.CodeGenerator.WriteMethodBody (Setter, sw, indent + "\t\t", opt); + opt.CodeGenerator.WriteMethodBody (Setter, sw, indent + "\t\t", opt, gen); Setter.Parameters [0].Name = pname; sw.WriteLine ("{0}\t}}", indent); } else if (GenerateDispatchingSetter) { diff --git a/tools/generator/Tests/Integration-Tests/BaseGeneratorTest.cs b/tools/generator/Tests/Integration-Tests/BaseGeneratorTest.cs index 28a5dcb56..425c72c6b 100644 --- a/tools/generator/Tests/Integration-Tests/BaseGeneratorTest.cs +++ b/tools/generator/Tests/Integration-Tests/BaseGeneratorTest.cs @@ -130,7 +130,7 @@ protected string FullPath (string path) return Path.Combine (dir, path.Replace ('/', Path.DirectorySeparatorChar)); } - protected void Run (CodeGenerationTarget target, string outputPath, string apiDescriptionFile, string expectedPath, string[] additionalSupportPaths = null) + protected void Run (CodeGenerationTarget target, string outputPath, string apiDescriptionFile, string expectedPath, string [] additionalSupportPaths = null) { Cleanup (outputPath); diff --git a/tools/generator/Tests/Integration-Tests/Compiler.cs b/tools/generator/Tests/Integration-Tests/Compiler.cs index 9c74b99e6..5dafd90e8 100644 --- a/tools/generator/Tests/Integration-Tests/Compiler.cs +++ b/tools/generator/Tests/Integration-Tests/Compiler.cs @@ -4,6 +4,8 @@ using System.IO; using System.Linq; using System.Collections.Generic; +using System.Diagnostics; +using System.Text; using NUnit.Framework; namespace generatortests @@ -67,6 +69,46 @@ public static Assembly Compile (Xamarin.Android.Binder.CodeGeneratorOptions opti parameters.IncludeDebugInformation = false; #endif + if (options.SupportDefaultInterfaceMethods) { + var sb = new StringBuilder (); + sb.Append (" /t:library") + .Append (" /unsafe") + .Append (" /langversion:latest") + .Append (' ').Append ($"/out:\"{assemblyFileName}\""); + foreach (var assembly in parameters.ReferencedAssemblies) + sb.Append ($" /r:\"{assembly}\""); + if (parameters.IncludeDebugInformation) + sb.Append (" /debug:portable"); + foreach (var sourceFile in sourceFiles) + sb.Append (' ').Append ($"\"{sourceFile}\""); + string compiler = Path.GetFullPath (Path.Combine (unitTestFrameworkAssemblyPath, "..", "..", "..", "packages", "xamarin.android.csc.dim.0.1.2", "tools", "csc.exe")); + var pinfo = new ProcessStartInfo () { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + if (Environment.OSVersion.Platform == PlatformID.Win32NT) { + pinfo.FileName = compiler; + pinfo.Arguments = sb.ToString (); + } else { + pinfo.FileName = "mono"; // SYSMONO + pinfo.Arguments = compiler + sb; + } + var results = new StringBuilder (); + var proc = new Process (); + proc.ErrorDataReceived += (o, e) => + results.Append (e.Data).Append (Environment.NewLine); + proc.OutputDataReceived += (o, e) => + results.Append (e.Data).Append (Environment.NewLine); + proc.StartInfo = pinfo; + proc.Start (); + proc.BeginOutputReadLine (); + proc.BeginErrorReadLine (); + proc.WaitForExit (); + output = results.ToString (); + hasErrors = proc.ExitCode != 0; + return hasErrors ? null : Assembly.LoadFrom (Path.GetFullPath (assemblyFileName)); + } using (var codeProvider = GetCodeDomProvider ()) { CompilerResults results = codeProvider.CompileAssemblyFromFile (parameters, sourceFiles.ToArray ()); diff --git a/tools/generator/Tests/Integration-Tests/DefaultInterfaceMethods.cs b/tools/generator/Tests/Integration-Tests/DefaultInterfaceMethods.cs new file mode 100644 index 000000000..b927500cf --- /dev/null +++ b/tools/generator/Tests/Integration-Tests/DefaultInterfaceMethods.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using System.Linq; +using NUnit.Framework; +using Xamarin.Android.Binder; + +namespace generatortests +{ + [TestFixture] + public class DefaultInterfaceMethods : BaseGeneratorTest + { + [Test] + public void GeneratedOK () + { + Options.SupportDefaultInterfaceMethods = true; + RunAllTargets ( + outputRelativePath: "DefaultInterfaceMethods", + apiDescriptionFile: "expected.ji/DefaultInterfaceMethods/DefaultInterfaceMethods.xml", + expectedRelativePath: "DefaultInterfaceMethods", + additionalSupportPaths: null); + } + + void RunAllTargets (string outputRelativePath, string apiDescriptionFile, string expectedRelativePath, string [] additionalSupportPaths) + { + Run (CodeGenerationTarget.JavaInterop1, Path.Combine ("out.ji", outputRelativePath), apiDescriptionFile, Path.Combine ("expected.ji", expectedRelativePath), additionalSupportPaths); + } + } +} + diff --git a/tools/generator/Tests/Unit-Tests/JavaInteropCodeGeneratorTests.cs b/tools/generator/Tests/Unit-Tests/JavaInteropCodeGeneratorTests.cs index 8cc33ce4b..cc68c4dec 100644 --- a/tools/generator/Tests/Unit-Tests/JavaInteropCodeGeneratorTests.cs +++ b/tools/generator/Tests/Unit-Tests/JavaInteropCodeGeneratorTests.cs @@ -220,7 +220,7 @@ public void WriteMethodBody () var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); var method = new TestMethod (@class, "bar"); Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); - generator.WriteMethodBody (method, writer, string.Empty, options); + generator.WriteMethodBody (method, writer, string.Empty, options, @class); Assert.AreEqual (@"const string __id = ""bar.()V""; try { diff --git a/tools/generator/Tests/Unit-Tests/XamarinAndroidCodeGeneratorTests.cs b/tools/generator/Tests/Unit-Tests/XamarinAndroidCodeGeneratorTests.cs index a70dccb2d..e54a2cebe 100644 --- a/tools/generator/Tests/Unit-Tests/XamarinAndroidCodeGeneratorTests.cs +++ b/tools/generator/Tests/Unit-Tests/XamarinAndroidCodeGeneratorTests.cs @@ -216,7 +216,7 @@ public void WriteMethodBody () var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); var method = new TestMethod (@class, "bar"); Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); - generator.WriteMethodBody (method, writer, string.Empty, options); + generator.WriteMethodBody (method, writer, string.Empty, options, @class); Assert.AreEqual (@"if (id_bar == IntPtr.Zero) id_bar = JNIEnv.GetMethodID (class_ref, ""bar"", ""()V""); diff --git a/tools/generator/Tests/expected.ji/DefaultInterfaceMethods/DefaultInterfaceMethods.xml b/tools/generator/Tests/expected.ji/DefaultInterfaceMethods/DefaultInterfaceMethods.xml new file mode 100644 index 000000000..9a3dda9c7 --- /dev/null +++ b/tools/generator/Tests/expected.ji/DefaultInterfaceMethods/DefaultInterfaceMethods.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/generator/Tests/expected.ji/DefaultInterfaceMethods/Xamarin.Test.ITheInterface.cs b/tools/generator/Tests/expected.ji/DefaultInterfaceMethods/Xamarin.Test.ITheInterface.cs new file mode 100644 index 000000000..af1137b35 --- /dev/null +++ b/tools/generator/Tests/expected.ji/DefaultInterfaceMethods/Xamarin.Test.ITheInterface.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using Android.Runtime; +using Java.Interop; + +namespace Xamarin.Test { + + partial interface ITheInterface { + + new static JniPeerMembers _members = new JniPeerMembers ("xamarin/test/TheInterface", typeof (ITheInterface)); + } + + // Metadata.xml XPath interface reference: path="/api/package[@name='xamarin.test']/interface[@name='TheInterface']" + [Register ("xamarin/test/TheInterface", "", "Xamarin.Test.ITheInterfaceInvoker")] + public partial interface ITheInterface : IJavaObject, IJavaPeerable { + + static Delegate cb_getBar; +#pragma warning disable 0169 + static Delegate GetGetBarHandler () + { + if (cb_getBar == null) + cb_getBar = JNINativeWrapper.CreateDelegate ((Func) n_GetBar); + return cb_getBar; + } + + static int n_GetBar (IntPtr jnienv, IntPtr native__this) + { + global::Xamarin.Test.ITheInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Bar; + } +#pragma warning restore 0169 + + virtual unsafe int Bar { + // Metadata.xml XPath method reference: path="/api/package[@name='xamarin.test']/interface[@name='TheInterface']/method[@name='getBar' and count(parameter)=0]" + [Register ("getBar", "()I", "GetGetBarHandler")] + get { + const string __id = "getBar.()I"; + try { + var __rm = _members.InstanceMethods.InvokeNonvirtualInt32Method (__id, (IJavaPeerable) this, null); + return __rm; + } finally { + } + } + } + + static Delegate cb_getStringProp; +#pragma warning disable 0169 + static Delegate GetGetStringPropHandler () + { + if (cb_getStringProp == null) + cb_getStringProp = JNINativeWrapper.CreateDelegate ((Func) n_GetStringProp); + return cb_getStringProp; + } + + static IntPtr n_GetStringProp (IntPtr jnienv, IntPtr native__this) + { + global::Xamarin.Test.ITheInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return JNIEnv.NewString (__this.StringProp); + } +#pragma warning restore 0169 + + static Delegate cb_setStringProp_Ljava_lang_String_; +#pragma warning disable 0169 + static Delegate GetSetStringProp_Ljava_lang_String_Handler () + { + if (cb_setStringProp_Ljava_lang_String_ == null) + cb_setStringProp_Ljava_lang_String_ = JNINativeWrapper.CreateDelegate ((Action) n_SetStringProp_Ljava_lang_String_); + return cb_setStringProp_Ljava_lang_String_; + } + + static void n_SetStringProp_Ljava_lang_String_ (IntPtr jnienv, IntPtr native__this, IntPtr native_v) + { + global::Xamarin.Test.ITheInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + string v = JNIEnv.GetString (native_v, JniHandleOwnership.DoNotTransfer); + __this.StringProp = v; + } +#pragma warning restore 0169 + + virtual unsafe string StringProp { + // Metadata.xml XPath method reference: path="/api/package[@name='xamarin.test']/interface[@name='TheInterface']/method[@name='getStringProp' and count(parameter)=0]" + [Register ("getStringProp", "()Ljava/lang/String;", "GetGetStringPropHandler")] + get { + const string __id = "getStringProp.()Ljava/lang/String;"; + try { + var __rm = _members.InstanceMethods.InvokeNonvirtualObjectMethod (__id, (IJavaPeerable) this, null); + return JNIEnv.GetString (__rm.Handle, JniHandleOwnership.TransferLocalRef); + } finally { + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='xamarin.test']/interface[@name='TheInterface']/method[@name='setStringProp' and count(parameter)=1 and parameter[1][@type='java.lang.String']]" + [Register ("setStringProp", "(Ljava/lang/String;)V", "GetSetStringProp_Ljava_lang_String_Handler")] + set { + const string __id = "setStringProp.(Ljava/lang/String;)V"; + IntPtr native_value = JNIEnv.NewString (value); + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (native_value); + _members.InstanceMethods.InvokeNonvirtualVoidMethod (__id, (IJavaPeerable) this, __args); + } finally { + JNIEnv.DeleteLocalRef (native_value); + } + } + } + + static Delegate cb_foo; +#pragma warning disable 0169 + static Delegate GetFooHandler () + { + if (cb_foo == null) + cb_foo = JNINativeWrapper.CreateDelegate ((Func) n_Foo); + return cb_foo; + } + + static int n_Foo (IntPtr jnienv, IntPtr native__this) + { + global::Xamarin.Test.ITheInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Foo (); + } +#pragma warning restore 0169 + + // Metadata.xml XPath method reference: path="/api/package[@name='xamarin.test']/interface[@name='TheInterface']/method[@name='foo' and count(parameter)=0]" + [Register ("foo", "()I", "GetFooHandler")] + virtual unsafe int Foo () + { + const string __id = "foo.()I"; + try { + var __rm = _members.InstanceMethods.InvokeNonvirtualInt32Method (__id, (IJavaPeerable) this, null); + return __rm; + } finally { + } + } + + static Delegate cb_string1; +#pragma warning disable 0169 + static Delegate GetString1Handler () + { + if (cb_string1 == null) + cb_string1 = JNINativeWrapper.CreateDelegate ((Func) n_String1); + return cb_string1; + } + + static IntPtr n_String1 (IntPtr jnienv, IntPtr native__this) + { + global::Xamarin.Test.ITheInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return JNIEnv.NewString (__this.String1 ()); + } +#pragma warning restore 0169 + + // Metadata.xml XPath method reference: path="/api/package[@name='xamarin.test']/interface[@name='TheInterface']/method[@name='string1' and count(parameter)=0]" + [Register ("string1", "()Ljava/lang/String;", "GetString1Handler")] + virtual unsafe string String1 () + { + const string __id = "string1.()Ljava/lang/String;"; + try { + var __rm = _members.InstanceMethods.InvokeNonvirtualObjectMethod (__id, (IJavaPeerable) this, null); + return JNIEnv.GetString (__rm.Handle, JniHandleOwnership.TransferLocalRef); + } finally { + } + } + + static Delegate cb_string1_Ljava_lang_String_arrayLjava_lang_String_arrayLjava_lang_String_; +#pragma warning disable 0169 + static Delegate GetString1_Ljava_lang_String_arrayLjava_lang_String_arrayLjava_lang_String_Handler () + { + if (cb_string1_Ljava_lang_String_arrayLjava_lang_String_arrayLjava_lang_String_ == null) + cb_string1_Ljava_lang_String_arrayLjava_lang_String_arrayLjava_lang_String_ = JNINativeWrapper.CreateDelegate ((Func) n_String1_Ljava_lang_String_arrayLjava_lang_String_arrayLjava_lang_String_); + return cb_string1_Ljava_lang_String_arrayLjava_lang_String_arrayLjava_lang_String_; + } + + static IntPtr n_String1_Ljava_lang_String_arrayLjava_lang_String_arrayLjava_lang_String_ (IntPtr jnienv, IntPtr native__this, IntPtr native_p1, IntPtr native_p2, IntPtr native_p3) + { + global::Xamarin.Test.ITheInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + string p1 = JNIEnv.GetString (native_p1, JniHandleOwnership.DoNotTransfer); + string[] p2 = (string[]) JNIEnv.GetArray (native_p2, JniHandleOwnership.DoNotTransfer, typeof (string)); + string[] p3 = (string[]) JNIEnv.GetArray (native_p3, JniHandleOwnership.DoNotTransfer, typeof (string)); + IntPtr __ret = JNIEnv.NewString (__this.String1 (p1, p2, p3)); + if (p2 != null) + JNIEnv.CopyArray (p2, native_p2); + if (p3 != null) + JNIEnv.CopyArray (p3, native_p3); + return __ret; + } +#pragma warning restore 0169 + + // Metadata.xml XPath method reference: path="/api/package[@name='xamarin.test']/interface[@name='TheInterface']/method[@name='string1' and count(parameter)=3 and parameter[1][@type='java.lang.String'] and parameter[2][@type='java.lang.String[]'] and parameter[3][@type='java.lang.String...']]" + [Register ("string1", "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;", "GetString1_Ljava_lang_String_arrayLjava_lang_String_arrayLjava_lang_String_Handler")] + virtual unsafe string String1 (string p1, string[] p2, params string[] p3) + { + const string __id = "string1.(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;"; + IntPtr native_p1 = JNIEnv.NewString (p1); + IntPtr native_p2 = JNIEnv.NewArray (p2); + IntPtr native_p3 = JNIEnv.NewArray (p3); + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [3]; + __args [0] = new JniArgumentValue (native_p1); + __args [1] = new JniArgumentValue (native_p2); + __args [2] = new JniArgumentValue (native_p3); + var __rm = _members.InstanceMethods.InvokeNonvirtualObjectMethod (__id, (IJavaPeerable) this, __args); + return JNIEnv.GetString (__rm.Handle, JniHandleOwnership.TransferLocalRef); + } finally { + JNIEnv.DeleteLocalRef (native_p1); + if (p2 != null) { + JNIEnv.CopyArray (native_p2, p2); + JNIEnv.DeleteLocalRef (native_p2); + } + if (p3 != null) { + JNIEnv.CopyArray (native_p3, p3); + JNIEnv.DeleteLocalRef (native_p3); + } + } + } + + } + + [global::Android.Runtime.Register ("xamarin/test/TheInterface", DoNotGenerateAcw=true)] + internal class ITheInterfaceInvoker : global::Java.Lang.Object, ITheInterface { + + internal new static readonly JniPeerMembers _members = new JniPeerMembers ("xamarin/test/TheInterface", typeof (ITheInterfaceInvoker)); + + 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 ITheInterface 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), "xamarin.test.TheInterface")); + 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 ITheInterfaceInvoker (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/tools/generator/Tests/expected.ji/DefaultInterfaceMethods/Xamarin.Test.TheImplementor.cs b/tools/generator/Tests/expected.ji/DefaultInterfaceMethods/Xamarin.Test.TheImplementor.cs new file mode 100644 index 000000000..33b88457a --- /dev/null +++ b/tools/generator/Tests/expected.ji/DefaultInterfaceMethods/Xamarin.Test.TheImplementor.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using Android.Runtime; +using Java.Interop; + +namespace Xamarin.Test { + + // Metadata.xml XPath class reference: path="/api/package[@name='xamarin.test']/class[@name='TheImplementor']" + [global::Android.Runtime.Register ("xamarin/test/TheImplementor", DoNotGenerateAcw=true)] + public partial class TheImplementor : global::Java.Lang.Object, global::Xamarin.Test.ITheInterface { + + internal new static readonly JniPeerMembers _members = new JniPeerMembers ("xamarin/test/TheImplementor", typeof (TheImplementor)); + internal static new IntPtr class_ref { + get { + return _members.JniPeerType.PeerReference.Handle; + } + } + + public override global::Java.Interop.JniPeerMembers JniPeerMembers { + get { return _members; } + } + + protected override IntPtr ThresholdClass { + get { return _members.JniPeerType.PeerReference.Handle; } + } + + protected override global::System.Type ThresholdType { + get { return _members.ManagedPeerType; } + } + + protected TheImplementor (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) {} + + // Metadata.xml XPath constructor reference: path="/api/package[@name='xamarin.test']/class[@name='TheImplementor']/constructor[@name='TheImplementor' and count(parameter)=0]" + [Register (".ctor", "()V", "")] + public unsafe TheImplementor () + : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer) + { + const string __id = "()V"; + + if (((global::Java.Lang.Object) this).Handle != IntPtr.Zero) + return; + + try { + var __r = _members.InstanceMethods.StartCreateInstance (__id, ((object) this).GetType (), null); + SetHandle (__r.Handle, JniHandleOwnership.TransferLocalRef); + _members.InstanceMethods.FinishCreateInstance (__id, this, null); + } finally { + } + } + + } +} diff --git a/tools/generator/Tests/generator-Tests.csproj b/tools/generator/Tests/generator-Tests.csproj index 760478073..eedbe6647 100644 --- a/tools/generator/Tests/generator-Tests.csproj +++ b/tools/generator/Tests/generator-Tests.csproj @@ -1,5 +1,6 @@  + Debug AnyCPU @@ -75,6 +76,7 @@ + @@ -167,6 +169,15 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + @@ -200,4 +211,8 @@ + + + + diff --git a/tools/generator/Tests/packages.config b/tools/generator/Tests/packages.config index 9f1bd4562..158b37758 100644 --- a/tools/generator/Tests/packages.config +++ b/tools/generator/Tests/packages.config @@ -3,4 +3,5 @@ + \ No newline at end of file diff --git a/tools/generator/XamarinAndroidCodeGenerator.cs b/tools/generator/XamarinAndroidCodeGenerator.cs index afa47aedc..b69c06149 100644 --- a/tools/generator/XamarinAndroidCodeGenerator.cs +++ b/tools/generator/XamarinAndroidCodeGenerator.cs @@ -118,7 +118,7 @@ void GenerateJNICall (Method method, TextWriter writer, string indent, CodeGener writer.WriteLine ("{0}return {1};", indent, method.RetVal.FromNative (opt, call, true)); } - internal override void WriteMethodBody (Method method, TextWriter writer, string indent, CodeGenerationOptions opt) + internal override void WriteMethodBody (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type) { writer.WriteLine ("{0}if ({1} == IntPtr.Zero)", indent, method.EscapedIdName); writer.WriteLine ("{0}\t{1} = JNIEnv.Get{2}MethodID (class_ref, \"{3}\", \"{4}\");", indent, method.EscapedIdName, method.IsStatic ? "Static" : String.Empty, method.JavaName, method.JniSignature);