Skip to content

Commit e56c65d

Browse files
committed
[generator] Cache static final field values.
1 parent 51b784a commit e56c65d

File tree

11 files changed

+242
-10
lines changed

11 files changed

+242
-10
lines changed

Diff for: src/Xamarin.SourceWriter/Models/TypeReferenceWriter.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,17 @@ public TypeReferenceWriter (string ns, string name)
4040

4141
public virtual void WriteTypeReference (CodeWriter writer)
4242
{
43-
if (Namespace.HasValue ())
44-
writer.Write ($"{Namespace}.{Name}{NullableOperator} ");
45-
else
46-
writer.Write ($"{Name}{NullableOperator} ");
43+
writer.Write ($"{ToString ()} ");
4744
}
4845

4946
string NullableOperator => Nullable ? "?" : string.Empty;
47+
48+
public override string ToString ()
49+
{
50+
if (Namespace.HasValue ())
51+
return $"{Namespace}.{Name}{NullableOperator}";
52+
53+
return $"{Name}{NullableOperator}";
54+
}
5055
}
5156
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
2+
[global::Java.Interop.JniTypeSignature ("java/code/MyClass", GenerateJavaPeer=false)]
3+
public partial class MyClass {
4+
private static java.code.Example? _field_cache;
5+
6+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
7+
public static java.code.Example? field {
8+
get {
9+
if (_field_cache != null) return (java.code.Example)_field_cache;
10+
11+
const string __id = "field.Ljava/code/Example;";
12+
13+
var __v = _members.StaticFields.GetObjectValue (__id);
14+
return (java.code.Example?)(_field_cache = global::Java.Interop.JniEnvironment.Runtime.ValueManager.GetValue<java.code.Example? >(ref __v, JniObjectReferenceOptions.Copy));
15+
}
16+
}
17+
18+
static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/MyClass", typeof (MyClass));
19+
20+
protected MyClass (ref JniObjectReference reference, JniObjectReferenceOptions options) : base (ref reference, options)
21+
{
22+
}
23+
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
2+
[global::Java.Interop.JniTypeSignature ("java/code/MyClass", GenerateJavaPeer=false)]
3+
public partial class MyClass {
4+
private static int? _field_cache;
5+
6+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
7+
public static int field {
8+
get {
9+
if (_field_cache != null) return (int)_field_cache;
10+
11+
const string __id = "field.I";
12+
13+
var __v = _members.StaticFields.GetInt32Value (__id);
14+
return (int)(_field_cache = __v);
15+
}
16+
}
17+
18+
static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/MyClass", typeof (MyClass));
19+
20+
protected MyClass (ref JniObjectReference reference, JniObjectReferenceOptions options) : base (ref reference, options)
21+
{
22+
}
23+
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
2+
[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)]
3+
public partial class MyClass {
4+
private static java.code.Example? _field_cache;
5+
6+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
7+
[Register ("field")]
8+
public static java.code.Example? field {
9+
get {
10+
if (_field_cache != null) return (java.code.Example)_field_cache;
11+
12+
const string __id = "field.Ljava/code/Example;";
13+
14+
var __v = _members.StaticFields.GetObjectValue (__id);
15+
return (java.code.Example?)(_field_cache = global::Java.Lang.Object.GetObject<java.code.Example> (__v.Handle, JniHandleOwnership.TransferLocalRef));
16+
}
17+
}
18+
19+
static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/MyClass", typeof (MyClass));
20+
21+
internal static IntPtr class_ref {
22+
get { return _members.JniPeerType.PeerReference.Handle; }
23+
}
24+
25+
protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer)
26+
{
27+
}
28+
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
2+
[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)]
3+
public partial class MyClass {
4+
private static int? _field_cache;
5+
6+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
7+
[Register ("field")]
8+
public static int field {
9+
get {
10+
if (_field_cache != null) return (int)_field_cache;
11+
12+
const string __id = "field.I";
13+
14+
var __v = _members.StaticFields.GetInt32Value (__id);
15+
return (int)(_field_cache = __v);
16+
}
17+
}
18+
19+
static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/MyClass", typeof (MyClass));
20+
21+
internal static IntPtr class_ref {
22+
get { return _members.JniPeerType.PeerReference.Handle; }
23+
}
24+
25+
protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer)
26+
{
27+
}
28+
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
2+
[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)]
3+
public partial class MyClass {
4+
5+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
6+
[Register ("field")]
7+
public static java.code.Example field {
8+
get {
9+
const string __id = "field.Ljava/code/Example;";
10+
11+
var __v = _members.StaticFields.GetObjectValue (__id);
12+
return global::Java.Lang.Object.GetObject<java.code.Example> (__v.Handle, JniHandleOwnership.TransferLocalRef);
13+
}
14+
}
15+
16+
static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/MyClass", typeof (MyClass));
17+
18+
internal static IntPtr class_ref {
19+
get { return _members.JniPeerType.PeerReference.Handle; }
20+
}
21+
22+
protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer)
23+
{
24+
}
25+
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']"
2+
[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)]
3+
public partial class MyClass {
4+
5+
// Metadata.xml XPath field reference: path="/api/package[@name='java.code']/class[@name='MyClass']/field[@name='field']"
6+
[Register ("field")]
7+
public static int field {
8+
get {
9+
const string __id = "field.I";
10+
11+
var __v = _members.StaticFields.GetInt32Value (__id);
12+
return __v;
13+
}
14+
}
15+
16+
static readonly JniPeerMembers _members = new XAPeerMembers ("java/code/MyClass", typeof (MyClass));
17+
18+
internal static IntPtr class_ref {
19+
get { return _members.JniPeerType.PeerReference.Handle; }
20+
}
21+
22+
protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer)
23+
{
24+
}
25+
26+
}

