diff --git a/src/Java.Interop.Localization/Resources.Designer.cs b/src/Java.Interop.Localization/Resources.Designer.cs index 691ab5745..b8c1dc8fe 100644 --- a/src/Java.Interop.Localization/Resources.Designer.cs +++ b/src/Java.Interop.Localization/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Java.Interop.Localization { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -384,6 +384,15 @@ public static string Generator_BG8A00 { } } + /// + /// Looks up a localized string similar to Invalid namespace transform '{0}'. + /// + public static string Generator_BG8A07 { + get { + return ResourceManager.GetString("Generator_BG8A07", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unknown generic argument constraint type '{0}' for member '{1}'.. /// diff --git a/src/Java.Interop.Localization/Resources.resx b/src/Java.Interop.Localization/Resources.resx index f49506af4..d441772e5 100644 --- a/src/Java.Interop.Localization/Resources.resx +++ b/src/Java.Interop.Localization/Resources.resx @@ -286,6 +286,10 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. {0} - .NET type name diff --git a/src/Java.Interop.Localization/xlf/Resources.cs.xlf b/src/Java.Interop.Localization/xlf/Resources.cs.xlf index c2805a256..573b63ea0 100644 --- a/src/Java.Interop.Localization/xlf/Resources.cs.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.cs.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.de.xlf b/src/Java.Interop.Localization/xlf/Resources.de.xlf index 663a20bca..3bc4cd696 100644 --- a/src/Java.Interop.Localization/xlf/Resources.de.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.de.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.es.xlf b/src/Java.Interop.Localization/xlf/Resources.es.xlf index 8712a71fa..2f3abf14a 100644 --- a/src/Java.Interop.Localization/xlf/Resources.es.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.es.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.fr.xlf b/src/Java.Interop.Localization/xlf/Resources.fr.xlf index 854c1f7cb..dbe446898 100644 --- a/src/Java.Interop.Localization/xlf/Resources.fr.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.fr.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.it.xlf b/src/Java.Interop.Localization/xlf/Resources.it.xlf index 515655eac..2dbdd4112 100644 --- a/src/Java.Interop.Localization/xlf/Resources.it.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.it.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.ja.xlf b/src/Java.Interop.Localization/xlf/Resources.ja.xlf index 8679271f0..c5219f984 100644 --- a/src/Java.Interop.Localization/xlf/Resources.ja.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.ja.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.ko.xlf b/src/Java.Interop.Localization/xlf/Resources.ko.xlf index 818ed84a1..de12f99b9 100644 --- a/src/Java.Interop.Localization/xlf/Resources.ko.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.ko.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.pl.xlf b/src/Java.Interop.Localization/xlf/Resources.pl.xlf index 7a2f58e5e..9ab39c8f1 100644 --- a/src/Java.Interop.Localization/xlf/Resources.pl.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.pl.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.pt-BR.xlf b/src/Java.Interop.Localization/xlf/Resources.pt-BR.xlf index 1d09ad5c0..8df0f8d90 100644 --- a/src/Java.Interop.Localization/xlf/Resources.pt-BR.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.pt-BR.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.ru.xlf b/src/Java.Interop.Localization/xlf/Resources.ru.xlf index e5ea1ca7d..69192c753 100644 --- a/src/Java.Interop.Localization/xlf/Resources.ru.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.ru.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.tr.xlf b/src/Java.Interop.Localization/xlf/Resources.tr.xlf index f6a4eedee..36225df9d 100644 --- a/src/Java.Interop.Localization/xlf/Resources.tr.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.tr.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.zh-Hans.xlf b/src/Java.Interop.Localization/xlf/Resources.zh-Hans.xlf index 429a4b640..21db2e011 100644 --- a/src/Java.Interop.Localization/xlf/Resources.zh-Hans.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.zh-Hans.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Localization/xlf/Resources.zh-Hant.xlf b/src/Java.Interop.Localization/xlf/Resources.zh-Hant.xlf index 1ba4eaa3c..01dbdf7f2 100644 --- a/src/Java.Interop.Localization/xlf/Resources.zh-Hant.xlf +++ b/src/Java.Interop.Localization/xlf/Resources.zh-Hant.xlf @@ -207,6 +207,11 @@ The following terms should not be translated: <package>. {0} - XML transform. (example: '<remove-node path="/api/package[@name='javax.sql']"') The following terms should not be translated: Metadata.xml. + + Invalid namespace transform '{0}' + Invalid namespace transform '{0}' + {0} - XML transform. (example: '<ns-replace source="example" replacement="Example" />') + Unknown generic argument constraint type '{0}' for member '{1}'. Unknown generic argument constraint type '{0}' for member '{1}'. diff --git a/src/Java.Interop.Tools.Generator/Extensions/UtilityExtensions.cs b/src/Java.Interop.Tools.Generator/Extensions/UtilityExtensions.cs index 935885d5d..a20b2833f 100644 --- a/src/Java.Interop.Tools.Generator/Extensions/UtilityExtensions.cs +++ b/src/Java.Interop.Tools.Generator/Extensions/UtilityExtensions.cs @@ -1,5 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text; using System.Xml; using System.Xml.Linq; @@ -41,5 +43,33 @@ public static bool StartsWithAny (this string value, params string [] values) return null; } + + // A case-insensitive Replace doesn't exist in classic .NET Framework. Loosely based on: + // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs + public static string ReplaceOrdinalIgnoreCase (this string source, string oldValue, string newValue) + { + var result = new StringBuilder (); + var pos = 0; + + while (true) { + var index = source.IndexOf (oldValue, pos, StringComparison.OrdinalIgnoreCase); + + // Not found, bail + if (index < 0) + break; + + // Append the unmodified portion of search space + result.Append (source.Substring (pos, index)); + + // Append the replacement + result.Append (newValue); + + pos = index + oldValue.Length; + } + + // Append what remains of the search space, then allocate the new string. + result.Append (source.Substring (pos)); + return result.ToString (); + } } } diff --git a/src/Java.Interop.Tools.Generator/Metadata/FixupXmlDocument.cs b/src/Java.Interop.Tools.Generator/Metadata/FixupXmlDocument.cs index 96b4bcfa4..50e459ebe 100644 --- a/src/Java.Interop.Tools.Generator/Metadata/FixupXmlDocument.cs +++ b/src/Java.Interop.Tools.Generator/Metadata/FixupXmlDocument.cs @@ -4,6 +4,7 @@ using System.Xml.Linq; using Xamarin.Android.Tools; +using System.Collections.Generic; namespace Java.Interop.Tools.Generator { @@ -168,6 +169,18 @@ public void Apply (ApiXmlDocument apiDocument, string apiLevelString, int produc } } + public IList GetNamespaceTransforms () + { + var list = new List (); + + foreach (var xe in FixupDocument.XPathSelectElements ("/metadata/ns-replace")) { + if (NamespaceTransform.TryParse (xe, out var transform)) + list.Add (transform); + } + + return list; + } + bool ShouldSkip (XElement node, int apiLevel, int productVersion) { if (apiLevel > 0) { diff --git a/src/Java.Interop.Tools.Generator/Metadata/NamespaceTransform.cs b/src/Java.Interop.Tools.Generator/Metadata/NamespaceTransform.cs new file mode 100644 index 000000000..4db92796e --- /dev/null +++ b/src/Java.Interop.Tools.Generator/Metadata/NamespaceTransform.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Xml.Linq; +using Xamarin.Android.Tools; + +namespace Java.Interop.Tools.Generator +{ + public class NamespaceTransform + { + public string OldValue { get; } + public string NewValue { get; } + public bool IsStartsWith { get; } + public bool IsEndsWith { get; } + + public NamespaceTransform (string oldValue, string newValue) + { + OldValue = oldValue; + NewValue = newValue; + + if (OldValue.EndsWith (".", StringComparison.Ordinal)) { + IsStartsWith = true; + OldValue = OldValue.Substring (0, OldValue.Length - 1); + } + + if (OldValue.StartsWith (".", StringComparison.Ordinal)) { + IsEndsWith = true; + OldValue = OldValue.Substring (1); + } + } + + public string ApplyInternal (string value) + { + string result; + + while (true) { + result = ApplyInternal (value); + + if (result == value) + return result; + + value = result; + } + } + + public string Apply (string value) + { + // Handle a "starts with" and "ends with" transform + if (IsStartsWith && IsEndsWith) { + if (value.Equals (OldValue, StringComparison.OrdinalIgnoreCase)) + return NewValue; + + // Don't let this fall through + return value; + } + + // Handle a "starts with" transform + if (IsStartsWith) { + if (value.StartsWith (OldValue, StringComparison.OrdinalIgnoreCase)) + return (NewValue + value.Substring (OldValue.Length)).TrimStart ('.'); + + return value; + } + + // Handle an "ends with" transform + if (IsEndsWith) { + if (value.EndsWith (OldValue, StringComparison.OrdinalIgnoreCase)) + return (value.Substring (0, value.Length - OldValue.Length) + NewValue).TrimEnd ('.'); + + return value; + } + + // Handle an "anywhere" transform + var value_tokens = value.Split ('.'); + var match_tokens = OldValue.Split ('.'); + + var results = new List (); + + for (var i = 0; i < value_tokens.Length; i++) { + if (AtMatch (value_tokens, i, match_tokens, 0)) { + if (NewValue.HasValue ()) + results.Add (NewValue); + + i += match_tokens.Length - 1; + } else { + results.Add (value_tokens [i]); + } + } + + return string.Join (".", results); + } + + public static bool TryParse (XElement element, [NotNullWhen (true)] out NamespaceTransform? transform) + { + var source = element.XGetAttribute ("source"); + var replacement = element.XGetAttribute ("replacement"); + + if (!source.HasValue () || replacement is null) { + Report.LogCodedWarning (0, Report.WarningInvalidNamespaceTransform, null, element, element.ToString ()); + transform = null; + return false; + } + + transform = new NamespaceTransform (source, replacement); + return true; + } + + private bool AtMatch (string [] valueTokens, int valueIndex, string [] matchTokens, int matchIndex) + { + if (matchIndex >= matchTokens.Length) + return true; + + if (valueIndex >= valueTokens.Length) + return false; + + if (string.Compare (valueTokens [valueIndex], matchTokens [matchIndex], StringComparison.OrdinalIgnoreCase) == 0) + return AtMatch (valueTokens, valueIndex + 1, matchTokens, matchIndex + 1); + + return false; + } + } +} + diff --git a/src/Java.Interop.Tools.Generator/Utilities/Report.cs b/src/Java.Interop.Tools.Generator/Utilities/Report.cs index aa81371ba..a556a3d93 100644 --- a/src/Java.Interop.Tools.Generator/Utilities/Report.cs +++ b/src/Java.Interop.Tools.Generator/Utilities/Report.cs @@ -67,6 +67,7 @@ public LocalizedMessage (int code, string value) public static LocalizedMessage WarningAttrMatchedNoNodes => new LocalizedMessage (0x8A04, Localization.Resources.Generator_BG8A00); public static LocalizedMessage WarningMoveNodeMatchedNoNodes => new LocalizedMessage (0x8A05, Localization.Resources.Generator_BG8A00); public static LocalizedMessage WarningRemoveAttrMatchedNoNodes => new LocalizedMessage (0x8A06, Localization.Resources.Generator_BG8A00); + public static LocalizedMessage WarningInvalidNamespaceTransform => new LocalizedMessage (0x8A07, Localization.Resources.Generator_BG8A07); public static LocalizedMessage WarningUnknownGenericConstraint => new LocalizedMessage (0x8B00, Localization.Resources.Generator_BG8B00); public static LocalizedMessage WarningBaseInterfaceNotFound => new LocalizedMessage (0x8C00, Localization.Resources.Generator_BG8C00); public static LocalizedMessage WarningBaseInterfaceInvalid => new LocalizedMessage (0x8C01, Localization.Resources.Generator_BG8C01); diff --git a/tests/Java.Interop.Tools.Generator-Tests/Metadata/NamespaceTransformTests.cs b/tests/Java.Interop.Tools.Generator-Tests/Metadata/NamespaceTransformTests.cs new file mode 100644 index 000000000..7da4b2cd0 --- /dev/null +++ b/tests/Java.Interop.Tools.Generator-Tests/Metadata/NamespaceTransformTests.cs @@ -0,0 +1,107 @@ +using System; +using System.Xml.Linq; +using Java.Interop.Tools.Generator; +using NUnit.Framework; + +namespace Java.Interop.Tools.Generator_Tests +{ + public class NamespaceTransformTests + { + [Test] + public void ParseNamespaceTransform () + { + var doc = XDocument.Parse (""); + var result = NamespaceTransform.TryParse (doc.Root.Element ("ns-replace"), out var nt); + + Assert.IsTrue (result); + Assert.IsNotNull (nt); + Assert.AreEqual ("com.example", nt.OldValue); + Assert.AreEqual ("Xamarin", nt.NewValue); + } + + [Test] + public void ParseNamespaceTransform2 () + { + var doc = XDocument.Parse (""); + var result = NamespaceTransform.TryParse (doc.Root.Element ("ns-replace"), out var nt); + + Assert.IsTrue (result); + Assert.IsNotNull (nt); + Assert.AreEqual ("com.example", nt.OldValue); + Assert.AreEqual ("", nt.NewValue); + } + + [Test] + public void ParseInvalidNamespaceTransform () + { + // Logs: warning BG8A07: Invalid namespace transform '' + var doc = XDocument.Parse (""); + var result = NamespaceTransform.TryParse (doc.Root.Element ("ns-replace"), out var nt); + + Assert.IsFalse (result); + Assert.IsNull (nt); + } + + [Test] + public void ParseInvalidNamespaceTransform2 () + { + // Logs: warning BG8A07: Invalid namespace transform '' + var doc = XDocument.Parse (""); + var result = NamespaceTransform.TryParse (doc.Root.Element ("ns-replace"), out var nt); + + Assert.IsFalse (result); + Assert.IsNull (nt); + } + + [Test] + public void GetTransformedNamespace () + { + // Normal and case-insensitive + AssertTransformedNamespace ("Androidx.Core", "AndroidX.Core", new NamespaceTransform ("Androidx", "AndroidX")); + AssertTransformedNamespace ("Androidx.Core", "AndroidX.Core", new NamespaceTransform ("androidx", "AndroidX")); + + // Replace 1 level with 2 + AssertTransformedNamespace ("Androidx.Core", "Xamarin.AndroidX.Core", new NamespaceTransform ("androidx", "Xamarin.AndroidX")); + + // Replace 2 levels with 1 + AssertTransformedNamespace ("Google.Androidx.Core", "AndroidX.Core", new NamespaceTransform ("Google.Androidx", "AndroidX")); + + // Replace 2 levels with 2 + AssertTransformedNamespace ("Google.Androidx.Core", "Xamarin.AndroidX.Core", new NamespaceTransform ("Google.Androidx", "Xamarin.AndroidX")); + + // Removing a match + AssertTransformedNamespace ("Androidx.Core.Test", "Androidx.Test", new NamespaceTransform ("core", "")); + AssertTransformedNamespace ("Androidx.Core.Test", "Androidx.Core", new NamespaceTransform ("test", "")); + AssertTransformedNamespace ("Androidx.Core.Test", "Core.Test", new NamespaceTransform ("androidx", "")); + + // Multiple matches + AssertTransformedNamespace ("Androidx.Androidx.Core", "AndroidX.AndroidX.Core", new NamespaceTransform ("androidx", "AndroidX")); + AssertTransformedNamespace ("google.androidx.Core", "Xamarin.AndroidX.Core", new NamespaceTransform ("androidx", "AndroidX"), new NamespaceTransform ("google", "Xamarin")); + + // Starts with and ends with + AssertTransformedNamespace ("example", "Transformed", new NamespaceTransform (".example.", "Transformed")); + AssertTransformedNamespace ("Androidx.Core", "Transformed", new NamespaceTransform (".Androidx.Core.", "Transformed")); + AssertTransformedNamespace ("Androidx.Core", "Androidx.Core", new NamespaceTransform (".Core.", "Transformed")); + AssertTransformedNamespace ("Androidx.Core", "Androidx.Core", new NamespaceTransform (".AndroidX.", "Transformed")); + + // Starts with + AssertTransformedNamespace ("Androidx.Androidx.Core", "AndroidX2.Androidx.Core", new NamespaceTransform ("AndroidX.", "AndroidX2")); + AssertTransformedNamespace ("Androidx.Androidx.Core", "Androidx.Core", new NamespaceTransform ("AndroidX.", "")); + + // Ends with + AssertTransformedNamespace ("Androidx.Core.Core", "Androidx.Core.Core2", new NamespaceTransform (".core", "Core2")); + AssertTransformedNamespace ("Androidx.Core.Core", "Androidx.Core", new NamespaceTransform (".core", "")); + + // Only matches full level + AssertTransformedNamespace ("AndroidX.Test.Tests", "AndroidX.NewTest.Tests", new NamespaceTransform ("Test", "NewTest")); + } + + void AssertTransformedNamespace (string value, string expected, params NamespaceTransform [] transforms) + { + foreach (var nt in transforms) + value = nt.Apply (value); + + Assert.AreEqual (expected, value); + } + } +} diff --git a/tests/generator-Tests/Integration-Tests/BaseGeneratorTest.cs b/tests/generator-Tests/Integration-Tests/BaseGeneratorTest.cs index d78b9113b..55c051cc6 100644 --- a/tests/generator-Tests/Integration-Tests/BaseGeneratorTest.cs +++ b/tests/generator-Tests/Integration-Tests/BaseGeneratorTest.cs @@ -121,12 +121,12 @@ private byte[] ReadAllBytesIgnoringLineEndings (string path) } } - protected void RunAllTargets (string outputRelativePath, string apiDescriptionFile, string expectedRelativePath, string[] additionalSupportPaths = null, string enumFieldsMapFile = null, string enumMethodMapFile = null) + protected void RunAllTargets (string outputRelativePath, string apiDescriptionFile, string expectedRelativePath, string[] additionalSupportPaths = null, string enumFieldsMapFile = null, string enumMethodMapFile = null, string metadataFile = null) { - Run (CodeGenerationTarget.XamarinAndroid, Path.Combine ("out", outputRelativePath), apiDescriptionFile, Path.Combine ("expected", expectedRelativePath), additionalSupportPaths, enumFieldsMapFile, enumMethodMapFile); - Run (CodeGenerationTarget.XAJavaInterop1, Path.Combine ("out.xaji", outputRelativePath), apiDescriptionFile, Path.Combine ("expected.xaji", expectedRelativePath), additionalSupportPaths, enumFieldsMapFile, enumMethodMapFile); + Run (CodeGenerationTarget.XamarinAndroid, Path.Combine ("out", outputRelativePath), apiDescriptionFile, Path.Combine ("expected", expectedRelativePath), additionalSupportPaths, enumFieldsMapFile, enumMethodMapFile, metadataFile); + Run (CodeGenerationTarget.XAJavaInterop1, Path.Combine ("out.xaji", outputRelativePath), apiDescriptionFile, Path.Combine ("expected.xaji", expectedRelativePath), additionalSupportPaths, enumFieldsMapFile, enumMethodMapFile, metadataFile); if (TryJavaInterop1) { - Run (CodeGenerationTarget.JavaInterop1, Path.Combine ("out.ji", outputRelativePath), apiDescriptionFile, Path.Combine ("expected.ji", expectedRelativePath), additionalSupportPaths, enumFieldsMapFile, enumMethodMapFile); + Run (CodeGenerationTarget.JavaInterop1, Path.Combine ("out.ji", outputRelativePath), apiDescriptionFile, Path.Combine ("expected.ji", expectedRelativePath), additionalSupportPaths, enumFieldsMapFile, enumMethodMapFile, metadataFile); } } @@ -136,7 +136,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, string enumFieldsMapFile = null, string enumMethodMapFile = null) + protected void Run (CodeGenerationTarget target, string outputPath, string apiDescriptionFile, string expectedPath, string[] additionalSupportPaths = null, string enumFieldsMapFile = null, string enumMethodMapFile = null, string metadataFile = null) { Cleanup (outputPath); AdditionalSourceDirectories.Clear (); @@ -151,6 +151,9 @@ protected void Run (CodeGenerationTarget target, string outputPath, string apiDe if (!string.IsNullOrWhiteSpace (enumMethodMapFile)) Options.EnumMethodsMapFile = FullPath (enumMethodMapFile); + if (!string.IsNullOrWhiteSpace (metadataFile)) + Options.FixupFiles.Add (metadataFile); + var adjuster_output = Path.Combine (Path.GetTempPath (), "generator-tests"); Directory.CreateDirectory (adjuster_output); diff --git a/tests/generator-Tests/Integration-Tests/Core_ClassParse.cs b/tests/generator-Tests/Integration-Tests/Core_ClassParse.cs index 2a710c4ee..3f4b9af17 100644 --- a/tests/generator-Tests/Integration-Tests/Core_ClassParse.cs +++ b/tests/generator-Tests/Integration-Tests/Core_ClassParse.cs @@ -14,7 +14,8 @@ public void GeneratedOK () RunAllTargets ( outputRelativePath: "Core_ClassParse", apiDescriptionFile: "expected/Core_ClassParse/api.xml", - expectedRelativePath: "Core_ClassParse"); + expectedRelativePath: "Core_ClassParse", + metadataFile: FullPath ("expected/Core_ClassParse/metadata.xml")); } } } diff --git a/tests/generator-Tests/Unit-Tests/FixupXmlDocumentTests.cs b/tests/generator-Tests/Unit-Tests/FixupXmlDocumentTests.cs index 5ce1c72e1..42e849c1d 100644 --- a/tests/generator-Tests/Unit-Tests/FixupXmlDocumentTests.cs +++ b/tests/generator-Tests/Unit-Tests/FixupXmlDocumentTests.cs @@ -85,6 +85,19 @@ public void RemoveAttribute () Assert.AreEqual ("", api.ApiDocument.ToString (SaveOptions.DisableFormatting).Replace ('\"', '\'')); } + [Test] + public void ParseNamespaceTransforms () + { + var fixup = GetFixupXmlDocument (""); + var transforms = fixup.GetNamespaceTransforms (); + + Assert.AreEqual (2, transforms.Count); + Assert.AreEqual ("androidx", transforms [0].OldValue); + Assert.AreEqual ("AndroidX", transforms [0].NewValue); + Assert.AreEqual ("com.google", transforms [1].OldValue); + Assert.AreEqual ("Xamarin", transforms [1].NewValue); + } + ApiXmlDocument GetXmlApiDocument () { var api = ""; diff --git a/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs b/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs index 9405852d1..184f37c64 100644 --- a/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs +++ b/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Xml.Linq; +using Java.Interop.Tools.Generator; using MonoDroid.Generation; using NUnit.Framework; @@ -302,5 +303,25 @@ public void IgnoreUserObfuscatedTypes () // None of these should be parsed because the user has said they are obfuscated Assert.AreEqual (0, gens.Count); } + + [Test] + public void TransformNamespaces () + { + var xml = XDocument.Parse (@" + + + + + "); + + var opt = new CodeGenerationOptions (); + opt.NamespaceTransforms.Add (new NamespaceTransform ("com.example", "Example")); + opt.NamespaceTransforms.Add (new NamespaceTransform (".test", "Tests")); + + var gens = XmlApiImporter.Parse (xml, opt); + + Assert.AreEqual (1, gens.Count); + Assert.AreEqual ("Example.Tests", gens [0].Namespace); + } } } diff --git a/tests/generator-Tests/expected.ji/Core_ClassParse/Mono.Android.projitems b/tests/generator-Tests/expected.ji/Core_ClassParse/Mono.Android.projitems index e94c83f04..ea168730b 100644 --- a/tests/generator-Tests/expected.ji/Core_ClassParse/Mono.Android.projitems +++ b/tests/generator-Tests/expected.ji/Core_ClassParse/Mono.Android.projitems @@ -7,6 +7,7 @@ + diff --git a/tests/generator-Tests/expected.ji/Core_ClassParse/Xamarin.Google.Composable.MyClass.cs b/tests/generator-Tests/expected.ji/Core_ClassParse/Xamarin.Google.Composable.MyClass.cs new file mode 100644 index 000000000..06d93f40b --- /dev/null +++ b/tests/generator-Tests/expected.ji/Core_ClassParse/Xamarin.Google.Composable.MyClass.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using Java.Interop; + +namespace Xamarin.Google.Composable { + + // Metadata.xml XPath class reference: path="/api/package[@name='com.com.google.compose']/class[@name='MyClass']" + [global::Java.Interop.JniTypeSignature ("com/com/google/compose/MyClass", GenerateJavaPeer=false)] + public partial class MyClass : global::Java.Lang.Object { + static readonly JniPeerMembers _members = new JniPeerMembers ("com/com/google/compose/MyClass", typeof (MyClass)); + + [global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)] + [global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)] + public override global::Java.Interop.JniPeerMembers JniPeerMembers { + get { return _members; } + } + + protected MyClass (ref JniObjectReference reference, JniObjectReferenceOptions options) : base (ref reference, options) + { + } + + } +} diff --git a/tests/generator-Tests/expected.xaji/Core_ClassParse/Java.Interop.__TypeRegistrations.cs b/tests/generator-Tests/expected.xaji/Core_ClassParse/Java.Interop.__TypeRegistrations.cs index 05fa69b48..2e45df39d 100644 --- a/tests/generator-Tests/expected.xaji/Core_ClassParse/Java.Interop.__TypeRegistrations.cs +++ b/tests/generator-Tests/expected.xaji/Core_ClassParse/Java.Interop.__TypeRegistrations.cs @@ -14,9 +14,11 @@ public static void RegisterPackages () #endif // def MONODROID_TIMING Java.Interop.TypeManager.RegisterPackages ( new string[]{ + "com/com/google/compose", "xamarin/test/invalidnames", }, new Converter[]{ + lookup_com_com_google_compose_package, lookup_xamarin_test_invalidnames_package, }); #if MONODROID_TIMING @@ -36,6 +38,18 @@ static Type Lookup (string[] mappings, string javaType) return Type.GetType (managedType); } + static string[] package_com_com_google_compose_mappings; + static Type lookup_com_com_google_compose_package (string klass) + { + if (package_com_com_google_compose_mappings == null) { + package_com_com_google_compose_mappings = new string[]{ + "com/com/google/compose/MyClass:Xamarin.Google.Composable.MyClass", + }; + } + + return Lookup (package_com_com_google_compose_mappings, klass); + } + static string[] package_xamarin_test_invalidnames_mappings; static Type lookup_xamarin_test_invalidnames_package (string klass) { diff --git a/tests/generator-Tests/expected.xaji/Core_ClassParse/Mono.Android.projitems b/tests/generator-Tests/expected.xaji/Core_ClassParse/Mono.Android.projitems index c6fa7bb59..5a1d5a97e 100644 --- a/tests/generator-Tests/expected.xaji/Core_ClassParse/Mono.Android.projitems +++ b/tests/generator-Tests/expected.xaji/Core_ClassParse/Mono.Android.projitems @@ -8,6 +8,7 @@ + diff --git a/tests/generator-Tests/expected.xaji/Core_ClassParse/Xamarin.Google.Composable.MyClass.cs b/tests/generator-Tests/expected.xaji/Core_ClassParse/Xamarin.Google.Composable.MyClass.cs new file mode 100644 index 000000000..a1288ce7e --- /dev/null +++ b/tests/generator-Tests/expected.xaji/Core_ClassParse/Xamarin.Google.Composable.MyClass.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using Android.Runtime; +using Java.Interop; + +namespace Xamarin.Google.Composable { + + // Metadata.xml XPath class reference: path="/api/package[@name='com.com.google.compose']/class[@name='MyClass']" + [global::Android.Runtime.Register ("com/com/google/compose/MyClass", DoNotGenerateAcw=true)] + public partial class MyClass : global::Java.Lang.Object { + static readonly JniPeerMembers _members = new XAPeerMembers ("com/com/google/compose/MyClass", typeof (MyClass)); + + internal static new IntPtr class_ref { + get { return _members.JniPeerType.PeerReference.Handle; } + } + + [global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)] + [global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)] + public override global::Java.Interop.JniPeerMembers JniPeerMembers { + get { return _members; } + } + + [global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)] + [global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)] + protected override IntPtr ThresholdClass { + get { return _members.JniPeerType.PeerReference.Handle; } + } + + [global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)] + [global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)] + protected override global::System.Type ThresholdType { + get { return _members.ManagedPeerType; } + } + + protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) + { + } + + } +} diff --git a/tests/generator-Tests/expected.xaji/Core_ClassParse/__NamespaceMapping__.cs b/tests/generator-Tests/expected.xaji/Core_ClassParse/__NamespaceMapping__.cs index 966d718be..4ebe1a2cc 100644 --- a/tests/generator-Tests/expected.xaji/Core_ClassParse/__NamespaceMapping__.cs +++ b/tests/generator-Tests/expected.xaji/Core_ClassParse/__NamespaceMapping__.cs @@ -2,6 +2,7 @@ [assembly:global::Android.Runtime.NamespaceMapping (Java = "java.lang", Managed="Java.Lang")] [assembly:global::Android.Runtime.NamespaceMapping (Java = "xamarin.test.invalidnames", Managed="Xamarin.Test.Invalidnames")] +[assembly:global::Android.Runtime.NamespaceMapping (Java = "com.com.google.compose", Managed="Xamarin.Google.Composable")] #if !NET namespace System.Runtime.Versioning { diff --git a/tests/generator-Tests/expected/Core_ClassParse/Xamarin.Google.Composable.MyClass.cs b/tests/generator-Tests/expected/Core_ClassParse/Xamarin.Google.Composable.MyClass.cs new file mode 100644 index 000000000..998184788 --- /dev/null +++ b/tests/generator-Tests/expected/Core_ClassParse/Xamarin.Google.Composable.MyClass.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using Android.Runtime; + +namespace Xamarin.Google.Composable { + + // Metadata.xml XPath class reference: path="/api/package[@name='com.com.google.compose']/class[@name='MyClass']" + [global::Android.Runtime.Register ("com/com/google/compose/MyClass", DoNotGenerateAcw=true)] + public partial class MyClass : global::Java.Lang.Object { + + internal static new IntPtr java_class_handle; + internal static new IntPtr class_ref { + get { + return JNIEnv.FindClass ("com/com/google/compose/MyClass", ref java_class_handle); + } + } + + protected override IntPtr ThresholdClass { + get { return class_ref; } + } + + protected override global::System.Type ThresholdType { + get { return typeof (MyClass); } + } + + protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) {} + + } +} diff --git a/tests/generator-Tests/expected/Core_ClassParse/api.xml b/tests/generator-Tests/expected/Core_ClassParse/api.xml index 41015a890..6289ee402 100644 --- a/tests/generator-Tests/expected/Core_ClassParse/api.xml +++ b/tests/generator-Tests/expected/Core_ClassParse/api.xml @@ -22,5 +22,8 @@ + + + diff --git a/tests/generator-Tests/expected/Core_ClassParse/metadata.xml b/tests/generator-Tests/expected/Core_ClassParse/metadata.xml new file mode 100644 index 000000000..fa358dfa5 --- /dev/null +++ b/tests/generator-Tests/expected/Core_ClassParse/metadata.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tools/generator/CodeGenerationOptions.cs b/tools/generator/CodeGenerationOptions.cs index ced29d57b..cf66e1138 100644 --- a/tools/generator/CodeGenerationOptions.cs +++ b/tools/generator/CodeGenerationOptions.cs @@ -80,6 +80,8 @@ public bool BuildingCoreAssembly { public string NullForgivingOperator => SupportNullableReferenceTypes ? "!" : string.Empty; + public List NamespaceTransforms { get; } = new List (); + public string GetTypeReferenceName (Field field) { var name = GetOutputName (field.Symbol.FullName); @@ -282,6 +284,17 @@ public string GetFileName (string fullName) return s; } } + + public string GetTransformedNamespace (string value) + { + if (!value.HasValue () || !NamespaceTransforms.Any ()) + return value; + + foreach (var nt in NamespaceTransforms) + value = nt.Apply (value); + + return value; + } } } diff --git a/tools/generator/CodeGenerator.cs b/tools/generator/CodeGenerator.cs index d246a952e..c7271d261 100644 --- a/tools/generator/CodeGenerator.cs +++ b/tools/generator/CodeGenerator.cs @@ -176,8 +176,12 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve return; // Apply metadata fixups - foreach (var fixup in fixups) - api.ApplyFixupFile (fixup); + foreach (var fixup in fixups) { + if (FixupXmlDocument.Load (fixup) is FixupXmlDocument f) { + api.ApplyFixupFile (f); + opt.NamespaceTransforms.AddRange (f.GetNamespaceTransforms ()); + } + } api.ApiDocument.Save (apiXmlFile + ".fixed"); diff --git a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs index 630a923e9..3c80a0c25 100644 --- a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs +++ b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs @@ -105,7 +105,7 @@ public static List ParsePackage (XElement ns, CodeGenerationOptions opt public static ClassGen CreateClass (XElement pkg, XElement elem, CodeGenerationOptions options) { - var klass = new ClassGen (CreateGenBaseSupport (pkg, elem, false)) { + var klass = new ClassGen (CreateGenBaseSupport (pkg, elem, options, false)) { BaseType = elem.XGetAttribute ("extends"), FromXml = true, IsAbstract = elem.XGetAttribute ("abstract") == "true", @@ -234,7 +234,7 @@ public static Field CreateField (GenBase declaringType, XElement elem, CodeGener return field; } - public static GenBaseSupport CreateGenBaseSupport (XElement pkg, XElement elem, bool isInterface) + public static GenBaseSupport CreateGenBaseSupport (XElement pkg, XElement elem, CodeGenerationOptions opt, bool isInterface) { var support = new GenBaseSupport { IsAcw = true, @@ -258,7 +258,7 @@ public static GenBaseSupport CreateGenBaseSupport (XElement pkg, XElement elem, if (pkg.Attribute ("managedName") != null) support.Namespace = pkg.XGetAttribute ("managedName"); else - support.Namespace = StringRocks.PackageToPascalCase (support.PackageName); + support.Namespace = opt.GetTransformedNamespace (StringRocks.PackageToPascalCase (support.PackageName)); var tpn = elem.Element ("typeParameters"); @@ -302,7 +302,7 @@ public static GenBaseSupport CreateGenBaseSupport (XElement pkg, XElement elem, public static InterfaceGen CreateInterface (XElement pkg, XElement elem, CodeGenerationOptions options) { - var iface = new InterfaceGen (CreateGenBaseSupport (pkg, elem, true)) { + var iface = new InterfaceGen (CreateGenBaseSupport (pkg, elem, options, true)) { ArgsType = elem.XGetAttribute ("argsType"), HasManagedName = elem.Attribute ("managedName") != null, NoAlternatives = elem.XGetAttribute ("no-alternatives") == "true",