Skip to content

Commit 09083d9

Browse files
gugavarojonpryor
authored andcommitted
[generator] Support empty package names (#520)
Fixes: #519 Java allows classes to be "global", and not reside in a package: // Java // note: no `package` statement public class Example { public void test() {} } `class-parse` will bind this as: <api api-source="class-parse"> <package name="" jni-name=""> <class extends="java.lang.Object" name="Example" jni-signature="LExample;" ...> <constructor ... jni-signature="()V" /> </class> </package> </api> `generator` consumes this XML, and generates invalid C# code: // C# namespace { // Metadata.xml XPath class reference: path="/api/package[@name='']/class[@name='Animal']" [global::Android.Runtime.Register ("/Example", DoNotGenerateAcw=true)] public partial class Example : global::Java.Lang.Object { } } This will fail to build with a [CS1001][0]. Even if it did build, it probably also has a `JNIEnv.FindClass("/Example")`, which would fail at runtime. The fix is to validate that classes and interfaces belong to a package or namespace. When no namespace exists, we should omit namespace info: // Fixed C#: [global::Android.Runtime.Register ("Example", DoNotGenerateAcw=true)] public partial class Example : global::Java.Lang.Object { } [0]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs1001
1 parent af3dc77 commit 09083d9

File tree

10 files changed

+235
-21
lines changed

10 files changed

+235
-21
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -522,8 +522,13 @@ public void WriteInterfaceDeclaration (InterfaceGen @interface, string indent)
522522
if (@interface.IsDeprecated)
523523
writer.WriteLine ("{0}[ObsoleteAttribute (@\"{1}\")]", indent, @interface.DeprecatedComment);
524524

525-
if (!@interface.IsConstSugar)
526-
writer.WriteLine ("{0}[Register (\"{1}\", \"\", \"{2}\"{3})]", indent, @interface.RawJniName, @interface.Namespace + "." + @interface.FullName.Substring (@interface.Namespace.Length + 1).Replace ('.', '/') + "Invoker", @interface.AdditionalAttributeString ());
525+
if (!@interface.IsConstSugar) {
526+
var signature = string.IsNullOrWhiteSpace (@interface.Namespace)
527+
? @interface.FullName.Replace ('.', '/')
528+
: @interface.Namespace + "." + @interface.FullName.Substring (@interface.Namespace.Length + 1).Replace ('.', '/');
529+
530+
writer.WriteLine ("{0}[Register (\"{1}\", \"\", \"{2}\"{3})]", indent, @interface.RawJniName, signature + "Invoker", @interface.AdditionalAttributeString ());
531+
}
527532

528533
if (@interface.TypeParameters != null && @interface.TypeParameters.Any ())
529534
writer.WriteLine ("{0}{1}", indent, @interface.TypeParameters.ToGeneratedAttributeString ());

tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public static GenBaseSupport CreateGenBaseSupport (XElement pkg, XElement elem,
131131
PackageName = pkg.XGetAttribute ("name"),
132132
Visibility = elem.XGetAttribute ("visibility")
133133
};
134-
134+
135135
if (support.IsDeprecated) {
136136
support.DeprecatedComment = elem.XGetAttribute ("deprecated");
137137

@@ -165,7 +165,7 @@ public static GenBaseSupport CreateGenBaseSupport (XElement pkg, XElement elem,
165165

166166
if (elem.Attribute ("managedName") != null) {
167167
support.Name = elem.XGetAttribute ("managedName");
168-
support.FullName = string.Format ("{0}.{1}", support.Namespace, support.Name);
168+
support.FullName = string.IsNullOrWhiteSpace(support.Namespace) ? support.Name : $"{support.Namespace}.{support.Name}";
169169
int idx = support.Name.LastIndexOf ('.');
170170
support.Name = idx > 0 ? support.Name.Substring (idx + 1) : support.Name;
171171
raw_name = support.Name;
@@ -177,7 +177,9 @@ public static GenBaseSupport CreateGenBaseSupport (XElement pkg, XElement elem,
177177
raw_name = support.Name;
178178
support.TypeNamePrefix = isInterface ? IsPrefixableName (raw_name) ? "I" : string.Empty : string.Empty;
179179
support.Name = EnsureValidIdentifer (support.TypeNamePrefix + raw_name);
180-
support.FullName = string.Format ("{0}.{1}{2}", support.Namespace, idx > 0 ? StringRocks.TypeToPascalCase (support.JavaSimpleName.Substring (0, idx + 1)) : string.Empty, support.Name);
180+
var supportNamespace = string.IsNullOrWhiteSpace (support.Namespace) ? string.Empty : $"{support.Namespace}.";
181+
var supportSimpleName = idx > 0 ? StringRocks.TypeToPascalCase (support.JavaSimpleName.Substring (0, idx + 1)) : string.Empty;
182+
support.FullName = string.Format ("{0}{1}{2}", supportNamespace, supportSimpleName, support.Name);
181183
}
182184

183185
support.IsObfuscated = IsObfuscatedName (pkg.Elements ().Count (), support.JavaSimpleName) && elem.XGetAttribute ("obfuscated") != "false";

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,18 @@ public override void Generate (CodeGenerationOptions opt, GenerationInfo gen_inf
137137
sw.WriteLine ("using Java.Interop;");
138138
}
139139
sw.WriteLine ();
140-
sw.WriteLine ("namespace {0} {{", Namespace);
141-
sw.WriteLine ();
140+
var hasNamespace = !string.IsNullOrWhiteSpace (Namespace);
141+
if (hasNamespace) {
142+
sw.WriteLine ("namespace {0} {{", Namespace);
143+
sw.WriteLine ();
144+
}
142145

143146
var generator = opt.CreateCodeGenerator (sw);
144-
generator.WriteClass (this, "\t", gen_info);
147+
generator.WriteClass (this, hasNamespace ? "\t" : string.Empty, gen_info);
145148

146-
sw.WriteLine ("}");
149+
if (hasNamespace) {
150+
sw.WriteLine ("}");
151+
}
147152
}
148153
}
149154

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,9 @@ IEnumerable<GenBase> Ancestors ()
170170

171171
// not: not currently assembly qualified, but it uses needed
172172
// Type.GetType() conventions such as '/' for nested types.
173-
public string AssemblyQualifiedName =>
174-
$"{Namespace}.{FullName.Substring (Namespace.Length + 1).Replace ('.', '/')}";
173+
public string AssemblyQualifiedName => string.IsNullOrWhiteSpace (Namespace)
174+
? $"{FullName.Replace ('.', '/')}"
175+
: $"{Namespace}." + $"{FullName.Substring (Namespace.Length + 1).Replace ('.', '/')}";
175176

176177
public int ApiAvailableSince { get; set; }
177178

@@ -590,7 +591,9 @@ static bool IsTypeCommensurate (CodeGenerationOptions opt, ISymbol sym)
590591

591592
public bool IsValid { get; set; } = true;
592593

593-
public string JavaName => $"{PackageName}.{JavaSimpleName}";
594+
public string JavaName => string.IsNullOrWhiteSpace (PackageName)
595+
? JavaSimpleName
596+
: $"{PackageName}.{JavaSimpleName}";
594597

595598
public string JavaSimpleName => support.JavaSimpleName;
596599

@@ -718,7 +721,7 @@ public string [] PreCallback (CodeGenerationOptions opt, string var_name, bool o
718721

719722
public string [] PostCallback (CodeGenerationOptions opt, string var_name) => new string [] { };
720723

721-
public string RawJniName => PackageName.Replace ('.', '/') + "/" + JavaSimpleName.Replace ('.', '$');
724+
public string RawJniName => (string.IsNullOrWhiteSpace (PackageName) ? string.Empty : PackageName.Replace ('.', '/') + "/") + JavaSimpleName.Replace ('.', '$');
722725

723726
public string RawVisibility => support.Visibility;
724727

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public override string FromNative (CodeGenerationOptions opt, string varname, bo
4646
public override void FixupAccessModifiers (CodeGenerationOptions opt)
4747
{
4848
if (!IsAnnotation) {
49-
foreach (var implementedInterface in ImplementedInterfaces) {
49+
foreach (var implementedInterface in ImplementedInterfaces) {
5050
if (string.IsNullOrEmpty (implementedInterface)) {
5151
System.Diagnostics.Debug.Assert (false, "BUGBUG - We should never have an empty or null string added on the implemented interface list.");
5252
continue;
@@ -62,7 +62,7 @@ public override void FixupAccessModifiers (CodeGenerationOptions opt)
6262
}
6363
}
6464
}
65-
65+
6666

6767
base.FixupAccessModifiers (opt);
6868
}
@@ -77,13 +77,18 @@ public override void Generate (CodeGenerationOptions opt, GenerationInfo gen_inf
7777
sw.WriteLine ("using Java.Interop;");
7878
}
7979
sw.WriteLine ();
80-
sw.WriteLine ("namespace {0} {{", Namespace);
81-
sw.WriteLine ();
80+
var hasNamespace = !string.IsNullOrWhiteSpace (Namespace);
81+
if (hasNamespace) {
82+
sw.WriteLine ("namespace {0} {{", Namespace);
83+
sw.WriteLine ();
84+
}
8285

8386
var generator = opt.CreateCodeGenerator (sw);
84-
generator.WriteInterface (this, "\t", gen_info);
87+
generator.WriteInterface (this, hasNamespace ? "\t" : string.Empty, gen_info);
8588

86-
sw.WriteLine ("}");
89+
if (hasNamespace) {
90+
sw.WriteLine ("}");
91+
}
8792
}
8893

8994
GenerateAnnotationAttribute (opt, gen_info);

tools/generator/Tests/expected.ji/TestInterface/Mono.Android.projitems

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
</PropertyGroup>
66
<!-- Classes -->
77
<ItemGroup>
8+
<Compile Include="$(MSBuildThisFileDirectory)ClassWithoutNamespace.cs" />
9+
<Compile Include="$(MSBuildThisFileDirectory)IInterfaceWithoutNamespace.cs" />
810
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop.__TypeRegistrations.cs" />
911
<Compile Include="$(MSBuildThisFileDirectory)Java.Lang.Object.cs" />
1012
<Compile Include="$(MSBuildThisFileDirectory)Java.Lang.String.cs" />
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
[assembly:global::Android.Runtime.NamespaceMapping (Java = "java.lang", Managed="Java.Lang")]
22
[assembly:global::Android.Runtime.NamespaceMapping (Java = "test.me", Managed="Test.ME")]
3+
[assembly:global::Android.Runtime.NamespaceMapping (Java = "", Managed="")]
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Android.Runtime;
4+
5+
// Metadata.xml XPath class reference: path="/api/package[@name='']/class[@name='ClassWithoutNamespace']"
6+
[global::Android.Runtime.Register ("ClassWithoutNamespace", DoNotGenerateAcw=true)]
7+
public abstract partial class ClassWithoutNamespace : global::Java.Lang.Object, IInterfaceWithoutNamespace {
8+
9+
internal static new IntPtr java_class_handle;
10+
internal static new IntPtr class_ref {
11+
get {
12+
return JNIEnv.FindClass ("ClassWithoutNamespace", ref java_class_handle);
13+
}
14+
}
15+
16+
protected override IntPtr ThresholdClass {
17+
get { return class_ref; }
18+
}
19+
20+
protected override global::System.Type ThresholdType {
21+
get { return typeof (ClassWithoutNamespace); }
22+
}
23+
24+
protected ClassWithoutNamespace (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) {}
25+
26+
static IntPtr id_ctor;
27+
// Metadata.xml XPath constructor reference: path="/api/package[@name='']/class[@name='ClassWithoutNamespace']/constructor[@name='ClassWithoutNamespace' and count(parameter)=0]"
28+
[Register (".ctor", "()V", "")]
29+
public unsafe ClassWithoutNamespace ()
30+
: base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
31+
{
32+
if (((global::Java.Lang.Object) this).Handle != IntPtr.Zero)
33+
return;
34+
35+
try {
36+
if (((object) this).GetType () != typeof (ClassWithoutNamespace)) {
37+
SetHandle (
38+
global::Android.Runtime.JNIEnv.StartCreateInstance (((object) this).GetType (), "()V"),
39+
JniHandleOwnership.TransferLocalRef);
40+
global::Android.Runtime.JNIEnv.FinishCreateInstance (((global::Java.Lang.Object) this).Handle, "()V");
41+
return;
42+
}
43+
44+
if (id_ctor == IntPtr.Zero)
45+
id_ctor = JNIEnv.GetMethodID (class_ref, "<init>", "()V");
46+
SetHandle (
47+
global::Android.Runtime.JNIEnv.StartCreateInstance (class_ref, id_ctor),
48+
JniHandleOwnership.TransferLocalRef);
49+
JNIEnv.FinishCreateInstance (((global::Java.Lang.Object) this).Handle, class_ref, id_ctor);
50+
} finally {
51+
}
52+
}
53+
54+
static Delegate cb_Foo;
55+
#pragma warning disable 0169
56+
static Delegate GetFooHandler ()
57+
{
58+
if (cb_Foo == null)
59+
cb_Foo = JNINativeWrapper.CreateDelegate ((Action<IntPtr, IntPtr>) n_Foo);
60+
return cb_Foo;
61+
}
62+
63+
static void n_Foo (IntPtr jnienv, IntPtr native__this)
64+
{
65+
ClassWithoutNamespace __this = global::Java.Lang.Object.GetObject<ClassWithoutNamespace> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
66+
__this.Foo ();
67+
}
68+
#pragma warning restore 0169
69+
70+
// Metadata.xml XPath method reference: path="/api/package[@name='']/interface[@name='InterfaceWithoutNamespace']/method[@name='Foo' and count(parameter)=0]"
71+
[Register ("Foo", "()V", "GetFooHandler")]
72+
public abstract void Foo ();
73+
74+
}
75+
76+
[global::Android.Runtime.Register ("ClassWithoutNamespace", DoNotGenerateAcw=true)]
77+
internal partial class ClassWithoutNamespaceInvoker : ClassWithoutNamespace {
78+
79+
public ClassWithoutNamespaceInvoker (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) {}
80+
81+
protected override global::System.Type ThresholdType {
82+
get { return typeof (ClassWithoutNamespaceInvoker); }
83+
}
84+
85+
static IntPtr id_Foo;
86+
// Metadata.xml XPath method reference: path="/api/package[@name='']/interface[@name='InterfaceWithoutNamespace']/method[@name='Foo' and count(parameter)=0]"
87+
[Register ("Foo", "()V", "GetFooHandler")]
88+
public override unsafe void Foo ()
89+
{
90+
if (id_Foo == IntPtr.Zero)
91+
id_Foo = JNIEnv.GetMethodID (class_ref, "Foo", "()V");
92+
try {
93+
JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_Foo);
94+
} finally {
95+
}
96+
}
97+
98+
}
99+
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Android.Runtime;
4+
5+
// Metadata.xml XPath interface reference: path="/api/package[@name='']/interface[@name='InterfaceWithoutNamespace']"
6+
[Register ("InterfaceWithoutNamespace", "", "IInterfaceWithoutNamespaceInvoker")]
7+
public partial interface IInterfaceWithoutNamespace : IJavaObject {
8+
9+
// Metadata.xml XPath method reference: path="/api/package[@name='']/interface[@name='InterfaceWithoutNamespace']/method[@name='Foo' and count(parameter)=0]"
10+
[Register ("Foo", "()V", "GetFooHandler:IInterfaceWithoutNamespaceInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")]
11+
void Foo ();
12+
13+
}
14+
15+
[global::Android.Runtime.Register ("InterfaceWithoutNamespace", DoNotGenerateAcw=true)]
16+
internal partial class IInterfaceWithoutNamespaceInvoker : global::Java.Lang.Object, IInterfaceWithoutNamespace {
17+
18+
static IntPtr java_class_ref = JNIEnv.FindClass ("InterfaceWithoutNamespace");
19+
20+
protected override IntPtr ThresholdClass {
21+
get { return class_ref; }
22+
}
23+
24+
protected override global::System.Type ThresholdType {
25+
get { return typeof (IInterfaceWithoutNamespaceInvoker); }
26+
}
27+
28+
new IntPtr class_ref;
29+
30+
public static IInterfaceWithoutNamespace GetObject (IntPtr handle, JniHandleOwnership transfer)
31+
{
32+
return global::Java.Lang.Object.GetObject<IInterfaceWithoutNamespace> (handle, transfer);
33+
}
34+
35+
static IntPtr Validate (IntPtr handle)
36+
{
37+
if (!JNIEnv.IsInstanceOf (handle, java_class_ref))
38+
throw new InvalidCastException (string.Format ("Unable to convert instance of type '{0}' to type '{1}'.",
39+
JNIEnv.GetClassNameFromInstance (handle), "InterfaceWithoutNamespace"));
40+
return handle;
41+
}
42+
43+
protected override void Dispose (bool disposing)
44+
{
45+
if (this.class_ref != IntPtr.Zero)
46+
JNIEnv.DeleteGlobalRef (this.class_ref);
47+
this.class_ref = IntPtr.Zero;
48+
base.Dispose (disposing);
49+
}
50+
51+
public IInterfaceWithoutNamespaceInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
52+
{
53+
IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
54+
this.class_ref = JNIEnv.NewGlobalRef (local_ref);
55+
JNIEnv.DeleteLocalRef (local_ref);
56+
}
57+
58+
static Delegate cb_Foo;
59+
#pragma warning disable 0169
60+
static Delegate GetFooHandler ()
61+
{
62+
if (cb_Foo == null)
63+
cb_Foo = JNINativeWrapper.CreateDelegate ((Action<IntPtr, IntPtr>) n_Foo);
64+
return cb_Foo;
65+
}
66+
67+
static void n_Foo (IntPtr jnienv, IntPtr native__this)
68+
{
69+
IInterfaceWithoutNamespace __this = global::Java.Lang.Object.GetObject<IInterfaceWithoutNamespace> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
70+
__this.Foo ();
71+
}
72+
#pragma warning restore 0169
73+
74+
IntPtr id_Foo;
75+
public unsafe void Foo ()
76+
{
77+
if (id_Foo == IntPtr.Zero)
78+
id_Foo = JNIEnv.GetMethodID (class_ref, "Foo", "()V");
79+
JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_Foo);
80+
}
81+
82+
}
83+

tools/generator/Tests/expected/TestInterface/TestInterface.xml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
<implements name="test.me.TestInterface" name-generic-aware="test.me.TestInterface" />
5151
<constructor deprecated="not deprecated" final="false" name="TestInterfaceImplementation" static="false" visibility="public" />
5252
</class>
53-
<!--
53+
<!--
5454
public interface GenericInterface<T> {
5555
void SetObject(T item);
5656
}
@@ -87,7 +87,7 @@
8787
<parameter name="value" type="java.lang.String[]" />
8888
</method>
8989
</class>
90-
<!--
90+
<!--
9191
public interface GenericPropertyInterface<T> {
9292
T getObject();
9393
void setObject(T value);
@@ -138,4 +138,13 @@
138138
</method>
139139
</class>
140140
</package>
141+
<package name="">
142+
<interface abstract="false" deprecated="not deprecated" extends="java.lang.Object" extends-generic-aware="java.lang.Object" final="false" name="InterfaceWithoutNamespace" static="false" visibility="public">
143+
<method abstract="true" deprecated="not deprecated" final="false" name="Foo" native="false" return="void" static="false" synchronized="false" visibility="public" />
144+
</interface>
145+
<class abstract="true" deprecated="not deprecated" extends="java.lang.Object" extends-generic-aware="java.lang.Object" final="false" name="ClassWithoutNamespace" static="false" visibility="public">
146+
<implements name="InterfaceWithoutNamespace" name-generic-aware="InterfaceWithoutNamespace" />
147+
<constructor deprecated="not deprecated" final="false" name="ClassWithoutNamespace" static="false" visibility="public" />
148+
</class>
149+
</package>
141150
</api>

0 commit comments

Comments
 (0)