Diff for: tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs

+42
Original file line numberDiff line numberDiff line change
@@ -1644,5 +1644,47 @@ public void WriteBoundMethodAbstractDeclarationWithGenericReturn ()
16441644
// Ignore nullable operator so this test works on all generators ;)
16451645
Assert.AreEqual (expected.NormalizeLineEndings (), writer.ToString ().NormalizeLineEndings ().Replace ("?", ""));
16461646
}
1647+
1648+
[Test]
1649+
public void WriteCachedReferenceTypeField ()
1650+
{
1651+
options.SymbolTable.AddType (new TestClass (null, "Java.Lang.Object"));
1652+
var eClass = new TestClass ("Java.Lang.Object", "java.code.Example");
1653+
options.SymbolTable.AddType (eClass);
1654+
1655+
var klass = new TestClass ("Object", "java.code.MyClass");
1656+
1657+
var field = SupportTypeBuilder.CreateField ("field", options, "java.code.Example", true);
1658+
field.IsFinal = true;
1659+
1660+
klass.Fields.Add (field);
1661+
1662+
generator.Context.ContextTypes.Push (klass);
1663+
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
1664+
generator.Context.ContextTypes.Pop ();
1665+
1666+
AssertTargetedExpected (nameof (WriteCachedReferenceTypeField), writer.ToString ());
1667+
}
1668+
1669+
[Test]
1670+
public void WriteCachedValueTypeField ()
1671+
{
1672+
options.SymbolTable.AddType (new TestClass (null, "Java.Lang.Object"));
1673+
var eClass = new TestClass ("Java.Lang.Object", "java.code.Example");
1674+
options.SymbolTable.AddType (eClass);
1675+
1676+
var klass = new TestClass ("Object", "java.code.MyClass");
1677+
1678+
var field = SupportTypeBuilder.CreateField ("field", options, "int", true);
1679+
field.IsFinal = true;
1680+
1681+
klass.Fields.Add (field);
1682+
1683+
generator.Context.ContextTypes.Push (klass);
1684+
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
1685+
generator.Context.ContextTypes.Pop ();
1686+
1687+
AssertTargetedExpected (nameof (WriteCachedValueTypeField), writer.ToString ());
1688+
}
16471689
}
16481690
}

Diff for: tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public class Field : ApiVersionsSupport.IApiAvailability, ISourceLineInfo
4242

4343
public bool NeedsProperty => !IsStatic || !IsFinal || string.IsNullOrEmpty (Value) || Symbol.IsArray || !primitive_types.Contains (Symbol.JavaName);
4444

45+
public string CachedMemberName => $"_{Name}_cache";
46+
4547
public bool Validate (CodeGenerationOptions opt, GenericParameterDefinitionList type_params, CodeGeneratorContext context)
4648
{
4749
Symbol = opt.SymbolTable.Lookup (TypeName, type_params);

Diff for: tools/generator/SourceWriters/BoundFieldAsProperty.cs

+30-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class BoundFieldAsProperty : PropertyWriter
1616
{
1717
readonly Field field;
1818
readonly CodeGenerationOptions opt;
19+
readonly FieldWriter? cached_field;
1920

2021
public BoundFieldAsProperty (GenBase type, Field field, CodeGenerationOptions opt)
2122
{
@@ -59,10 +60,23 @@ public BoundFieldAsProperty (GenBase type, Field field, CodeGenerationOptions op
5960

6061
if (!field.IsConst)
6162
HasSet = true;
63+
64+
// This is considerably harder to support if we don't have NRT, due to the
65+
// differences in handling nullable value and reference types.
66+
if (field.IsConst && opt.SupportNullableReferenceTypes)
67+
cached_field = new FieldWriter {
68+
Name = field.CachedMemberName,
69+
Type = new TypeReferenceWriter (fieldType.TrimEnd ('?')) { Nullable = true },
70+
IsStatic = true,
71+
IsPrivate = true,
72+
UseExplicitPrivateKeyword = type is InterfaceGen,
73+
};
6274
}
6375

6476
public override void Write (CodeWriter writer)
6577
{
78+
cached_field?.Write (writer);
79+
6680
// This is just a temporary hack to write the [GeneratedEnum] attribute before the // Metadata.xml
6781
// comment so that we are 100% equal to pre-refactor.
6882
var generated_attr = Attributes.OfType<GeneratedEnumAttr> ().FirstOrDefault ();
@@ -82,6 +96,13 @@ public override void WriteAttributes (CodeWriter writer)
8296

8397
protected override void WriteGetterBody (CodeWriter writer)
8498
{
99+
var cached_field_type = cached_field is not null ? new TypeReferenceWriter (cached_field.Type.Namespace, cached_field.Type.Name) : null;
100+
101+
if (cached_field is not null) {
102+
writer.WriteLine ($"if ({field.CachedMemberName} != null) return ({cached_field_type}){field.CachedMemberName};");
103+
writer.WriteLine ();
104+
}
105+
85106
writer.WriteLine ($"const string __id = \"{field.JavaName}.{field.Symbol.JniName}\";");
86107
writer.WriteLine ();
87108

@@ -93,24 +114,27 @@ protected override void WriteGetterBody (CodeWriter writer)
93114

94115
writer.WriteLine ($"var __v = {field.Symbol.ReturnCast}_members.{indirect}.{invoke} (__id{(field.IsStatic ? "" : ", this")});");
95116

117+
var cache_setter = cached_field is not null ? $"({PropertyType})({field.CachedMemberName} = " : "";
118+
var cache_setter_end = cached_field is not null ? ")" : "";
119+
96120
if (opt.CodeGenerationTarget == CodeGenerationTarget.JavaInterop1) {
97121
if (field.Symbol.NativeType == field.Symbol.FullName) {
98-
writer.WriteLine ("return __v;");
122+
writer.WriteLine ($"return {cache_setter}__v{cache_setter_end};");
99123
return;
100124
}
101-
writer.Write ("return global::Java.Interop.JniEnvironment.Runtime.ValueManager.GetValue<");
125+
writer.Write ($"return {cache_setter}global::Java.Interop.JniEnvironment.Runtime.ValueManager.GetValue<");
102126
PropertyType.WriteTypeReference (writer);
103127
writer.Write (">(ref __v, JniObjectReferenceOptions.Copy)");
104-
writer.WriteLine (";");
128+
writer.WriteLine ($"{cache_setter_end};");
105129
return;
106130
}
107131

108132
if (field.Symbol.IsArray) {
109-
writer.WriteLine ($"return global::Android.Runtime.JavaArray<{opt.GetOutputName (field.Symbol.ElementType)}>.FromJniHandle (__v.Handle, JniHandleOwnership.TransferLocalRef);");
133+
writer.WriteLine ($"return {cache_setter}global::Android.Runtime.JavaArray<{opt.GetOutputName (field.Symbol.ElementType)}>.FromJniHandle (__v.Handle, JniHandleOwnership.TransferLocalRef){cache_setter_end};");
110134
} else if (field.Symbol.NativeType != field.Symbol.FullName) {
111-
writer.WriteLine ($"return {field.Symbol.ReturnCast}{(field.Symbol.FromNative (opt, invokeType != "Object" ? "__v" : "__v.Handle", true) + opt.GetNullForgiveness (field))};");
135+
writer.WriteLine ($"return {cache_setter}{field.Symbol.ReturnCast}{(field.Symbol.FromNative (opt, invokeType != "Object" ? "__v" : "__v.Handle", true) + opt.GetNullForgiveness (field))}{cache_setter_end};");
112136
} else {
113-
writer.WriteLine ("return __v;");
137+
writer.WriteLine ($"return {cache_setter}__v{cache_setter_end};");
114138
}
115139
}
116140

Diff for: tools/generator/generator.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<TargetFramework>$(DotNetTargetFramework)</TargetFramework>
55
<OutputType>Exe</OutputType>
66
<DefineConstants>$(DefineConstants);GENERATOR;HAVE_CECIL;JCW_ONLY_TYPE_NAMES</DefineConstants>
7+
<Nullable>annotations</Nullable>
78
</PropertyGroup>
89

910
<Import Project="..\..\TargetFrameworkDependentValues.props" />

0 commit comments

Comments
 (0)