From 34c96b090cb2617bed2fbad15de209cbd5a9dfb4 Mon Sep 17 00:00:00 2001 From: spaceflint <> Date: Wed, 4 Aug 2021 22:10:27 +0300 Subject: [PATCH] Version 0.2 --- Baselib/mscorlib.filter | 9 +- Baselib/src/System/Array.cs | 71 ++- Baselib/src/System/Collections/IComparer.cs | 47 +- Baselib/src/System/Enum.cs | 20 +- Baselib/src/System/GenericType.cs | 18 +- .../src/System/Globalization/CompareInfo.cs | 2 +- .../src/System/Globalization/CultureInfo.cs | 3 + Baselib/src/System/Guid.cs | 15 +- Baselib/src/System/IComparable.cs | 6 +- Baselib/src/System/Math.cs | 199 ++++++ Baselib/src/System/Reflection/FSharpCompat.cs | 124 ++++ Baselib/src/System/Reflection/MethodBase.cs | 2 +- .../src/System/Reflection/RuntimeAssembly.cs | 6 + .../Reflection/RuntimeConstructorInfo.cs | 2 +- .../src/System/Reflection/RuntimeFieldInfo.cs | 87 +-- .../System/Reflection/RuntimeMethodInfo.cs | 26 +- .../src/System/Reflection/RuntimeModule.cs | 2 +- .../System/Reflection/RuntimePropertyInfo.cs | 275 +++++++++ Baselib/src/System/Reflection/RuntimeType2.cs | 564 ++++++++++++++++++ .../src/System/Resources/ResourceManager.cs | 5 +- Baselib/src/System/RuntimeType.cs | 449 ++------------ Baselib/src/System/Text/StringBuilder.cs | 7 + CilToJava/src/CilInterface.cs | 19 + CilToJava/src/CilMain.cs | 6 +- CilToJava/src/CilMethod.cs | 98 ++- CilToJava/src/CodeBuilder.cs | 12 +- CilToJava/src/CodeCall.cs | 61 +- CilToJava/src/CodeCompare.cs | 25 + CilToJava/src/CodeField.cs | 106 ++++ CilToJava/src/CodeLocals.cs | 9 +- CilToJava/src/CodeMisc.cs | 16 +- CilToJava/src/GenericUtil.cs | 16 +- CilToJava/src/InterfaceBuilder.cs | 38 +- CilToJava/src/TypeBuilder.cs | 29 +- CilToJava/src/ValueUtil.cs | 26 +- JavaBinary/src/JavaCodeWriter.cs | 49 ++ JavaBinary/src/JavaStackMap.cs | 4 + LICENSE | 2 +- Main/src/DotNetImporter.cs | 35 +- Main/src/DotNetPrinter.cs | 2 +- Tests/src/TestArray.cs | 29 + Tests/src/TestGeneric.cs | 23 +- Tests/src/TestReflection.cs | 3 + Tests/src/TestSystem.cs | 21 + USAGE-ANDROID.md | 88 +-- USAGE.md | 39 +- 46 files changed, 2090 insertions(+), 605 deletions(-) create mode 100644 Baselib/src/System/Reflection/FSharpCompat.cs create mode 100644 Baselib/src/System/Reflection/RuntimePropertyInfo.cs create mode 100644 Baselib/src/System/Reflection/RuntimeType2.cs diff --git a/Baselib/mscorlib.filter b/Baselib/mscorlib.filter index 31994d0..2ab8c7e 100644 --- a/Baselib/mscorlib.filter +++ b/Baselib/mscorlib.filter @@ -46,6 +46,7 @@ System.IWellKnownStringEqualityComparer System.DivideByZeroException System.FormatException +System.FormattableString System.Func`* System.ICloneable @@ -89,6 +90,7 @@ System.Collections.ObjectModel.ReadOnlyCollection`1 System.Collections.HashHelpers System.Collections.Generic.Comparer`1 +System.Collections.Generic.IComparer`1 System.Collections.Generic.ComparisonComparer`1 System.Collections.Generic.ObjectComparer`1 System.Collections.Generic.GenericComparer`1 @@ -189,13 +191,18 @@ System.Reflection.CallingConventions System.Reflection.IntrospectionExtensions System.Reflection.IReflect System.Reflection.MemberInfo +System.Reflection.FieldAttributes System.Reflection.FieldInfo +System.Reflection.EventAttributes System.Reflection.EventInfo System.Reflection.ParameterAttributes System.Reflection.ParameterInfo +System.Reflection.PropertyAttributes System.Reflection.PropertyInfo +System.Reflection.MethodAttributes System.Reflection.MethodInfo System.Reflection.ConstructorInfo +System.Reflection.TypeAttributes System.Reflection.TypeInfo System.Reflection.MemberFilter System.Reflection.Missing @@ -204,7 +211,6 @@ System.Reflection.InterfaceMapping System.Reflection.InvalidFilterCriteriaException System.Reflection.IReflectableType System.Reflection.ParameterModifier -System.Reflection.TypeAttributes System.Reflection.TypeFilter System.Reflection.TargetParameterCountException System.Reflection.TargetInvocationException @@ -229,6 +235,7 @@ System.Runtime.CompilerServices.AsyncTaskCache System.Runtime.CompilerServices.AsyncTaskMethodBuilder System.Runtime.CompilerServices.AsyncTaskMethodBuilder`* System.Runtime.CompilerServices.AsyncVoidMethodBuilder +System.Runtime.CompilerServices.FormattableStringFactory System.Runtime.CompilerServices.IAsyncStateMachine System.Runtime.CompilerServices.TaskAwaiter System.Runtime.CompilerServices.TaskAwaiter`* diff --git a/Baselib/src/System/Array.cs b/Baselib/src/System/Array.cs index 5b94235..0f2a465 100644 --- a/Baselib/src/System/Array.cs +++ b/Baselib/src/System/Array.cs @@ -711,33 +711,34 @@ namespace system // public static void Sort(T[] array) - => SortGeneric(array, array, 0, -1, false, null); + => SortGeneric(array, array, 0, -1, false, null); - public static void Sort(T[] array, system.collections.generic.IComparer comparer) - => SortGeneric(array, array, 0, -1, false, comparer); + public static void Sort(T[] array, IComparer comparer) + => SortGeneric(array, array, 0, -1, false, comparer); public static void Sort(T[] array, int index, int length) - => SortGeneric(array, array, index, length, true, null); + => SortGeneric(array, array, index, length, true, null); - public static void Sort(T[] array, int index, int length, system.collections.generic.IComparer comparer) - => SortGeneric(array, array, index, length, true, comparer); + public static void Sort(T[] array, int index, int length, IComparer comparer) + => SortGeneric(array, array, index, length, true, comparer); public static void Sort(TKey[] keys, TValue[] items) - => SortGeneric(keys, items, 0, -1, false, null); + => SortGeneric(keys, items, 0, -1, false, null); - public static void Sort(TKey[] keys, TValue[] items, system.collections.generic.IComparer comparer) - => SortGeneric(keys, items, 0, -1, false, comparer); + public static void Sort(TKey[] keys, TValue[] items, IComparer comparer) + => SortGeneric(keys, items, 0, -1, false, comparer); public static void Sort(TKey[] keys, TValue[] items, int index, int length) - => SortGeneric(keys, items, index, length, true, null); + => SortGeneric(keys, items, index, length, true, null); public static void Sort(TKey[] keys, TValue[] items, int index, int length, - system.collections.generic.IComparer comparer) - => SortGeneric(keys, items, index, length, true, comparer); + IComparer comparer) + => SortGeneric(keys, items, index, length, true, comparer); - private static void SortGeneric(object keys_, object items_, - int index, int length, bool haveLength, - java.util.Comparator comparer) + private static void SortGeneric(object keys_, object items_, + int index, int length, + bool haveLength, + IComparer comparer) { ThrowIfNull(keys_); var keys = GetProxy(keys_); @@ -749,7 +750,8 @@ namespace system ThrowIfNull(items_); items = GetProxy(items_); } - SortCommon(keys, items, index, length, haveLength, comparer); + SortCommon(keys, items, index, length, haveLength, + new system.collections.GenericComparerProxy(comparer)); } // @@ -1463,10 +1465,9 @@ namespace system // create the Array.Proxy object, where T is the array element. // note that we use reflection to call the constructor, which takes // one additional hidden parameter for the generic type. - var elementType = - system.RuntimeType.GetType(objClass.getComponentType()); var newProxyObject = - ProxyConstructor.newInstance(new object[] { obj, elementType }); + ProxyConstructor.newInstance(new object[] { + obj, GetArrayElementType(obj, objClass) }); proxy = ArrayProxyCache.GetOrAdd(obj, newProxyObject); } if (ok == 2) @@ -1526,6 +1527,38 @@ namespace system return (Array) (Array.ProxySyncRoot) proxy; } + private static System.Type GetArrayElementType( + object arrayObject, java.lang.Class arrayClass) + { + // an array object does not keep generic type information + // for its component, so a Tuple[] array becomes + // just Tuple$$2[]. casting to IEnumerable> + // would fail. below, we try to extract the concrete generic + // type from the first element in the array. + // + // first, check if the array component is a non-concrete + // generic type, and that the array has at least one element. + var elementType = system.RuntimeType.GetType( + arrayClass.getComponentType()); + if ( elementType.IsGenericTypeDefinition + && java.lang.reflect.Array.getLength(arrayObject) > 0) + { + // extract the first element, and make sure it is the same + // class as the array (so same number of generic argument) + var element0 = java.lang.reflect.Array.get(arrayObject, 0); + if ( (! object.ReferenceEquals(element0, null)) + && element0.GetType() is RuntimeType element0Type + && ((RuntimeType) elementType).JavaClassForArray() + == element0Type.JavaClassForArray()) + { + // return a concrete generic type + elementType = elementType.MakeGenericType( + element0Type.GetGenericArguments()); + } + } + return elementType; + } + public static void MarkJagged(object obj) { ((Array) GetProxy(obj, cachedArrayType, false)).jagged = true; diff --git a/Baselib/src/System/Collections/IComparer.cs b/Baselib/src/System/Collections/IComparer.cs index e12517d..346e58f 100644 --- a/Baselib/src/System/Collections/IComparer.cs +++ b/Baselib/src/System/Collections/IComparer.cs @@ -3,30 +3,52 @@ namespace system.collections { [java.attr.AsInterface] - public abstract class IComparer : java.lang.Comparable, java.util.Comparator + public abstract class IComparer : java.util.Comparator { public abstract int Compare(object x, object y); [java.attr.RetainName] - public int compareTo(object obj) - => ((System.Collections.IComparer) this).Compare(this, obj); + public int compare(object obj1, object obj2) + => this.Compare(obj1, obj2); + + [java.attr.RetainName] + public bool equals(object otherComparer) + => system.Object.Equals(this, otherComparer); + } + + + + public class GenericComparerProxy : java.util.Comparator + { + System.Collections.Generic.IComparer comparer; + + public GenericComparerProxy(System.Collections.Generic.IComparer _comparer) + => comparer = _comparer; [java.attr.RetainName] public int compare(object obj1, object obj2) - => ((System.Collections.IComparer) this).Compare(obj1, obj2); + => comparer.Compare((T) obj1, (T) obj2); [java.attr.RetainName] - public bool equals(object obj) - => ((System.Collections.IComparer) this).Compare(this, obj) == 0; + public bool equals(object otherComparer) + => system.Object.Equals(this, otherComparer); } } +/* +removing custom IComparer. the idea was to be able to send an +object implementing this interface to a Java method that takes a +java.util.Comparator. this works for the non-generic IComparer, +but will not work for the generic counterpart. instead we have +a bridge (GenericComparerProxy above) which takes an IComparer +object and returns a java.util.Comparator. for an example usage, +see SortGeneric in system.Array. namespace system.collections.generic { [java.attr.AsInterface] - public abstract class IComparer : java.lang.Comparable, java.util.Comparator + public abstract class IComparer : java.util.Comparator { // a generic variance field is created for this abstract class, // see also GenericUtil::CreateGenericVarianceField() @@ -34,17 +56,12 @@ namespace system.collections.generic public abstract int Compare(T x, T y); [java.attr.RetainName] - public int compareTo(object obj) - => ((System.Collections.Generic.IComparer) this).Compare((T) (object) this, (T) obj); - - [java.attr.RetainName] - public int compare(object obj1, object obj2) - => ((System.Collections.Generic.IComparer) this).Compare((T) obj1, (T) obj2); + public int compare(object obj1, object obj2) => Compare(obj1, obj2); [java.attr.RetainName] public bool equals(object obj) - => ((System.Collections.Generic.IComparer) this).Compare((T) (object) this, (T) obj) == 0; + => throw new System.PlatformNotSupportedException(); } } - +*/ diff --git a/Baselib/src/System/Enum.cs b/Baselib/src/System/Enum.cs index 803eb49..2e2ace8 100644 --- a/Baselib/src/System/Enum.cs +++ b/Baselib/src/System/Enum.cs @@ -263,22 +263,26 @@ namespace system static string FormatNames(java.lang.Class cls, long v, bool asFlags) { + var enumLiteralFlags = ( java.lang.reflect.Modifier.PUBLIC + | java.lang.reflect.Modifier.STATIC + | java.lang.reflect.Modifier.FINAL); + var fields = cls.getDeclaredFields(); int n = fields.Length; var s = asFlags - ? ToStringMulti(v, fields, n) - : ToStringSingle(v, fields, n); + ? ToStringMulti(v, fields, n, enumLiteralFlags) + : ToStringSingle(v, fields, n, enumLiteralFlags); return s ?? java.lang.Long.toString(v); } - static string ToStringSingle(long v, java.lang.reflect.Field[] fields, int n) + static string ToStringSingle(long v, java.lang.reflect.Field[] fields, int n, + int enumLiteralFlags) { for (int i = 0; i < n; i++) { var f = fields[i]; - if (f.getModifiers() == ( java.lang.reflect.Modifier.PUBLIC - | java.lang.reflect.Modifier.STATIC)) + if (f.getModifiers() == enumLiteralFlags) { f.setAccessible(true); if (f.getLong(null) == v) @@ -288,7 +292,8 @@ namespace system return null; } - static string ToStringMulti(long v, java.lang.reflect.Field[] fields, int n) + static string ToStringMulti(long v, java.lang.reflect.Field[] fields, int n, + int enumLiteralFlags) { var v0 = v; var sb = new java.lang.StringBuilder(); @@ -297,8 +302,7 @@ namespace system for (int i = 0; i < n; i++) { var f = fields[i]; - if (f.getModifiers() == ( java.lang.reflect.Modifier.PUBLIC - | java.lang.reflect.Modifier.STATIC)) + if (f.getModifiers() == enumLiteralFlags) { f.setAccessible(true); var fv = f.getLong(null); diff --git a/Baselib/src/System/GenericType.cs b/Baselib/src/System/GenericType.cs index b360ea5..735c97c 100644 --- a/Baselib/src/System/GenericType.cs +++ b/Baselib/src/System/GenericType.cs @@ -89,7 +89,7 @@ namespace system if (! ( proxyType is RuntimeType proxyRuntimeType && proxyRuntimeType.IsCastableToGenericInterface(castToType, - (parentObject is system.Array.ProxySyncRoot parentArray) ))) + (parentObject is system.Array.ProxySyncRoot) ))) { return null; } @@ -183,8 +183,8 @@ namespace system public static void ThrowInvalidCastException(object objToCast, System.Type castToType) { - string msg = "Unable to cast object of type '" + objToCast.GetType().Name - + "' to type '" + castToType.Name + "'."; + string msg = "Unable to cast object of type '" + objToCast.GetType() + + "' to type '" + castToType + "'."; throw new InvalidCastException(msg); } @@ -200,8 +200,20 @@ namespace system // this is a marker interface used by the system.RuntimeType constructor // to identify generic types. this is implemented by interface types; // class types implement IGenericObject, which derives from this type. + + // it also makes it easy to specify ProGuard rules for Android 'R8': + + // -keepclassmembers class * implements system.IGenericEntity { + // public static final java.lang.String ?generic?variance; + // public static final *** ?generic?info?class; + // private system.RuntimeType ?generic?type; + // public static final *** ?generic?info?method (...); + // (...); + // } } + + interface IGenericObject : IGenericEntity { // Returns the type of a generic object (i.e. its '-generic-type' field). diff --git a/Baselib/src/System/Globalization/CompareInfo.cs b/Baselib/src/System/Globalization/CompareInfo.cs index 4c7b400..d65c1de 100644 --- a/Baselib/src/System/Globalization/CompareInfo.cs +++ b/Baselib/src/System/Globalization/CompareInfo.cs @@ -1285,7 +1285,7 @@ namespace system.globalization { var iterator = JavaCollator.getCollationElementIterator("" + ch0); if (iterator.next() != 0) { - throw new System.Globalization.CultureNotFoundException( + throw new PlatformNotSupportedException( "unexpected value for UNICODE NULL"); } } diff --git a/Baselib/src/System/Globalization/CultureInfo.cs b/Baselib/src/System/Globalization/CultureInfo.cs index b344f8a..9412393 100644 --- a/Baselib/src/System/Globalization/CultureInfo.cs +++ b/Baselib/src/System/Globalization/CultureInfo.cs @@ -99,6 +99,9 @@ namespace system.globalization { public static CultureInfo CurrentCulture => system.threading.Thread.CurrentThread.CurrentCulture; + public static CultureInfo CurrentUICulture + => system.threading.Thread.CurrentThread.CurrentCulture; + public static CultureInfo InvariantCulture => s_InvariantCultureInfo; public virtual object Clone() => throw new System.NotImplementedException(); diff --git a/Baselib/src/System/Guid.cs b/Baselib/src/System/Guid.cs index a65724b..51d239e 100644 --- a/Baselib/src/System/Guid.cs +++ b/Baselib/src/System/Guid.cs @@ -9,7 +9,20 @@ namespace system [java.attr.RetainType] private java.util.UUID _uuid; private java.util.UUID uuid => _uuid ?? (_uuid = new java.util.UUID(0, 0)); - public Guid(string g) => _uuid = java.util.UUID.fromString(g); + public Guid(string g) + { + if (g.Length == 32) + { + var g1 = (java.lang.CharSequence) (object) g; + g = new java.lang.StringBuilder(36) + .append(g1, 0, 0 + 8).append('-') + .append(g1, 8, 8 + 4).append('-') + .append(g1, 12, 12 + 4).append('-') + .append(g1, 16, 16 + 4).append('-') + .append(g1, 20, 20 + 12).ToString(); + } + _uuid = java.util.UUID.fromString(g); + } public override string ToString() => uuid.ToString(); public string ToString(string format) => ToString(format, null); diff --git a/Baselib/src/System/IComparable.cs b/Baselib/src/System/IComparable.cs index 1294095..f177d0f 100644 --- a/Baselib/src/System/IComparable.cs +++ b/Baselib/src/System/IComparable.cs @@ -9,11 +9,7 @@ namespace system public abstract int CompareTo(object obj); [java.attr.RetainName] - public int compareTo(object obj) - { - //return ((System.IComparable) this).CompareTo(obj); - return this.CompareTo(obj); - } + public int compareTo(object obj) => this.CompareTo(obj); } diff --git a/Baselib/src/System/Math.cs b/Baselib/src/System/Math.cs index d442d10..c000447 100644 --- a/Baselib/src/System/Math.cs +++ b/Baselib/src/System/Math.cs @@ -49,10 +49,28 @@ namespace system public static double Tan(double a) => java.lang.Math.tan(a); public static double Tanh(double a) => java.lang.Math.tanh(a); + public static double Asin(double a) => java.lang.Math.asin(a); + public static double Asinh(double a) => + java.lang.Math.log(a + java.lang.Math.sqrt(a * a + 1.0)); + public static double Acos(double a) => java.lang.Math.acos(a); + public static double Acosh(double a) => + java.lang.Math.log(a + java.lang.Math.sqrt(a * a - 1.0)); + public static double Atan(double a) => java.lang.Math.atan(a); + public static double Atan2(double x, double y) => java.lang.Math.atan2(x, y); + public static double Atanh(double a) => 0.5 * java.lang.Math.log((1 + a) / (1 - a)); + + public static double Cbrt(double a) => java.lang.Math.cbrt(a); + public static double Exp(double a) => java.lang.Math.exp(a); public static double Pow(double a, double b) => java.lang.Math.pow(a, b); public static double Sqrt(double a) => java.lang.Math.sqrt(a); public static double ScaleB(double x, int n) => java.lang.Math.scalb(x, n); + public static double Ceiling(double a) => java.lang.Math.ceil(a); + public static double Floor(double a) => java.lang.Math.floor(a); + public static double IEEERemainder(double x, double y) => + java.lang.Math.IEEEremainder(x, y); + public static double CopySign(double x, double y) => java.lang.Math.copySign(x, y); + public static double Round(double a) { // java round clamps the values to Long.MIN_VALUE .. Long.MAX_VALUE @@ -74,6 +92,187 @@ namespace system public static void OverflowException() => throw new System.OverflowException("Arithmetic operation resulted in an overflow."); + + public static double BitDecrement(double a) + { + // java.lang.Math.nextDown(double) + if (java.lang.Double.isNaN(a) || a == java.lang.Double.NEGATIVE_INFINITY) + return a; + if (a == 0.0) + return -java.lang.Double.MIN_VALUE; + return java.lang.Double.longBitsToDouble( + java.lang.Double.doubleToRawLongBits(a) + + ((a > 0.0) ? -1L : 1L)); + } + + public static double BitIncrement(double a) + { + // java.lang.Math.nextUp(double) + if (java.lang.Double.isNaN(a) || a == java.lang.Double.POSITIVE_INFINITY) + return a; + a += 0.0; + return java.lang.Double.longBitsToDouble( + java.lang.Double.doubleToRawLongBits(a) + + ((a >= 0.0) ? 1L : -1L)); + } + + public static double MaxMagnitude(double x, double y) + { + var ax = java.lang.Math.abs(x); + var ay = java.lang.Math.abs(y); + return ( (ax > ay) + || (ax == ay && x >= 0.0) + || java.lang.Double.isNaN(ax)) ? x : y; + } + + public static double MinMagnitude(double x, double y) + { + var ax = java.lang.Math.abs(x); + var ay = java.lang.Math.abs(y); + return ( (ax < ay) + || (ax == ay && x < 0.0) + || java.lang.Double.isNaN(ax)) ? x : y; + } + + public static double Log(double a) => java.lang.Math.log(a); + public static double Log10(double a) => java.lang.Math.log10(a); + public static double Log2(double a) => java.lang.Math.log(a) / java.lang.Math.log(2.0); + public static int ILogB(double a) => (int) (java.lang.Math.log(a) / java.lang.Math.log(2.0)); + + public static double Log(double a, double b) + { + if (java.lang.Double.isNaN(a)) + return a; + if (java.lang.Double.isNaN(b)) + return b; + if ( (b == 1.0) + || ( (a != 1.0) + && ( b == 0.0 + || b == java.lang.Double.POSITIVE_INFINITY))) + return java.lang.Double.NaN; + return java.lang.Math.log(a) / java.lang.Math.log(b); + } + + } + + + + public static class MathF + { + public static int Sign(float a) => (int) java.lang.Math.signum(a); + public static float Abs(float a) => (a < 0) ? -a : a; + public static float Min(float a, float b) => (a <= b) ? a : b; + public static float Max(float a, float b) => (a >= b) ? a : b; + + public static float Sin(float a) => (float) java.lang.Math.sin(a); + public static float Sinh(float a) => (float) java.lang.Math.sinh(a); + public static float Cos(float a) => (float) java.lang.Math.cos(a); + public static float Cosh(float a) => (float) java.lang.Math.cosh(a); + public static float Tan(float a) => (float) java.lang.Math.tan(a); + public static float Tanh(float a) => (float) java.lang.Math.tanh(a); + + public static float Asin(float a) => (float) java.lang.Math.asin(a); + public static float Asinh(float a) => (float) + java.lang.Math.log(a + java.lang.Math.sqrt((double) a * a + 1.0)); + public static float Acos(float a) => (float) java.lang.Math.acos(a); + public static float Acosh(float a) => (float) + java.lang.Math.log(a + java.lang.Math.sqrt((double) a * a - 1.0)); + public static float Atan(float a) => (float) java.lang.Math.atan(a); + public static float Atan2(float x, double y) => (float) java.lang.Math.atan2(x, y); + public static float Atanh(float a) => + (float) java.lang.Math.log((1 + a) / (1 - a)) * 0.5f; + + public static float Cbrt(float a) => (float) java.lang.Math.cbrt(a); + public static float Exp(float a) => (float) java.lang.Math.exp(a); + public static float Pow(float a, float b) => (float) java.lang.Math.pow(a, b); + public static float Sqrt(float a) => (float) java.lang.Math.sqrt(a); + public static float ScaleB(float x, int n) => (float) java.lang.Math.scalb(x, n); + + public static float Ceiling(float a) => (float) java.lang.Math.ceil(a); + public static float Floor(float a) => (float) java.lang.Math.floor(a); + public static float IEEERemainder(float x, float y) => + (float) java.lang.Math.IEEEremainder(x, y); + public static float CopySign(float x, float y) => java.lang.Math.copySign(x, y); + + public static float Round(float a) + { + // java round clamps the values to Long.MIN_VALUE .. Long.MAX_VALUE + // so we have to use floor rather than round + if (a > System.Int64.MaxValue) + return (float) java.lang.Math.floor(a + 0.5); + else if (a < System.Int64.MinValue) + return (float) java.lang.Math.floor(a - 0.5); + else + return (float) java.lang.Math.round(a); + } + + public static float Truncate(float a) + { + if (! (java.lang.Float.isInfinite(a) || java.lang.Float.isNaN(a))) + a = (float) ((long) a); + return a; + } + + public static float BitDecrement(float a) + { + // java.lang.Math.nextDown(float) + if (java.lang.Float.isNaN(a) || a == java.lang.Float.NEGATIVE_INFINITY) + return a; + if (a == 0.0f) + return -java.lang.Float.MIN_VALUE; + return java.lang.Float.intBitsToFloat( + java.lang.Float.floatToRawIntBits(a) + + ((a > 0.0f) ? -1 : 1)); + } + + public static float BitIncrement(float a) + { + // java.lang.Math.nextUp(float) + if (java.lang.Float.isNaN(a) || a == java.lang.Float.POSITIVE_INFINITY) + return a; + a += 0.0f; + return java.lang.Float.intBitsToFloat( + java.lang.Float.floatToRawIntBits(a) + + ((a >= 0.0f) ? 1 : -1)); + } + + public static float MaxMagnitude(float x, float y) + { + var ax = java.lang.Math.abs(x); + var ay = java.lang.Math.abs(y); + return ( (ax > ay) + || (ax == ay && x >= 0.0f) + || java.lang.Float.isNaN(ax)) ? x : y; + } + + public static float MinMagnitude(float x, float y) + { + var ax = java.lang.Math.abs(x); + var ay = java.lang.Math.abs(y); + return ( (ax < ay) + || (ax == ay && x < 0.0f) + || java.lang.Float.isNaN(ax)) ? x : y; + } + + public static float Log(float a) => (float) java.lang.Math.log(a); + public static float Log10(float a) => (float) java.lang.Math.log10(a); + public static float Log2(float a) => (float) (java.lang.Math.log(a) / java.lang.Math.log(2.0)); + public static int ILogB(float a) => (int) (java.lang.Math.log(a) / java.lang.Math.log(2.0)); + + public static float Log(float a, float b) + { + if (java.lang.Float.isNaN(a)) + return a; + if (java.lang.Float.isNaN(b)) + return b; + if ( (b == 1.0f) + || ( (a != 1.0f) + && ( b == 0.0f + || b == java.lang.Float.POSITIVE_INFINITY))) + return java.lang.Float.NaN; + return (float) (java.lang.Math.log(a) / java.lang.Math.log(b)); + } + } } diff --git a/Baselib/src/System/Reflection/FSharpCompat.cs b/Baselib/src/System/Reflection/FSharpCompat.cs new file mode 100644 index 0000000..fe35d0f --- /dev/null +++ b/Baselib/src/System/Reflection/FSharpCompat.cs @@ -0,0 +1,124 @@ + +namespace system.reflection +{ + + public static class FSharpCompat + { + + // we don't yet translate .Net attributes to Java annotations, + // so we have to fake some attributes to support F# ToString() + + public static object[] GetCustomAttributes_Type(java.lang.Class scanClass, + java.lang.Class attrClass) + { + // we manufacture a CompilationMapping attribute for a type + // type implements at least three specific interfaces. + // if it has a nested Tags class, it is a (discriminated union) + // SumType; otherwise it is a plain RecordType. + + if (IsFSharpAttr(attrClass) && IsFSharpType(scanClass)) + { + int sourceConstructFlags = IsFSharpSumType(scanClass) + ? /* SumType */ 1 : /* RecordType */ 2; + + return CreateAttr(attrClass, sourceConstructFlags); + } + + return null; + } + + + + public static object[] GetCustomAttributes_Property(java.lang.Class declClass, + java.lang.Class attrClass) + { + if (IsFSharpAttr(attrClass)) + { + // we manufacture a CompilationMapping attribute for a property: + // - if declared directly in a class that is an "F# type" + // and is not also a SumType + // - if declared in an inner class, and the outer class + // is an "F# type" that is a SumType + + bool isFSharp = IsFSharpType(declClass); + if (isFSharp) + { + if (IsFSharpSumType(declClass)) + isFSharp = false; + } + else + { + var outerClass = declClass.getDeclaringClass(); + if (outerClass != null) + isFSharp = IsFSharpType(outerClass); + } + + if (isFSharp) + { + return CreateAttr(attrClass, /* Field */ 4); + } + + return RuntimeType.EmptyObjectArray; + } + return null; + } + + + private static bool IsFSharpType(java.lang.Class scanClass) + { + int requiredInterfaceCount = 0; + foreach (var ifc in scanClass.getInterfaces()) + { + if ( ifc == (java.lang.Class) + typeof(System.Collections.IStructuralEquatable) + || ifc == (java.lang.Class) + typeof(System.Collections.IStructuralComparable) + || ifc == (java.lang.Class) typeof(System.IComparable)) + requiredInterfaceCount++; + } + return (requiredInterfaceCount >= 3); + } + + + private static bool IsFSharpSumType(java.lang.Class scanClass) + => system.RuntimeType.FindInnerClass(scanClass, "Tags") != null; + + + private static bool IsFSharpAttr(java.lang.Class attrClass) + => attrClass == (java.lang.Class) + typeof(Microsoft.FSharp.Core.CompilationMappingAttribute); + + + private static object[] CreateAttr(java.lang.Class attrClass, + int sourceConstructFlags) + { + foreach (var _constr in attrClass.getConstructors()) + { + #pragma warning disable 0436 + var constr = (java.lang.reflect.Constructor) (object) _constr; + #pragma warning restore 0436 + var parameters = constr.getParameterTypes(); + if ( parameters.Length == 2 + && parameters[0] == java.lang.Integer.TYPE + && parameters[1].isMemberClass()) + { + var created = constr.newInstance(new object[] { + java.lang.Integer.valueOf(sourceConstructFlags), + null + }); + return new object[] { created }; + } + } + return null; + } + + } + +} + + +namespace Microsoft.FSharp.Core +{ + [java.attr.Discard] // discard in output + public class CompilationMappingAttribute { } +} diff --git a/Baselib/src/System/Reflection/MethodBase.cs b/Baselib/src/System/Reflection/MethodBase.cs index f5798e6..fdce73f 100644 --- a/Baselib/src/System/Reflection/MethodBase.cs +++ b/Baselib/src/System/Reflection/MethodBase.cs @@ -51,7 +51,7 @@ namespace system.reflection public bool IsPrivate => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Private; - public bool IsFamily => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Family; + public bool IsFamily => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Family; public bool IsAssembly => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Assembly; diff --git a/Baselib/src/System/Reflection/RuntimeAssembly.cs b/Baselib/src/System/Reflection/RuntimeAssembly.cs index ccc85b2..f31a411 100644 --- a/Baselib/src/System/Reflection/RuntimeAssembly.cs +++ b/Baselib/src/System/Reflection/RuntimeAssembly.cs @@ -99,6 +99,12 @@ namespace system.reflection throw new NotImplementedException("Assembly.GetCustomAttributes"); } + // + // Properties + // + + public override bool ReflectionOnly => false; + // // ISerializable // diff --git a/Baselib/src/System/Reflection/RuntimeConstructorInfo.cs b/Baselib/src/System/Reflection/RuntimeConstructorInfo.cs index d577c55..69e0b57 100644 --- a/Baselib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/Baselib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -127,7 +127,7 @@ namespace system.reflection object[] parameters, CultureInfo culture) { if (parameters == null) - parameters = new object[0]; + parameters = system.RuntimeType.EmptyObjectArray; int numParameters = parameters.Length; if (HasUniqueArg) diff --git a/Baselib/src/System/Reflection/RuntimeFieldInfo.cs b/Baselib/src/System/Reflection/RuntimeFieldInfo.cs index 6145122..b5c40df 100644 --- a/Baselib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/Baselib/src/System/Reflection/RuntimeFieldInfo.cs @@ -12,6 +12,7 @@ namespace system.reflection { [java.attr.RetainType] public java.lang.reflect.Field JavaField; [java.attr.RetainType] public system.RuntimeType reflectedType; + [java.attr.RetainType] private FieldAttributes cachedAttrs = 0; // // constructor @@ -30,69 +31,83 @@ namespace system.reflection public static FieldInfo[] GetFields(BindingFlags bindingAttr, RuntimeType initialType) { - var list = new System.Collections.Generic.List(); + var list = new java.util.ArrayList(); - BindingFlagsIterator.Run(bindingAttr, initialType, MemberTypes.Field, + BindingFlagsIterator.Run(bindingAttr & ~BindingFlags.GetField, + initialType, MemberTypes.Field, (javaAccessibleObject) => { var javaField = (java.lang.reflect.Field) javaAccessibleObject; javaField.setAccessible(true); - list.Add(new RuntimeFieldInfo(javaField, initialType)); + list.add(new RuntimeFieldInfo(javaField, initialType)); return true; }); - return list.ToArray(); + return (RuntimeFieldInfo[]) list.toArray(new RuntimeFieldInfo[0]); } + // + // + // + public override System.Type FieldType => system.RuntimeType.GetType(JavaField.getType()); - // - // - // - public override object GetValue(object obj) - => throw new PlatformNotSupportedException(); + => system.RuntimeType.UnboxJavaReturnValue(JavaField.get(obj)); public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture) => throw new PlatformNotSupportedException(); - public override System.Reflection.FieldAttributes Attributes - => throw new PlatformNotSupportedException(); + public override FieldAttributes Attributes + { + get + { + var attrs = cachedAttrs; + if (attrs == 0) + { + int modifiers = JavaField.getModifiers(); + if ((modifiers & java.lang.reflect.Modifier.PUBLIC) != 0) + attrs |= FieldAttributes.Public; + if ((modifiers & java.lang.reflect.Modifier.PRIVATE) != 0) + attrs |= FieldAttributes.Private; + if ((modifiers & java.lang.reflect.Modifier.PROTECTED) != 0) + attrs |= FieldAttributes.Family; + if ((modifiers & java.lang.reflect.Modifier.TRANSIENT) != 0) + attrs |= FieldAttributes.NotSerialized; - public override System.Type DeclaringType - => throw new PlatformNotSupportedException(); + if ((modifiers & java.lang.reflect.Modifier.STATIC) != 0) + { + attrs |= FieldAttributes.Static; + if ( ((modifiers & java.lang.reflect.Modifier.FINAL) != 0) + && JavaField.getType().isPrimitive() + && JavaField.get(null) != null) + { + attrs |= FieldAttributes.Literal; + } + } - public override System.Type ReflectedType - => throw new PlatformNotSupportedException(); + cachedAttrs = attrs; + } + return attrs; + } + } + + public override Type ReflectedType => reflectedType; + + public override Type DeclaringType + => system.RuntimeType.GetType(JavaField.getDeclaringClass()); public override string Name => JavaField.getName(); public override object GetRawConstantValue() { - if ((JavaField.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0) + if (0 != (JavaField.getModifiers() & ( java.lang.reflect.Modifier.STATIC + | java.lang.reflect.Modifier.FINAL)) + && JavaField.getType().isPrimitive()) { - var value = JavaField.get(null); - switch (value) - { - case java.lang.Boolean boolBox: - return system.Boolean.Box(boolBox.booleanValue() ? 1 : 0); - case java.lang.Byte byteBox: - return system.SByte.Box(byteBox.byteValue()); - case java.lang.Character charBox: - return system.Char.Box(charBox.charValue()); - case java.lang.Short shortBox: - return system.Int16.Box(shortBox.shortValue()); - case java.lang.Integer intBox: - return system.Int32.Box(intBox.intValue()); - case java.lang.Long longBox: - return system.Int64.Box(longBox.longValue()); - case java.lang.Float floatBox: - return system.Single.Box(floatBox.floatValue()); - case java.lang.Double doubleBox: - return system.Double.Box(doubleBox.doubleValue()); - } + return GetValue(null); } throw new System.NotSupportedException(); } diff --git a/Baselib/src/System/Reflection/RuntimeMethodInfo.cs b/Baselib/src/System/Reflection/RuntimeMethodInfo.cs index 0fc5dce..5035ecb 100644 --- a/Baselib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/Baselib/src/System/Reflection/RuntimeMethodInfo.cs @@ -50,7 +50,7 @@ namespace system.reflection string originalName = javaMethod.getName(); var compareName = RuntimeMethodInfo.AdjustedMethodName(originalName); - if (name == compareName) + if (name == compareName && CompareParameters(javaMethod, types)) { javaMethod.setAccessible(true); var jmodifiers = javaMethod.getModifiers(); @@ -63,6 +63,26 @@ namespace system.reflection }); return foundMethod; + + + + #pragma warning disable 0436 + static bool CompareParameters(java.lang.reflect.Method javaMethod, Type[] types) + { + if (types == null) + return true; + var paramTypes = javaMethod.getParameterTypes(); + if (types.Length != paramTypes.Length) + return false; + for (int i = 0; i < paramTypes.Length; i++) + { + if (! object.ReferenceEquals( + types[i], system.RuntimeType.GetType(paramTypes[i]))) + return false; + } + return true; + } + #pragma warning restore 0436 } // @@ -76,8 +96,6 @@ namespace system.reflection throw new PlatformNotSupportedException("non-null binder"); if (callConvention != CallingConventions.Any) throw new PlatformNotSupportedException("calling convention must be Any"); - if (types != null && types.Length != 0) - throw new PlatformNotSupportedException("non-null types"); if (modifiers != null) throw new PlatformNotSupportedException("non-null modifiers"); } @@ -337,7 +355,7 @@ namespace system.reflection } // - // + // GetParameters // public override ParameterInfo[] GetParameters() diff --git a/Baselib/src/System/Reflection/RuntimeModule.cs b/Baselib/src/System/Reflection/RuntimeModule.cs index 3236e63..8d7e00b 100644 --- a/Baselib/src/System/Reflection/RuntimeModule.cs +++ b/Baselib/src/System/Reflection/RuntimeModule.cs @@ -13,7 +13,7 @@ namespace system.reflection { [java.attr.RetainType] public java.security.ProtectionDomain JavaDomain; - public override System.Type[] GetTypes() => new System.Type[0]; + public override System.Type[] GetTypes() => system.RuntimeType.EmptyTypeArray; public MetadataImport MetadataImport => _MetadataImport; public StructLayoutAttribute StructLayoutAttribute => _StructLayoutAttribute; diff --git a/Baselib/src/System/Reflection/RuntimePropertyInfo.cs b/Baselib/src/System/Reflection/RuntimePropertyInfo.cs new file mode 100644 index 0000000..18cfed2 --- /dev/null +++ b/Baselib/src/System/Reflection/RuntimePropertyInfo.cs @@ -0,0 +1,275 @@ + +using System; +using System.Reflection; +using System.Globalization; +using System.Runtime.Serialization; + +namespace system.reflection +{ + + [System.Serializable] + public sealed class RuntimePropertyInfo : PropertyInfo, ISerializable + { + #pragma warning disable 0436 + [java.attr.RetainType] private java.lang.reflect.Method JavaGetMethod; + [java.attr.RetainType] private java.lang.reflect.Method JavaSetMethod; + #pragma warning restore 0436 + [java.attr.RetainType] private string propertyName; + [java.attr.RetainType] private system.RuntimeType propertyType; + [java.attr.RetainType] private system.RuntimeType reflectedType; + [java.attr.RetainType] private system.RuntimeType declaringType; + + // + // GetMethod (called by system.RuntimeType.GetPropertyImpl) + // + + public static PropertyInfo GetProperty(string name, BindingFlags bindingAttr, + Binder binder, Type returnType, + Type[] types, ParameterModifier[] modifiers, + RuntimeType initialType) + { + ThrowHelper.ThrowIfNull(name); + + if (types != null && types.Length != 0) // if indexed property + throw new PlatformNotSupportedException(); + + #pragma warning disable 0436 + java.lang.reflect.Method getMethod = null; + java.lang.Class getClass = null; + java.lang.reflect.Method setMethod = null; + java.lang.Class setClass = null; + #pragma warning restore 0436 + + BindingFlagsIterator.Run(bindingAttr & ~BindingFlags.GetProperty, + initialType, MemberTypes.Method, + (javaAccessibleObject) => + { + #pragma warning disable 0436 + var javaMethod = (java.lang.reflect.Method) javaAccessibleObject; + #pragma warning restore 0436 + + var cls = IsGetMethod(javaMethod, name, returnType); + if (cls != null) + { + getMethod = javaMethod; + getClass = cls; + } + else + { + cls = IsSetMethod(javaMethod, name, returnType); + if (cls != null) + { + setMethod = javaMethod; + setClass = cls; + } + } + + return true; + }); + + if (getClass != null) + { + if (setClass != null && setClass != getClass) + setMethod = null; + } + else if (setClass != null) + { + getClass = setClass; + } + else // neither get nor set methods + return null; + + return new RuntimePropertyInfo(getMethod, setMethod, getClass, initialType); + + + + #pragma warning disable 0436 + static java.lang.Class IsGetMethod(java.lang.reflect.Method javaMethod, + string propertyName, Type propertyType) + { + if (javaMethod.getName() == "get_" + propertyName) + { + javaMethod.setAccessible(true); + var returnClass = javaMethod.getReturnType(); + if ( object.ReferenceEquals(propertyType, null) + || object.ReferenceEquals(propertyType, + system.RuntimeType.GetType(returnClass))) + { + return returnClass; + } + } + return null; + } + + static java.lang.Class IsSetMethod(java.lang.reflect.Method javaMethod, + string propertyName, Type propertyType) + { + if (javaMethod.getName() == "set_" + propertyName) + { + javaMethod.setAccessible(true); + var paramClasses = javaMethod.getParameterTypes(); + if (paramClasses.Length == 1) + { + var paramClass = paramClasses[0]; + if ( object.ReferenceEquals(propertyType, null) + || object.ReferenceEquals(propertyType, + system.RuntimeType.GetType(paramClass))) + { + return paramClass; + } + } + } + return null; + } + #pragma warning restore 0436 + } + + // + // GetProperties (called by system.RuntimeType.GetProperties() + // + + public static PropertyInfo[] GetProperties(BindingFlags bindingAttr, + RuntimeType initialType) + { + var list = new java.util.ArrayList(); + + BindingFlagsIterator.Run(bindingAttr & ~BindingFlags.GetProperty, + initialType, MemberTypes.Method, + (javaAccessibleObject) => + { + #pragma warning disable 0436 + var javaMethod = (java.lang.reflect.Method) javaAccessibleObject; + #pragma warning restore 0436 + if (javaMethod.getName().StartsWith("get_")) + { + javaMethod.setAccessible(true); + var returnClass = javaMethod.getReturnType(); + if ( returnClass != java.lang.Void.TYPE + && javaMethod.getParameterTypes().Length == 0) + { + list.add(new RuntimePropertyInfo( + javaMethod, null, returnClass, initialType)); + } + } + return true; + }); + + return (RuntimePropertyInfo[]) list.toArray(new RuntimePropertyInfo[0]); + } + + // + // constructor + // + + #pragma warning disable 0436 + private RuntimePropertyInfo(java.lang.reflect.Method javaGetMethod, + java.lang.reflect.Method javaSetMethod, + java.lang.Class javaClass, + system.RuntimeType reflectedType) + #pragma warning restore 0436 + { + var getOrSetMethod = javaGetMethod ?? javaSetMethod; + propertyName = getOrSetMethod.getName().Substring(4); + propertyType = (system.RuntimeType) system.RuntimeType.GetType(javaClass); + this.JavaGetMethod = javaGetMethod; + this.JavaSetMethod = javaSetMethod; + this.reflectedType = reflectedType; + this.declaringType = (system.RuntimeType) system.RuntimeType.GetType( + getOrSetMethod.getDeclaringClass()); + } + + // + // + // + + public override PropertyAttributes Attributes + => throw new PlatformNotSupportedException(); + + public override MethodInfo[] GetAccessors(bool nonPublic) + => throw new PlatformNotSupportedException(); + + public override MethodInfo GetGetMethod(bool nonPublic) + => throw new PlatformNotSupportedException(); + + public override MethodInfo GetSetMethod(bool nonPublic) + => throw new PlatformNotSupportedException(); + + public override bool CanRead => (JavaGetMethod != null); + public override bool CanWrite => (JavaSetMethod != null); + + // + // + // + + public override Type PropertyType => propertyType; + + public override ParameterInfo[] GetIndexParameters() + => throw new PlatformNotSupportedException(); + + public override object GetValue(object obj, BindingFlags invokeAttr, + Binder binder, object[] index, CultureInfo culture) + { + invokeAttr &= ~( BindingFlags.Public | BindingFlags.NonPublic + | BindingFlags.Instance | BindingFlags.GetProperty); + if (invokeAttr != BindingFlags.Default) + throw new PlatformNotSupportedException("bad binding flags " + invokeAttr); + if (binder != null) + throw new PlatformNotSupportedException("non-null binder"); + if (culture != null) + throw new PlatformNotSupportedException("non-null culture"); + + if (index != null || JavaGetMethod == null) + throw new ArgumentException(); + + return system.RuntimeType.UnboxJavaReturnValue( + JavaGetMethod.invoke(obj, null)); + } + + public override void SetValue(object obj, object value, BindingFlags invokeAttr, + Binder binder, object[] index, CultureInfo culture) + => throw new PlatformNotSupportedException(); + + // + // + // + + public override Type ReflectedType => reflectedType; + + public override Type DeclaringType => declaringType; + + public override string Name => propertyName; + + // + // custom attributes + // + + public override bool IsDefined(Type attributeType, bool inherit) + => throw new PlatformNotSupportedException(); + + public override object[] GetCustomAttributes(bool inherit) + => throw new PlatformNotSupportedException(); + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + if (attributeType is system.RuntimeType attributeRuntimeType) + { + // we don't yet translate .Net attributes to Java annotations, + // so we have to fake some attributes to support F# ToString() + var attrs = system.reflection.FSharpCompat.GetCustomAttributes_Property( + declaringType.JavaClassForArray(), + attributeRuntimeType.JavaClassForArray()); + if (attrs != null) + return attrs; + } + throw new PlatformNotSupportedException(); + } + + // + // ISerializable + // + + public void GetObjectData(SerializationInfo info, StreamingContext context) + => throw new PlatformNotSupportedException(); + + } +} diff --git a/Baselib/src/System/Reflection/RuntimeType2.cs b/Baselib/src/System/Reflection/RuntimeType2.cs new file mode 100644 index 0000000..99eb7ad --- /dev/null +++ b/Baselib/src/System/Reflection/RuntimeType2.cs @@ -0,0 +1,564 @@ + +using System; +using System.Reflection; +using System.Globalization; +using System.Runtime.Serialization; + +namespace system +{ + + // + // System/RuntimeType.cs contains methods required by the type + // system. System/Reflection/RuntimeType2.cs (this file) contains + // methods to provide run-time reflection support. + // + + public partial class RuntimeType + { + + // + // FullName + // + + public override string FullName + { + get + { + string name; + var declType = DeclaringType; + if (! object.ReferenceEquals(declType, null)) + name = declType.FullName + "+"; + else if (IsGenericParameter) + return null; + else + { + name = Namespace; + if (name != null) + name += "."; + } + name += Name; + return name; + } + } + + // + // Name + // + + public override string Name + { + get + { + var generic = Generic; + + if (JavaClass == null) + return GenericParameterName(generic); + if (JavaClass.isArray()) + return (GetType(JavaClass.getComponentType()).Name) + "[]"; + else + return ClassName(generic); + + string ClassName(GenericData generic) + { + var name = JavaClass.getSimpleName(); + int numArgsInDeclType = 0; + + var declType = DeclaringType; + if (declType is RuntimeType declRuntimeType && declRuntimeType.Generic != null) + { + // a nested generic type duplicates the parameters of the parent + numArgsInDeclType = declRuntimeType.Generic.ArgumentTypes.Length; + } + + if (generic != null) + { + int numArgs = generic.ArgumentTypes.Length; + int index = name.LastIndexOf("$$" + numArgs); + if (index != -1) + name = name.Substring(0, index); + numArgs -= numArgsInDeclType; + if (numArgs > 0) + name += "`" + numArgs.ToString(); + } + + return name; + } + + string GenericParameterName(GenericData generic) + { + if (generic != null) + { + // generic parameter + var argTypes = generic.PrimaryType.Generic.ArgumentTypes; + for (int i = 0; i < argTypes.Length; i++) + { + if (object.ReferenceEquals(argTypes[i], this)) + { + var typeNames = generic.PrimaryType.JavaClass.getTypeParameters(); + if (i < typeNames.Length) + { + // if we have a valid generic signature, extract parameter name + var nm = typeNames[i].getName(); + if (! string.IsNullOrEmpty(nm)) + return nm; + } + return "T" + i.ToString(); + } + } + } + return null; // invalid combination + } + + } + } + + // + // Namespace + // + + public override string Namespace + { + get + { + var javaClass = JavaClass; + if (javaClass == null) + return null; + + if (javaClass.isArray()) + { + do + { + javaClass = javaClass.getComponentType(); + } + while (javaClass.isArray()); + return GetType(javaClass).Namespace; + } + + string dottedName = javaClass.getName(); + int lastIndex = dottedName.LastIndexOf('.'); + if (lastIndex <= 0) + return null; + + int firstIndex = 0; + string resultName = ""; + for (;;) + { + int nextIndex = dottedName.IndexOf('.', firstIndex); + var component = Char.ToUpperInvariant(dottedName[firstIndex]) + + dottedName.Substring(firstIndex + 1, nextIndex - firstIndex - 1); + resultName += component; + if (nextIndex == lastIndex) + return resultName; + resultName += "."; + firstIndex = nextIndex + 1; + } + } + } + + // + // DeclaringType + // + + public override Type DeclaringType + { + get + { + var javaClass = JavaClass; + if (javaClass == null) + return null; + if (javaClass.isArray()) + javaClass = javaClass.getComponentType(); + var declClass = javaClass.getDeclaringClass(); + return (declClass == null) ? null : GetType(declClass); + } + } + + // + // ReflectedType + // + + public override Type ReflectedType => DeclaringType; + + // + // DeclaringMethod + // + + public override MethodBase DeclaringMethod + => throw new PlatformNotSupportedException(); + + // + // BaseType + // + + public override Type BaseType + { + get + { + if ( JavaClass == null + || object.ReferenceEquals(this, ObjectType) + || IsInterface(this)) + { + return null; + } + if (object.ReferenceEquals(this, ExceptionType)) + { + // CilType translates System.Exception to java.lang.Throwable, + // and here we map java.lang.Throwable to system.Exception, + // thereby creating an infinite loop in which system.Exception + // inherits from java.lang.Exception, which inherits from + // java.lang.Throwable, which is mapped to system.Exception. + // the workaround breaks the infinite loop. + return ObjectType; + } + return FromJavaGenericType(JavaClass.getGenericSuperclass()); + } + } + + // + // Module + // + + public override Module Module => Assembly.GetModules(false)[0]; + + // + // Assembly + // + + public override Assembly Assembly => system.reflection.RuntimeAssembly.CurrentAssembly; + + // + // AssemblyQualifiedName + // + + public override string AssemblyQualifiedName + { + get + { + var name = FullName; + if (name != null) + name = Assembly.CreateQualifiedName(Assembly.FullName, name); + return name; + } + } + + // + // GetCustomAttributes + // + + public override object[] GetCustomAttributes(bool inherit) => EmptyObjectArray; + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + if (attributeType is system.RuntimeType attributeRuntimeType) + { + // we don't yet translate .Net attributes to Java annotations, + // so we have to fake some attributes to support F# ToString() + var attrs = system.reflection.FSharpCompat.GetCustomAttributes_Type( + this.JavaClass, attributeRuntimeType.JavaClass); + if (attrs != null) + return attrs; + } + return EmptyObjectArray; + } + + // + // IsDefined + // + + public override bool IsDefined(Type attributeType, bool inherit) => false; + + // + // GetInterfaces + // + + public override Type[] GetInterfaces() + { + var output = new java.util.HashSet(); + GetInterfacesInternal(JavaClass, output); + return (Type[]) output.toArray(System.Type.EmptyTypes); + + void GetInterfacesInternal(java.lang.Class input, java.util.HashSet output) + { + foreach (var javaType in input.getGenericInterfaces()) + { + output.add(FromJavaGenericType(javaType)); + if (javaType is java.lang.Class classType) + GetInterfacesInternal(classType, output); + } + } + } + + // + // GetInterface + // + + public override Type GetInterface(string name, bool ignoreCase) + { + if (name == null) + throw new ArgumentNullException(); + StringComparison cmp = ignoreCase + ? System.StringComparison.InvariantCultureIgnoreCase + : System.StringComparison.InvariantCulture; + foreach (var ifc in GetInterfaces()) + { + if (string.Compare(name, ifc.FullName, cmp) == 0) + return ifc; + } + return null; + } + + // + // GetNestedTypes + // + + public override Type[] GetNestedTypes(BindingFlags bindingAttr) + { + bool takePublic = (bindingAttr & BindingFlags.Public) != 0; + bool takeNonPublic = (bindingAttr & BindingFlags.NonPublic) != 0; + if (takePublic || takeNonPublic) + { + var innerClasses = JavaClass.getDeclaredClasses(); + if (innerClasses.Length > 0) + { + var list = new java.util.ArrayList(); + for (int i = 0; i < innerClasses.Length; i++) + { + var innerCls = innerClasses[i]; + var isPublic = (0 != (innerCls.getModifiers() + & java.lang.reflect.Modifier.PUBLIC)); + + if (takePublic == isPublic || takeNonPublic != isPublic) + { + var innerType = GetType(innerCls); + var generic = ((RuntimeType) innerType).Generic; + list.add(generic != null ? generic.PrimaryType : innerType); + } + } + + return (Type[]) list.toArray(system.RuntimeType.EmptyTypeArray); + } + } + + return system.RuntimeType.EmptyTypeArray; + } + + // + // GetNestedType + // + + public override Type GetNestedType(string name, BindingFlags bindingAttr) + { + ThrowHelper.ThrowIfNull(name); + if ((bindingAttr & BindingFlags.IgnoreCase) != 0) + throw new PlatformNotSupportedException(); + + var innerCls = FindInnerClass(JavaClass, name); + if (innerCls != null) + { + bool takePublic = (bindingAttr & BindingFlags.Public) != 0; + bool takeNonPublic = (bindingAttr & BindingFlags.NonPublic) != 0; + var isPublic = (0 != (innerCls.getModifiers() + & java.lang.reflect.Modifier.PUBLIC)); + + if (takePublic == isPublic || takeNonPublic != isPublic) + { + var innerType = GetType(innerCls); + var generic = ((RuntimeType) innerType).Generic; + return (generic != null ? generic.PrimaryType : innerType); + } + } + + return null; + } + + // + // FindInnerClass (public helper) + // + + public static java.lang.Class FindInnerClass( + java.lang.Class javaClass, string name) + { + var innerClasses = javaClass.getDeclaredClasses(); + for (int i = 0; i < innerClasses.Length; i++) + { + var innerCls = innerClasses[i]; + if (innerCls.getSimpleName() == name) + return innerCls; + } + return null; + } + + // + // Reflection on members of the type + // + + public override EventInfo GetEvent(string name, BindingFlags bindingAttr) + => throw new PlatformNotSupportedException(); + + public override FieldInfo GetField(string name, BindingFlags bindingAttr) + { + throw new PlatformNotSupportedException(); + } + + protected override PropertyInfo GetPropertyImpl( + string name, BindingFlags bindingAttr, Binder binder, Type returnType, + Type[] types, ParameterModifier[] modifiers) + { + if (JavaClass == null) // if generic parameter + return null; + return system.reflection.RuntimePropertyInfo.GetProperty( + name, bindingAttr, binder, returnType, types, modifiers, this); + } + + protected override MethodInfo GetMethodImpl( + string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, + Type[] types, ParameterModifier[] modifiers) + { + if (JavaClass == null) // if generic parameter + return null; + return system.reflection.RuntimeMethodInfo.GetMethod( + name, bindingAttr, binder, callConvention, types, modifiers, this); + } + + protected override ConstructorInfo GetConstructorImpl( + BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, + Type[] types, ParameterModifier[] modifiers) + { + if (JavaClass == null) // if generic parameter + return null; + return system.reflection.RuntimeConstructorInfo.GetConstructor( + bindingAttr, binder, callConvention, types, modifiers, this); + } + + // + // + // + + public override MemberInfo[] GetMembers(BindingFlags bindingAttr) + => throw new PlatformNotSupportedException(); + + public override EventInfo[] GetEvents(BindingFlags bindingAttr) + => throw new PlatformNotSupportedException(); + + public override FieldInfo[] GetFields(BindingFlags bindingAttr) + => system.reflection.RuntimeFieldInfo.GetFields(bindingAttr, this); + + public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) + => system.reflection.RuntimePropertyInfo.GetProperties(bindingAttr, this); + + public override MethodInfo[] GetMethods(BindingFlags bindingAttr) + => throw new PlatformNotSupportedException(); + + public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) + => throw new PlatformNotSupportedException(); + + // + // + // + + public override object InvokeMember( + string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, + ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) + { + throw new PlatformNotSupportedException(); + } + + // + // reflection support - extract information from Java generic signatures + // (generated by MakeGenericSignature in GenericUtil) + // + + Type FromJavaGenericType(java.lang.reflect.Type javaType) + { + if (javaType is java.lang.reflect.ParameterizedType parameterizedType) + { + // a ParameterizedType is GenericType + // where each GenericArg would be a TypeVariable or a Class + + var primaryClass = (java.lang.Class) parameterizedType.getRawType(); + var javaTypeArgs = parameterizedType.getActualTypeArguments(); + int n = javaTypeArgs.Length; + var typeArgs = new Type[n]; + for (int i = 0; i < n; i++) + typeArgs[i] = FromJavaGenericType(javaTypeArgs[i]); + return GetType(primaryClass, typeArgs); + } + + if ( javaType is java.lang.reflect.TypeVariable varType + && varType.getGenericDeclaration() is java.lang.Class varClass) + { + // a TypeVariable is a GenericArg with a reference to the type + // it belongs to. if that type (in the java generics mechanism) + // corresponds to 'this' type, and if 'this' type is a concrete + // generic instance (as opposed to a generic definition), then + // we grab a concrete generic type from 'this' type instance. + // + // for example, with A and B : A, then + // for generic instance B, we are able to resolve + // the base type as A. + + var varName = varType.getName(); + var primaryType = GetType(varClass) as RuntimeType; + var argTypes = primaryType?.Generic?.ArgumentTypes; + if (argTypes != null) + { + int n = argTypes.Length; + for (int i = 0; i < n; i++) + { + if (argTypes[i].Name == varName) + { + if ( varClass == JavaClass + && Generic != null && Generic.ArgumentTypes != null + && (! object.ReferenceEquals(Generic.PrimaryType, this))) + { + return Generic.ArgumentTypes[i]; + } + return argTypes[i]; + } + } + } + } + + if (javaType is java.lang.Class plainClass) + { + // a java generic Type may also be a java.lang.Class + return GetType(plainClass); + } + + // if this is a java concept like GenericArrayType or WildcardType, + // or anything else we don't recognize, return System.Object + + return ObjectType; + } + + // + // UnboxJavaReturnValue + // + + public static object UnboxJavaReturnValue(object value) + { + switch (value) + { + case java.lang.Boolean boolBox: + return system.Boolean.Box(boolBox.booleanValue() ? 1 : 0); + case java.lang.Byte byteBox: + return system.SByte.Box(byteBox.byteValue()); + case java.lang.Character charBox: + return system.Char.Box(charBox.charValue()); + case java.lang.Short shortBox: + return system.Int16.Box(shortBox.shortValue()); + case java.lang.Integer intBox: + return system.Int32.Box(intBox.intValue()); + case java.lang.Long longBox: + return system.Int64.Box(longBox.longValue()); + case java.lang.Float floatBox: + return system.Single.Box(floatBox.floatValue()); + case java.lang.Double doubleBox: + return system.Double.Box(doubleBox.doubleValue()); + } + return value; + } + + } + +} diff --git a/Baselib/src/System/Resources/ResourceManager.cs b/Baselib/src/System/Resources/ResourceManager.cs index 1f1c442..fdb4270 100644 --- a/Baselib/src/System/Resources/ResourceManager.cs +++ b/Baselib/src/System/Resources/ResourceManager.cs @@ -5,10 +5,7 @@ namespace system.resources public class ResourceManager { - public ResourceManager(string baseName, System.Reflection.Assembly assembly) - { - Console.WriteLine($"Creating ResourceManager with base name '{baseName}' in assembly '{assembly}'"); - } + public ResourceManager(string baseName, System.Reflection.Assembly assembly) { } public string GetString(string name, System.Globalization.CultureInfo culture) => name; diff --git a/Baselib/src/System/RuntimeType.cs b/Baselib/src/System/RuntimeType.cs index b1572d2..2a1fc6a 100644 --- a/Baselib/src/System/RuntimeType.cs +++ b/Baselib/src/System/RuntimeType.cs @@ -7,8 +7,15 @@ using System.Runtime.Serialization; namespace system { + // + // System/RuntimeType.cs (this file) contains methods required + // by the type system. System/Reflection/RuntimeType2.cs + // contains methods to provide run-time reflection support. + // + [Serializable] - public class RuntimeType : system.reflection.TypeInfo, ISerializable, ICloneable + public partial class RuntimeType : system.reflection.TypeInfo, + ISerializable, ICloneable { private sealed class GenericData @@ -89,6 +96,26 @@ namespace system break; } } + + if (searchClass == javaClass) + { + // if Android 'R8' (ProGuard) stripped the InnerClass + // attribute, then the above getDeclaredClasses() would + // return nothing. in this case we use an alternative + // approach of scanning for the -generic-info-class + // field; see also TypeBuilder.BuildJavaClass() + foreach (var field in javaClass.getDeclaredFields()) + { + // search for field: public static final synthetic, + // with a type that is public final synthetic + if ( field.getModifiers() == 0x1019 + && field.getType().getModifiers() == 0x1011) + { + searchClass = field.getType(); + break; + } + } + } } #pragma warning disable 0436 @@ -307,16 +334,17 @@ namespace system if (! TypeSystemInitialized) { - Console.WriteLine("\n!!! Exception occured during initialization of the type system:\n"); + java.lang.System.@out.println("\n!!! Exception occured during initialization of the type system:\n"); var exc = exception; for (;;) { - Console.WriteLine("Exception " + ((java.lang.Object) (object) exc).getClass() - + "\nMessage: " + ((java.lang.Throwable) exc).getMessage() - + "\n" + exc.StackTrace); + java.lang.System.@out.println( + "Exception " + ((java.lang.Object) (object) exc).getClass() + + "\nMessage: " + ((java.lang.Throwable) exc).getMessage() + + "\n" + exc.StackTrace); if ((exc = exc.InnerException) == null) break; - Console.Write("Caused by Inner "); + java.lang.System.@out.print("Caused by Inner "); } } return exception; @@ -419,203 +447,8 @@ namespace system public static bool op_Inequality(RuntimeType obj1, RuntimeType obj2) => ! object.ReferenceEquals(obj1, obj2); - - - // - // MemberInfo - // - - public override string Name - { - get - { - var generic = Generic; - - if (JavaClass == null) - return GenericParameterName(generic); - if (JavaClass.isArray()) - return (GetType(JavaClass.getComponentType()).Name) + "[]"; - else - return ClassName(generic); - - string ClassName(GenericData generic) - { - var name = JavaClass.getSimpleName(); - int numArgsInDeclType = 0; - - var declType = DeclaringType; - if (declType is RuntimeType declRuntimeType && declRuntimeType.Generic != null) - { - // a nested generic type duplicates the parameters of the parent - numArgsInDeclType = declRuntimeType.Generic.ArgumentTypes.Length; - } - - if (generic != null) - { - int numArgs = generic.ArgumentTypes.Length; - int index = name.LastIndexOf("$$" + numArgs); - if (index != -1) - name = name.Substring(0, index); - numArgs -= numArgsInDeclType; - if (numArgs > 0) - name += "`" + numArgs.ToString(); - } - - return name; - } - - string GenericParameterName(GenericData generic) - { - if (generic != null) - { - // generic parameter - var argTypes = generic.PrimaryType.Generic.ArgumentTypes; - for (int i = 0; i < argTypes.Length; i++) - { - if (object.ReferenceEquals(argTypes[i], this)) - { - var typeNames = generic.PrimaryType.JavaClass.getTypeParameters(); - if (i < typeNames.Length) - { - // if we have a valid generic signature, extract parameter name - var nm = typeNames[i].getName(); - if (! string.IsNullOrEmpty(nm)) - return nm; - } - return "T" + i.ToString(); - } - } - } - return null; // invalid combination - } - - } - } - - public override Type DeclaringType - { - get - { - var javaClass = JavaClass; - if (javaClass == null) - return null; - if (javaClass.isArray()) - javaClass = javaClass.getComponentType(); - var declClass = javaClass.getDeclaringClass(); - return (declClass == null) ? null : GetType(declClass); - } - } - - public override Type ReflectedType => DeclaringType; - - public override object[] GetCustomAttributes(bool inherit) => null; - public override object[] GetCustomAttributes(Type attributeType, bool inherit) => null; - public override bool IsDefined(Type attributeType, bool inherit) => false; - - // - // _Type - // - - public override Type BaseType - { - get - { - if ( JavaClass == null - || object.ReferenceEquals(this, ObjectType) - || IsInterface(this)) - { - return null; - } - if (object.ReferenceEquals(this, ExceptionType)) - { - // CilType translates System.Exception to java.lang.Throwable, - // and here we map java.lang.Throwable to system.Exception, - // thereby creating an infinite loop in which system.Exception - // inherits from java.lang.Exception, which inherits from - // java.lang.Throwable, which is mapped to system.Exception. - // the workaround breaks the infinite loop. - return ObjectType; - } - return FromJavaGenericType(JavaClass.getGenericSuperclass()); - } - } - - public override string Namespace - { - get - { - var javaClass = JavaClass; - if (javaClass == null) - return null; - - if (javaClass.isArray()) - { - do - { - javaClass = javaClass.getComponentType(); - } - while (javaClass.isArray()); - return GetType(javaClass).Namespace; - } - - string dottedName = javaClass.getName(); - int lastIndex = dottedName.LastIndexOf('.'); - if (lastIndex <= 0) - return null; - - int firstIndex = 0; - string resultName = ""; - for (;;) - { - int nextIndex = dottedName.IndexOf('.', firstIndex); - var component = Char.ToUpperInvariant(dottedName[firstIndex]) - + dottedName.Substring(firstIndex + 1, nextIndex - firstIndex - 1); - resultName += component; - if (nextIndex == lastIndex) - return resultName; - resultName += "."; - firstIndex = nextIndex + 1; - } - } - } - - public override string FullName - { - get - { - string name; - var declType = DeclaringType; - if (! object.ReferenceEquals(declType, null)) - name = declType.FullName + "+"; - else if (IsGenericParameter) - return null; - else - { - name = Namespace; - if (name != null) - name += "."; - } - name += Name; - return name; - } - } - - public override Assembly Assembly => system.reflection.RuntimeAssembly.CurrentAssembly; - - public override string AssemblyQualifiedName - { - get - { - var name = FullName; - if (name != null) - name = Assembly.CreateQualifiedName(Assembly.FullName, name); - return name; - } - } - - public override Module Module => Assembly.GetModules(false)[0]; - public override System.Guid GUID => System.Guid.Empty; + public override Type UnderlyingSystemType => this; // Attributes property @@ -665,44 +498,6 @@ namespace system public override bool IsSerializable => ((GetAttributeFlagsImpl() & TypeAttributes.Serializable) != 0); - - - public override Type[] GetInterfaces() - { - var output = new java.util.HashSet(); - GetInterfacesInternal(JavaClass, output); - return (Type[]) output.toArray(System.Type.EmptyTypes); - - void GetInterfacesInternal(java.lang.Class input, java.util.HashSet output) - { - foreach (var javaType in input.getGenericInterfaces()) - { - output.add(FromJavaGenericType(javaType)); - if (javaType is java.lang.Class classType) - GetInterfacesInternal(classType, output); - } - } - } - - - - public override Type GetInterface(string name, bool ignoreCase) - { - if (name == null) - throw new ArgumentNullException(); - StringComparison cmp = ignoreCase - ? System.StringComparison.InvariantCultureIgnoreCase - : System.StringComparison.InvariantCulture; - foreach (var ifc in GetInterfaces()) - { - if (string.Compare(name, ifc.FullName, cmp) == 0) - return ifc; - } - return null; - } - - - protected override bool IsCOMObjectImpl() => false; protected override bool IsByRefImpl() => false; // always false? @@ -744,16 +539,6 @@ namespace system } } - public override Type[] GetNestedTypes(BindingFlags bindingAttr) - { - throw new PlatformNotSupportedException(); - } - - public override Type GetNestedType(string name, BindingFlags bindingAttr) - { - throw new PlatformNotSupportedException(); - } - public override Type[] GetGenericArguments() { if (Generic != null) @@ -761,7 +546,7 @@ namespace system return (Type[]) java.util.Arrays.copyOf( Generic.ArgumentTypes, Generic.ArgumentTypes.Length); } - return new Type[0]; + return EmptyTypeArray; } public override Type GetGenericTypeDefinition() @@ -798,8 +583,6 @@ namespace system // Primitives types // - - protected override TypeCode GetTypeCodeImpl() => (TypeCode) (((int) (CachedAttrs & AttrTypeCode) >> 24) + 1); @@ -905,7 +688,7 @@ namespace system public object CallConstructor(bool publicOnly) { - object[] args = (Generic == null) ? new object[0] : Generic.ArgumentTypes; + object[] args = (Generic == null) ? EmptyObjectArray : Generic.ArgumentTypes; try { #pragma warning disable 0436 @@ -1159,83 +942,10 @@ namespace system } - - // - // reflection support - extract information from Java generic signatures - // (generated by MakeGenericSignature in GenericUtil) - // - - Type FromJavaGenericType(java.lang.reflect.Type javaType) - { - if (javaType is java.lang.reflect.ParameterizedType parameterizedType) - { - // a ParameterizedType is GenericType - // where each GenericArg would be a TypeVariable or a Class - - var primaryClass = (java.lang.Class) parameterizedType.getRawType(); - var javaTypeArgs = parameterizedType.getActualTypeArguments(); - int n = javaTypeArgs.Length; - var typeArgs = new Type[n]; - for (int i = 0; i < n; i++) - typeArgs[i] = FromJavaGenericType(javaTypeArgs[i]); - return GetType(primaryClass, typeArgs); - } - - if ( javaType is java.lang.reflect.TypeVariable varType - && varType.getGenericDeclaration() is java.lang.Class varClass) - { - // a TypeVariable is a GenericArg with a reference to the type - // it belongs to. if that type (in the java generics mechanism) - // corresponds to 'this' type, and if 'this' type is a concrete - // generic instance (as opposed to a generic definition), then - // we grab a concrete generic type from 'this' type instance. - // - // for example, with A and B : A, then - // for generic instance B, we are able to resolve - // the base type as A. - - var varName = varType.getName(); - var primaryType = GetType(varClass) as RuntimeType; - var argTypes = primaryType?.Generic?.ArgumentTypes; - if (argTypes != null) - { - int n = argTypes.Length; - for (int i = 0; i < n; i++) - { - if (argTypes[i].Name == varName) - { - if ( varClass == JavaClass - && Generic != null && Generic.ArgumentTypes != null - && (! object.ReferenceEquals(Generic.PrimaryType, this))) - { - return Generic.ArgumentTypes[i]; - } - return argTypes[i]; - } - } - } - } - - if (javaType is java.lang.Class plainClass) - { - // a java generic Type may also be a java.lang.Class - return GetType(plainClass); - } - - // if this is a java concept like GenericArrayType or WildcardType, - // or anything else we don't recognize, return System.Object - - return ObjectType; - } - - - // // Methods to create and retrieve type objects // - - public static Type GetType(java.lang.Class cls, Type[] argTypes) { var key = new TypeKey(cls, argTypes); @@ -1376,6 +1086,9 @@ namespace system if (TypeCache == null) { + EmptyObjectArray = new object[0]; + EmptyTypeArray = new Type[0]; + TypeCache = new java.util.concurrent.ConcurrentHashMap(); TypeLock = new java.util.concurrent.locks.ReentrantLock(); @@ -1464,6 +1177,9 @@ namespace system + [java.attr.RetainType] public static object[] EmptyObjectArray; + [java.attr.RetainType] public static Type[] EmptyTypeArray; + [java.attr.RetainType] private static java.util.concurrent.ConcurrentHashMap TypeCache; [java.attr.RetainType] private static java.util.concurrent.locks.ReentrantLock TypeLock; [java.attr.RetainType] private static java.lang.Class ValueTypeClass; @@ -1672,85 +1388,6 @@ namespace system } - // - // Reflection on members of the type - // - - public override MethodBase DeclaringMethod - => throw new PlatformNotSupportedException(); - - protected override MethodInfo GetMethodImpl( - string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, - Type[] types, ParameterModifier[] modifiers) - { - if (JavaClass == null) // if generic parameter - return null; - return system.reflection.RuntimeMethodInfo.GetMethod( - name, bindingAttr, binder, callConvention, types, modifiers, this); - } - - protected override ConstructorInfo GetConstructorImpl( - BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, - Type[] types, ParameterModifier[] modifiers) - { - if (JavaClass == null) // if generic parameter - return null; - return system.reflection.RuntimeConstructorInfo.GetConstructor( - bindingAttr, binder, callConvention, types, modifiers, this); - } - - public override object InvokeMember( - string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, - ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) - { - throw new PlatformNotSupportedException(); - } - - protected override PropertyInfo GetPropertyImpl( - string name, BindingFlags bindingAttr, Binder binder, Type returnType, - Type[] types, ParameterModifier[] modifiers) - { - throw new PlatformNotSupportedException(); - } - - public override MethodInfo[] GetMethods(BindingFlags bindingAttr) - { - throw new PlatformNotSupportedException(); - } - - public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) - { - throw new PlatformNotSupportedException(); - } - - public override FieldInfo GetField(string name, BindingFlags bindingAttr) - { - throw new PlatformNotSupportedException(); - } - - public override FieldInfo[] GetFields(BindingFlags bindingAttr) - { - return system.reflection.RuntimeFieldInfo.GetFields(bindingAttr, this); - } - - public override MemberInfo[] GetMembers(BindingFlags bindingAttr) - { - throw new PlatformNotSupportedException(); - } - - public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) - { - throw new PlatformNotSupportedException(); - } - - public override EventInfo GetEvent(string name, BindingFlags bindingAttr) - => throw new PlatformNotSupportedException(); - - public override EventInfo[] GetEvents(BindingFlags bindingAttr) - => throw new PlatformNotSupportedException(); - - - // // unimplemented methods from System.Type: // base implementations throw NotSupportedException diff --git a/Baselib/src/System/Text/StringBuilder.cs b/Baselib/src/System/Text/StringBuilder.cs index c52f768..c02b2c7 100644 --- a/Baselib/src/System/Text/StringBuilder.cs +++ b/Baselib/src/System/Text/StringBuilder.cs @@ -143,6 +143,13 @@ namespace system.text return this; } + public StringBuilder Append(object value) + { + if (value != null) + sb.append(value.ToString()); + return this; + } + public int Capacity { get => sb.capacity(); diff --git a/CilToJava/src/CilInterface.cs b/CilToJava/src/CilInterface.cs index 65c12ce..556233d 100644 --- a/CilToJava/src/CilInterface.cs +++ b/CilToJava/src/CilInterface.cs @@ -171,6 +171,18 @@ namespace SpaceFlint.CilToJava public List Parameters; public CilType ReturnType; + // any resolved generic types from return type and parameters, + // concatenated together. used to differentiate generic methods + // that differ only in return or parameters types. for example: + // + // interface I1 { int M(T v); } + // interface I2 { int M(T v); } + // class CC : I1, I2 + // int M(T1 v) => ...; + // int M(T2 v) => ...; + // + // see also InterfaceBuilder.BuildGenericProxy() + public string ResolvedGenericTypes; public CilInterfaceMethod(CilMethod fromMethod) @@ -186,11 +198,14 @@ namespace SpaceFlint.CilToJava if (idx != -1) name = name.Substring(idx + 1); + ResolvedGenericTypes = ""; + var returnType = (CilType) fromMethod.ReturnType; if (returnType.IsGenericParameter) { var genericMark = CilMain.GenericStack.Mark(); var (type, index) = CilMain.GenericStack.Resolve(returnType.JavaName); + ResolvedGenericTypes = $"{type},{index};"; if (index == 0) returnType = type; @@ -208,6 +223,7 @@ namespace SpaceFlint.CilToJava var genericMark = CilMain.GenericStack.Mark(); var (type, index) = CilMain.GenericStack.Resolve(parameter.JavaName); + ResolvedGenericTypes += $"{type},{index};"; if (index == 0) { if (parameter.ArrayRank != 0) @@ -342,6 +358,9 @@ namespace SpaceFlint.CilToJava continue; } + if (fromMethod.HasCustomAttribute("Discard")) + continue; // skip if decorated with [java.attr.Discard] + var genericMark = CilMain.GenericStack.Mark(); var inputMethod = CilMain.GenericStack.EnterMethod(fromMethod); diff --git a/CilToJava/src/CilMain.cs b/CilToJava/src/CilMain.cs index c7f5ab6..5c739f0 100644 --- a/CilToJava/src/CilMain.cs +++ b/CilToJava/src/CilMain.cs @@ -67,7 +67,8 @@ namespace SpaceFlint.CilToJava internal static JavaClass CreateInnerClass(JavaClass outerClass, string innerName, - JavaAccessFlags innerFlags = 0) + JavaAccessFlags innerFlags = 0, + bool markGenericEntity = false) { if (innerFlags == 0) { @@ -85,6 +86,9 @@ namespace SpaceFlint.CilToJava innerClass.Fields = new List(); innerClass.Methods = new List(); + if (markGenericEntity) + innerClass.AddInterface("system.IGenericEntity"); + outerClass.AddInnerClass(innerClass); return innerClass; } diff --git a/CilToJava/src/CilMethod.cs b/CilToJava/src/CilMethod.cs index 253ee3a..195e751 100644 --- a/CilToJava/src/CilMethod.cs +++ b/CilToJava/src/CilMethod.cs @@ -80,7 +80,7 @@ namespace SpaceFlint.CilToJava Flags |= CONSTRUCTOR; Flags |= THIS_ARG | ARRAY_CALL; - ImportParameters(fromMethod); + ImportParameters(fromMethod, null); ImportGenericParameters(fromMethod); } @@ -91,7 +91,7 @@ namespace SpaceFlint.CilToJava DeclType = CilType.From(fromMethod.DeclaringType); SetMethodType(defMethod); - bool appendSuffix = ImportParameters(fromMethod); + bool appendSuffix = ImportParameters(fromMethod, defMethod); TranslateNameClrToJvm(fromMethod, appendSuffix); if (IsConstructor) @@ -166,7 +166,7 @@ namespace SpaceFlint.CilToJava - bool ImportParameters(MethodReference fromMethod) + bool ImportParameters(MethodReference fromMethod, MethodDefinition defMethod) { bool appendSuffix = false; @@ -194,7 +194,10 @@ namespace SpaceFlint.CilToJava nm += "array-" + paramType.ArrayRank + "-"; if (paramType.IsByReference) nm = "-ref" + nm.Replace("&", ""); - nm += "$" + ((GenericParameter) fromParameterType.GetElementType()).Position; + nm += "$" + GenericParameterPosition(defMethod, i, + ((GenericParameter) + fromParameterType.GetElementType())); + //nm += "$" + ((GenericParameter) fromParameterType.GetElementType()).Position; paramType = CilType.WrapMethodGenericParameter(paramType, nm); Flags |= GEN_ARGS; } @@ -275,6 +278,70 @@ namespace SpaceFlint.CilToJava return true; } + + + + static int GenericParameterPosition(MethodDefinition defMethod, + int parameterIndex, + GenericParameter parameter) + { + // when a method has parameters with a generic type, the + // method name gets a suffix like -generic-$n, where n is + // the index of the generic parameter in the generic type. + // e.g., method "int Mth(TY)" in class CA would be + // named "Mth-generic-$1" because TY has generic index 1. + // + // however when the method overrides a base class method, + // the n should be the index of the generic parameter in + // the base type. e.g., "override int Mth(UZ)" in class + // CB : CA translated to "Mth-generic-$2" + // (as UZ has generic index 2 in CB) would be incorrect, + // because it breaks the overload/override chain. + // + // the code below identifies that CB.Mth is an override + // of a generic base type method, and that CB.UZ maps to + // CA.TY in the base Mth(), and prefers the index of CA.TY + // (i.e. 1) over CB.UZ (i.e. 2), to correctly translate + // the override as "Mth-generic-$1". + + if ( parameter.Type == GenericParameterType.Type + && defMethod != null + && defMethod.IsVirtual && (! defMethod.IsNewSlot)) + { + var thisClass = defMethod.DeclaringType; + if (thisClass.BaseType is GenericInstanceType baseClass) + { + var baseGenericArgs = baseClass.GenericArguments; + + foreach (var baseMethod in CilType.AsDefinition(baseClass).Methods) + { + if ( baseMethod.IsVirtual + && CompareMethods(defMethod, baseMethod)) + { + // we found a base class method that matches + // the overriding method, so take the position + // (i.e. the generic parameter index within + // generic type) from the base method parameter + + if (baseMethod.Parameters[parameterIndex].ParameterType + .GetElementType() is GenericParameter baseParameter + && baseGenericArgs.Count > baseParameter.Position + && baseGenericArgs[baseParameter.Position] == parameter) + { + return baseParameter.Position; + } + } + } + } + } + + // in any other case, for example if this is not an + // overriding method, or if the base type is not generic, + // then select the position within the current type + + return parameter.Position; + } + } @@ -485,9 +552,25 @@ namespace SpaceFlint.CilToJava { var prefixType = MethodIsShadowing(defMethod); - if (prefixType == null && DeclType.IsInterface && (! DeclType.IsRetainName)) + if (prefixType == null && (! DeclType.IsRetainName)) { - prefixType = DeclType; + if (DeclType.IsInterface) + { + prefixType = DeclType; + } + else + { + // if a class inherits from system.IDisposable, and + // defines a non-RetainName close() method, then we + // must rename the method to prevent collision with + // java.lang.AutoCloseable.close(). + // see also ConvertInterfaceCall() in CodeCall module. + if ( defMethod.Name == "close" && (! IsRetainName) + && DeclType.AssignableTo(SystemIDisposable)) + { + prefixType = DeclType; + } + } } if (prefixType != null) @@ -876,5 +959,8 @@ namespace SpaceFlint.CilToJava internal static readonly JavaMethodRef ValueClone = new JavaMethod("system-ValueMethod-Clone", CilType.SystemValueType); + internal static readonly JavaType SystemIDisposable = + new JavaType(0, 0, "system.IDisposable"); + } } diff --git a/CilToJava/src/CodeBuilder.cs b/CilToJava/src/CodeBuilder.cs index 3d1bf00..ab973b2 100644 --- a/CilToJava/src/CodeBuilder.cs +++ b/CilToJava/src/CodeBuilder.cs @@ -28,6 +28,7 @@ namespace SpaceFlint.CilToJava internal JavaStackMap stackMap; internal int lineNumber; + internal int numCastableInterfaces; @@ -51,8 +52,9 @@ namespace SpaceFlint.CilToJava o.defMethodBody = defMethod.Body; o.newMethod = newMethod; o.method = myMethod; + o.numCastableInterfaces = numCastableInterfaces; - o.Process(numCastableInterfaces); + o.Process(); } catch (Exception e) { @@ -77,7 +79,7 @@ namespace SpaceFlint.CilToJava - void Process(int numCastableInterfaces) + void Process() { code = newMethod.Code = new JavaCode(newMethod); var oldLabel = code.SetLabel(0xFFFF); @@ -86,7 +88,7 @@ namespace SpaceFlint.CilToJava arrays = new CodeArrays(code, locals); exceptions = new CodeExceptions(defMethodBody, code, locals); - InsertMethodInitCode(numCastableInterfaces); + InsertMethodInitCode(); code.SetLabel(oldLabel); @@ -101,7 +103,7 @@ namespace SpaceFlint.CilToJava - void InsertMethodInitCode(int numCastableInterfaces) + void InsertMethodInitCode() { if (method.IsStatic) { @@ -135,7 +137,7 @@ namespace SpaceFlint.CilToJava // init the array of generic interfaces InterfaceBuilder.InitInterfaceArrayField( - method.DeclType, numCastableInterfaces, code); + method.DeclType, numCastableInterfaces, code, 0); // in any constructor, we want to allocate boxed instance fields ValueUtil.InitializeInstanceFields(newMethod.Class, method.DeclType, diff --git a/CilToJava/src/CodeCall.cs b/CilToJava/src/CodeCall.cs index d4d22b4..a4651b6 100644 --- a/CilToJava/src/CodeCall.cs +++ b/CilToJava/src/CodeCall.cs @@ -180,6 +180,7 @@ namespace SpaceFlint.CilToJava var currentClass = method.DeclType; var callClass = callMethod.DeclType; + bool isCallToClone = false; byte op; if (callMethod.IsStatic) { @@ -245,10 +246,7 @@ namespace SpaceFlint.CilToJava } if (callMethod.Name == "clone" && callMethod.Parameters.Count == 0) - { - // if calling clone on the super object, implement Cloneable - code.Method.Class.AddInterface("java.lang.Cloneable"); - } + isCallToClone = true; } else { @@ -272,6 +270,22 @@ namespace SpaceFlint.CilToJava ClearMethodArguments(callMethod, (op == 0xB7)); PushMethodReturnType(callMethod); + + if (isCallToClone) + { + // if calling clone on the super object, implement Cloneable + code.Method.Class.AddInterface("java.lang.Cloneable"); + + if (numCastableInterfaces != 0) + { + code.NewInstruction( + 0xC0 /* checkcast */, method.DeclType, null); + + // init the array of generic interfaces + InterfaceBuilder.InitInterfaceArrayField( + method.DeclType, numCastableInterfaces, code, -1); + } + } } return true; @@ -306,7 +320,12 @@ namespace SpaceFlint.CilToJava callClass = CilType.From(CilType.SystemValueType); } else + { + if (ConvertInterfaceCall(callClass, callMethod)) + return true; + op = 0xB9; // invokeinterface + } } else { @@ -468,6 +487,40 @@ namespace SpaceFlint.CilToJava + bool ConvertInterfaceCall(CilType callClass, CilMethod callMethod) + { + // convert a call to System.IDisposable.Dispose() to call + // java.lang.AutoCloseable.close(), so a "using" statement + // can reference java classes imported by DotNetImporter. + // + // note that our system.IDisposable in baselib inherits from + // java.lang.AutoCloseable and has a default implementation + // of close() that calls Dispatch(). + // + // see also handling of java.lang.AutoCloseable in DotNetImporter, + // and of close() method in CilMethod.InsertMethodNamePrefix. + + if ( callClass.ClassName == CilMethod.SystemIDisposable.ClassName + && callMethod.Name == "system-IDisposable-Dispose" + && callMethod.Parameters.Count == 0) + { + if (method.DeclType.ClassName == CilMethod.SystemIDisposable.ClassName) + { + // we are compiling system.IDisposable.close(), + // and want to keep the call to Dispose() as is + return false; + } + code.NewInstruction(0xB9 /* invokeinterface */, + new JavaType(0, 0, "java.lang.AutoCloseable"), + new JavaMethodRef("close", JavaType.VoidType)); + ClearMethodArguments(callMethod, false); + return true; + } + return false; + } + + + bool Translate_Constrained(CilMethod callMethod, object data) { var typeRef = data as TypeReference; diff --git a/CilToJava/src/CodeCompare.cs b/CilToJava/src/CodeCompare.cs index 134206f..e937f57 100644 --- a/CilToJava/src/CodeCompare.cs +++ b/CilToJava/src/CodeCompare.cs @@ -763,6 +763,8 @@ namespace SpaceFlint.CilToJava indexLocalVars1 += indexLocalVars0;*/ int resetLength = resetLocals.Length; + bool canBreakEarly = true; + var inst = targetInst; while (inst != branchInst) { @@ -794,6 +796,29 @@ namespace SpaceFlint.CilToJava (ushort) (inst.Offset + 1), resetLocals); } + // if we hit a 'return' or 'throw', then we can (and should) + // break early, to minimize the area of effect, and the risk + // of breaking code between L.A and L.C. (consider that a + // .Net compiler may generate code between labels L.A and L.C + // that expects the local to be valid.) + + var flowControl = inst.OpCode.FlowControl; + + if ( flowControl == Mono.Cecil.Cil.FlowControl.Branch + || flowControl == Mono.Cecil.Cil.FlowControl.Cond_Branch) + { + // indicate that we see non-sequential control flow + canBreakEarly = false; + } + else if ( canBreakEarly + && ( flowControl == Mono.Cecil.Cil.FlowControl.Return + || flowControl == Mono.Cecil.Cil.FlowControl.Throw)) + { + // if we only saw sequential control flow, then we can + // break early as soon as we see a 'return' or a 'throw' + break; + } + inst = inst.Next; } } diff --git a/CilToJava/src/CodeField.cs b/CilToJava/src/CodeField.cs index bd01328..a461ccd 100644 --- a/CilToJava/src/CodeField.cs +++ b/CilToJava/src/CodeField.cs @@ -125,6 +125,10 @@ namespace SpaceFlint.CilToJava byte op; if (! isStatic) { + if (method.IsConstructor && + LoadFieldInConstructor(fldName, fldType, fldClass)) + return true; + PopObjectAndLoadFromSpan(fldClass); op = 0xB4; // getfield } @@ -180,6 +184,108 @@ namespace SpaceFlint.CilToJava + bool LoadFieldInConstructor(string fldName, CilType fldType, CilType fldClass) + { + // Java does not allow 'getfield' instructions on an 'uninitializedThis' + // until the call to super constructor. but in .Net this is permitted, + // and the F# compiler generates such code in some cases. we try to work + // around this, by identifying the constructor parameter that was used in + // an earlier 'putfield', and loading that, instead of doing 'getfield'. + + bool isUninitializedThisField = + ( method.IsConstructor && fldClass.Equals(method.DeclType) + && stackMap.GetLocal(0).Equals(JavaStackMap.UninitializedThis)); + if (! isUninitializedThisField) + return false; + + // most recent instruction before the 'getfield' + // should have been 'ldarg.0', translated to 'aload_0' + int i = code.Instructions.Count - 1; + if (i > 0 && code.Instructions[i].Opcode == 0x19 /* aload */ + && code.Instructions[i].Data is int thisIndex + && thisIndex == 0 + // note that we pop the stack here + && code.StackMap.PopStack(CilMain.Where) + .Equals(JavaStackMap.UninitializedThis)) + { + // try to find an earlier 'putfield' instruction for the field + while (i-- > 0) + { + var inst = code.Instructions[i]; + if ( inst.Opcode == 0xB5 /* putfield */ + && method.DeclType.Equals(inst.Class)) + { + var instField = (JavaFieldRef) inst.Data; + if ( fldName == instField.Name + && fldType.Equals(instField.Type)) + { + // try to find the load instruction that was used + // to load the value, for that earlier 'putfield' + if (FindLoadLocal(i - 1, code.Instructions.Count - 1)) + return true; + } + } + } + } + throw new Exception("load from uninitialized this"); + + + bool FindLoadLocal(int prevIdx, int lastIdx) + { + var prevInst = code.Instructions[prevIdx]; + + if ( prevIdx > 0 && prevInst.Opcode == 0xB8 /* invokestatic */ + && (JavaMethodRef) prevInst.Data is JavaMethodRef prevMethod) + { + if (prevMethod.Name == "Box") + { + // we possibly found the sequence used in boxing - + // xload value, invokestatic Value.Box(), putfield + + prevInst = code.Instructions[prevIdx - 1]; + } + else if ( prevIdx > 5 && prevMethod.Name == "Copy" + && code.Instructions[prevIdx - 1].Opcode == 0x5A /* dup_x1 */ + && code.Instructions[prevIdx - 2].Opcode == 0xB8 /* invokestatic */ + && code.Instructions[prevIdx - 3].Opcode == 0x19 /* aload (type) */ + && code.Instructions[prevIdx - 4].Opcode == 0xB8 /* invokestatic */ + && code.Instructions[prevIdx - 5].Opcode == 0x19 /* aload (value) */) + { + // we possibly found the sequence used in generics - + // aload (local to use for store value) + // invokestatic Generic.Load + // aload (generic type parameter) + // invokestatic Generic.New + // dup_x1 + // invokestatic Generic.Copy <=== prevIdx + // putfield + // (see also StoreInstance method in this module) + + prevInst = code.Instructions[prevIdx - 5]; + } + } + + if ( prevInst.Opcode >= 0x15 /* iload, lload, fload, */ + && prevInst.Opcode <= 0x19 /* dload, aload */ + && prevInst.Data is int localIndex) + { + // if the instruction before the 'putfield' is a load + // from local, and assuming this local is a parameter, + // then we can just load this local again, to replace + // a 'getfield' instruction that cannot access 'this' + + code.Instructions[lastIdx].Opcode = prevInst.Opcode; + code.Instructions[lastIdx].Data = localIndex; + stackMap.PushStack(code.StackMap.GetLocal(localIndex)); + return true; + } + + return false; + } + } + + + bool StoreFieldValue(string fldName, CilType fldType, CilType fldClass, bool isStatic, bool isVolatile) { diff --git a/CilToJava/src/CodeLocals.cs b/CilToJava/src/CodeLocals.cs index 36587c3..df5e5f5 100644 --- a/CilToJava/src/CodeLocals.cs +++ b/CilToJava/src/CodeLocals.cs @@ -461,9 +461,10 @@ namespace SpaceFlint.CilToJava } else { - if (localType.IsGenericParameter && (! localType.IsByReference)) + if (localType.IsGenericParameter) { - GenericUtil.ValueLoad(code); + if (! localType.IsByReference) + GenericUtil.ValueLoad(code); localType = CilType.From(JavaType.ObjectType); } @@ -612,7 +613,7 @@ namespace SpaceFlint.CilToJava } - + /* public List<(CilType, int)> GetUninitializedVariables() { List<(CilType, int)> list = null; @@ -629,7 +630,7 @@ namespace SpaceFlint.CilToJava } return list; } - + */ public (CilType, int) GetLocalFromLoadInst(Code op, object data) diff --git a/CilToJava/src/CodeMisc.cs b/CilToJava/src/CodeMisc.cs index e6b3e6c..8fc4134 100644 --- a/CilToJava/src/CodeMisc.cs +++ b/CilToJava/src/CodeMisc.cs @@ -346,13 +346,17 @@ namespace SpaceFlint.CilToJava if (CodeSpan.LoadStore(false, intoType, null, dataType, code)) return; - if ( (! dataType.IsReference) - && intoType is BoxedType intoBoxedType - && dataType.PrimitiveType == intoBoxedType.UnboxedType.PrimitiveType) + if ( intoType is BoxedType intoBoxedType + && dataType.PrimitiveType == + intoBoxedType.UnboxedType.PrimitiveType) { - // 'stobj primitive' with a primitive value on the stack - intoBoxedType.SetValueOV(code); - return; + // 'stobj primitive' with a primitive value on the stack, + // or 'stobj primitive[]' with a primitive array on the stack + if ((! dataType.IsReference) || dataType.IsArray) + { + intoBoxedType.SetValueOV(code); + return; + } } code.StackMap.PushStack(intoType); diff --git a/CilToJava/src/GenericUtil.cs b/CilToJava/src/GenericUtil.cs index 53416b1..b7c09c0 100644 --- a/CilToJava/src/GenericUtil.cs +++ b/CilToJava/src/GenericUtil.cs @@ -178,7 +178,8 @@ namespace SpaceFlint.CilToJava // JavaClass CreateClass(JavaClass fromClass) => - CilMain.CreateInnerClass(fromClass, fromClass.Name + "$$static", 0); + CilMain.CreateInnerClass(fromClass, fromClass.Name + "$$static", 0, + markGenericEntity: true); // // create a private instance field to hold the runtime type @@ -345,13 +346,14 @@ namespace SpaceFlint.CilToJava } if (! anyVariance) { + /* removed; see IComparer.cs in baselib if (fromType.JavaName == "system.collections.generic.IComparer$$1") { // force a variance string for an interface that we create // as an abstract class; see also IComparer.cs in baselib varianceString = "I"; } - else + else*/ return; } @@ -487,6 +489,16 @@ namespace SpaceFlint.CilToJava { if (loadIndex < 0) { + if ( code.Method.Class != null + && (code.Method.Class + .Flags & JavaAccessFlags.ACC_INTERFACE) != 0) + { + // a method compiled as part of an interface does not + // have access to the generic-type member field + throw CilMain.Where.Exception( + "unsupported generic argument reference in interface method"); + } + // generic type is accessible through the generic-type member field code.NewInstruction(0x19 /* aload */, null, (int) 0); diff --git a/CilToJava/src/InterfaceBuilder.cs b/CilToJava/src/InterfaceBuilder.cs index 0544332..8d6cea7 100644 --- a/CilToJava/src/InterfaceBuilder.cs +++ b/CilToJava/src/InterfaceBuilder.cs @@ -74,7 +74,6 @@ namespace SpaceFlint.CilToJava // the RuntimeType constructor in baselib uses IGenericEntity // marker interface to identify generic classes. note that // real generic types implement IGenericObject -> IGenericEntity. - theClass.AddInterface("system.IGenericEntity"); } @@ -124,14 +123,14 @@ namespace SpaceFlint.CilToJava // if the class implements a generic interface for multiple types, // then we need a method suffix to differentiate between the methods. // see also: CilMethod::InsertMethodNamePrefix - string methodSuffix = ""; + /*string methodSuffix = ""; foreach (var genericType in ifc.GenericTypes) - methodSuffix += "--" + CilMethod.GenericParameterSuffixName(genericType); + methodSuffix += "--" + CilMethod.GenericParameterSuffixName(genericType);*/ foreach (var ifcMethod in ifc.Methods) { // build proxy classes: proxy sub-class -> this class - BuildGenericProxy(ifcMethod, methodSuffix, intoType, theMethods, ifcClass); + BuildGenericProxy(ifcMethod, /*methodSuffix,*/ intoType, theMethods, ifcClass); } } } @@ -147,7 +146,8 @@ namespace SpaceFlint.CilToJava // reference as a parameter and initializes the instance field. var newClass = CilMain.CreateInnerClass(parentClass, - parentClass.Name + "$$generic" + ifcNumber.ToString()); + parentClass.Name + "$$generic" + ifcNumber.ToString(), + markGenericEntity: true); var fld = new JavaField(); fld.Name = ParentFieldName; @@ -199,7 +199,7 @@ namespace SpaceFlint.CilToJava // public static void InitInterfaceArrayField(CilType toType, int numCastableInterfaces, - JavaCode code) + JavaCode code, int objectIndex) { // if a type has castable interface (as counted by CastableInterfaceCount), // then we need to initialize the helper array field, for use by the @@ -208,8 +208,14 @@ namespace SpaceFlint.CilToJava if (numCastableInterfaces == 0) return; - code.NewInstruction(0x19 /* aload */, null, (int) 0); + // objectIndex specifies the local index for the object reference, + // e.g. 0 for the 'this' object, or -1 for top of stack. + if (objectIndex == -1) + code.NewInstruction(0x59 /* dup */, null, null); + else + code.NewInstruction(0x19 /* aload */, null, objectIndex); code.StackMap.PushStack(toType); + code.NewInstruction(0xBB /* new */, AtomicReferenceArrayType, null); code.StackMap.PushStack(AtomicReferenceArrayType); code.NewInstruction(0x59 /* dup */, null, null); @@ -381,7 +387,7 @@ namespace SpaceFlint.CilToJava - public static void BuildGenericProxy(CilInterfaceMethod ifcMethod, string methodSuffix, + public static void BuildGenericProxy(CilInterfaceMethod ifcMethod, /*string methodSuffix,*/ CilType intoType, List classMethods, JavaClass ifcClass) { @@ -408,9 +414,23 @@ namespace SpaceFlint.CilToJava { // more than one method may match, if a derived type overrides // or hides a method that also exists in a base type. but the - // derived (primary) type methods always come first. + // derived (primary) type methods always come first if (targetMethod == null) targetMethod = clsMethod.Method; + + // if a second method matches, and the set of generic types + // in its signature exactly matches the interface method we + // are looking for, then prefer this method. when a class + // implements same-name methods from multiple interfaces, + // this is needed to pick the right method. + // see also ResolvedGenericTypes in CilInterfaceMethod. + + else if ( clsMethod.ResolvedGenericTypes.Length != 0 + && clsMethod.ResolvedGenericTypes + == ifcMethod.ResolvedGenericTypes) + { + targetMethod = clsMethod.Method; + } } } } diff --git a/CilToJava/src/TypeBuilder.cs b/CilToJava/src/TypeBuilder.cs index c7a9bdc..0c06e9a 100644 --- a/CilToJava/src/TypeBuilder.cs +++ b/CilToJava/src/TypeBuilder.cs @@ -100,8 +100,24 @@ namespace SpaceFlint.CilToJava // Android 'D8' desugars static methods on an interface by // moving into a separate class, so we do it ourselves. // see also system.RuntimeType.CreateGeneric() in baselib - infoClass = CilMain.CreateInnerClass(jclass, jclass.Name + "$$info"); + infoClass = CilMain.CreateInnerClass(jclass, jclass.Name + "$$info", + markGenericEntity: true); CilMain.JavaClasses.Add(infoClass); + + // Android 'R8' (ProGuard) might discard this new class, + // so insert a dummy field with the type of the class. + // see also ProGuard rules in IGenericEntity in baselib + var infoClassField = new JavaField(); + infoClassField.Name = "-generic-info-class"; + infoClassField.Type = new JavaType(0, 0, infoClass.Name); + infoClassField.Class = jclass; + infoClassField.Flags = JavaAccessFlags.ACC_PUBLIC + | JavaAccessFlags.ACC_STATIC + | JavaAccessFlags.ACC_FINAL + | JavaAccessFlags.ACC_SYNTHETIC; + if (jclass.Fields == null) + jclass.Fields = new List(1); + jclass.Fields.Add(infoClassField); } GenericUtil.CreateGenericInfoMethod(infoClass, dataClass, myType); @@ -347,10 +363,12 @@ namespace SpaceFlint.CilToJava attrs &= ~FieldAttributes.Static; } - if ((attrs & FieldAttributes.InitOnly) != 0) + if (0 != (attrs & ( FieldAttributes.InitOnly + | FieldAttributes.Literal))) { flags |= JavaAccessFlags.ACC_FINAL; - attrs &= ~FieldAttributes.InitOnly; + attrs &= ~( FieldAttributes.InitOnly + | FieldAttributes.Literal); } if ((attrs & FieldAttributes.NotSerialized) != 0) @@ -359,8 +377,7 @@ namespace SpaceFlint.CilToJava attrs &= ~FieldAttributes.NotSerialized; } - attrs &= ~( FieldAttributes.Literal - | FieldAttributes.HasFieldRVA + attrs &= ~( FieldAttributes.HasFieldRVA | FieldAttributes.HasDefault | FieldAttributes.SpecialName | FieldAttributes.RTSpecialName); @@ -385,10 +402,8 @@ namespace SpaceFlint.CilToJava for (int i = 0; i < n; i++) { var defMethod = cilType.Methods[i]; - /* if (defMethod.HasCustomAttribute("Discard")) continue; // if decorated with [java.attr.Discard], don't export to java - */ var genericMark = CilMain.GenericStack.Mark(); var myMethod = CilMain.GenericStack.EnterMethod(defMethod); diff --git a/CilToJava/src/ValueUtil.cs b/CilToJava/src/ValueUtil.cs index f060cb3..0c2a676 100644 --- a/CilToJava/src/ValueUtil.cs +++ b/CilToJava/src/ValueUtil.cs @@ -18,7 +18,7 @@ namespace SpaceFlint.CilToJava valueClass.Super = CilType.SystemValueType.ClassName; CreateDefaultConstructor(valueClass, fromType, numCastableInterfaces, true); - CreateValueMethods(valueClass, fromType); + CreateValueMethods(valueClass, fromType, numCastableInterfaces); if ((valueClass.Flags & JavaAccessFlags.ACC_ABSTRACT) == 0) valueClass.Flags |= JavaAccessFlags.ACC_FINAL; @@ -58,7 +58,8 @@ namespace SpaceFlint.CilToJava } // init the array of generic interfaces - InterfaceBuilder.InitInterfaceArrayField(fromType, numCastableInterfaces, code); + InterfaceBuilder.InitInterfaceArrayField( + fromType, numCastableInterfaces, code, 0); if (initFields) { @@ -82,11 +83,12 @@ namespace SpaceFlint.CilToJava - static void CreateValueMethods(JavaClass valueClass, CilType fromType) + static void CreateValueMethods(JavaClass valueClass, CilType fromType, + int numCastableInterfaces) { CreateValueClearMethod(valueClass, fromType); CreateValueCopyToMethod(valueClass, fromType); - CreateValueCloneMethod(valueClass, fromType); + CreateValueCloneMethod(valueClass, fromType, numCastableInterfaces); // // system-ValueMethod-Clear() resets all fields to their default value @@ -180,9 +182,11 @@ namespace SpaceFlint.CilToJava // any boxed fields // - void CreateValueCloneMethod(JavaClass valueClass, CilType fromType) + void CreateValueCloneMethod(JavaClass valueClass, CilType fromType, + int numCastableInterfaces) { - var code = CilMain.CreateHelperMethod(valueClass, CilMethod.ValueClone, 1, 3); + var code = CilMain.CreateHelperMethod(valueClass, CilMethod.ValueClone, + 1, (numCastableInterfaces == 0) ? 3 : 5); bool atLeastOneField = false; code.NewInstruction(0x19 /* aload */, null, (int) 0); @@ -215,7 +219,15 @@ namespace SpaceFlint.CilToJava } if (! atLeastOneField) - code.NewInstruction(0xC0 /* checkcast */, CilType.SystemValueType, null); + code.NewInstruction(0xC0 /* checkcast */, fromType, null); + + if (numCastableInterfaces != 0) + { + code.StackMap = new JavaStackMap(); + // init the array of generic interfaces + InterfaceBuilder.InitInterfaceArrayField( + fromType, numCastableInterfaces, code, -1); + } code.NewInstruction(fromType.ReturnOpcode, null, null); } diff --git a/JavaBinary/src/JavaCodeWriter.cs b/JavaBinary/src/JavaCodeWriter.cs index 00ea198..fbff37f 100644 --- a/JavaBinary/src/JavaCodeWriter.cs +++ b/JavaBinary/src/JavaCodeWriter.cs @@ -189,6 +189,8 @@ namespace SpaceFlint.JavaBinary else if (inst.Data is double vDouble) { + if (FillInstruction_ConstLoad_Double(inst, vDouble)) + return; constantIndex = wtr.ConstDouble(vDouble); op = 0x14; } @@ -215,7 +217,11 @@ namespace SpaceFlint.JavaBinary } else if (inst.Data is float vFloat) + { + if (FillInstruction_ConstLoad_Float(inst, vFloat)) + return; constantIndex = wtr.ConstFloat(vFloat); + } else if (inst.Data is string vString) constantIndex = wtr.ConstString(vString); @@ -284,6 +290,49 @@ namespace SpaceFlint.JavaBinary + bool FillInstruction_ConstLoad_Float(Instruction inst, float value) + { + if (value == 0.0f) + { + inst.Bytes = new byte[1]; + inst.Bytes[0] = (byte) 11; // fconst_0 + } + else if (value == 1.0f) + { + inst.Bytes = new byte[1]; + inst.Bytes[0] = (byte) 12; // fconst_1 + } + else if (value == 2.0f) + { + inst.Bytes = new byte[1]; + inst.Bytes[0] = (byte) 13; // fconst_2 + } + else + return false; + return true; + } + + + + bool FillInstruction_ConstLoad_Double(Instruction inst, double value) + { + if (value == 0.0) + { + inst.Bytes = new byte[1]; + inst.Bytes[0] = (byte) 14; // dconst_0 + } + else if (value == 1.0) + { + inst.Bytes = new byte[1]; + inst.Bytes[0] = (byte) 15; // dconst_1 + } + else + return false; + return true; + } + + + bool FillInstruction_Local(JavaWriter wtr, Instruction inst, byte op) { if (op == 0x84) diff --git a/JavaBinary/src/JavaStackMap.cs b/JavaBinary/src/JavaStackMap.cs index a02e0de..8c8c381 100644 --- a/JavaBinary/src/JavaStackMap.cs +++ b/JavaBinary/src/JavaStackMap.cs @@ -589,7 +589,11 @@ namespace SpaceFlint.JavaBinary offset = (ushort) (offset - (lastOffset + 1)); item.deltaOffset = offset; + // discard any trailing 'top' elements for out-of-scape locals int numLocals = frame.locals.Count; + while (numLocals > 0 && frame.locals[numLocals - 1].Equals(Top)) + numLocals--; + var localsList = new List(numLocals); for (int i = 0; i < numLocals; i += frame.locals[i].Category) localsList.Add(JavaTypeToVerificationType(frame.locals[i], wtr)); diff --git a/LICENSE b/LICENSE index 67d27c6..bad4627 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 spaceflint7 +Copyright (c) 2020, 2021 spaceflint7 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Main/src/DotNetImporter.cs b/Main/src/DotNetImporter.cs index a7d04b9..94fd9db 100644 --- a/Main/src/DotNetImporter.cs +++ b/Main/src/DotNetImporter.cs @@ -182,6 +182,22 @@ public class DotNetImporter } } + // arrange Java inner classes as CIL nested classes, + // and add Java outer classes to the CIL module itself + + foreach (var jclass in classes2) + { + Where.Push($"class '{jclass.Name}'"); + + var cilType = typeMap[jclass.Name] as TypeDefinition; + AddClassToDeclaringType(cilType, jclass); + + Where.Pop(); + } + + // establish links between classes and their + // super classes and interfaces + foreach (var jclass in classes2) { Where.Push($"class '{jclass.Name}'"); @@ -335,7 +351,7 @@ public class DotNetImporter - public void LinkCilTypesByClass(TypeDefinition cilType, JavaClass jclass) + public void AddClassToDeclaringType(TypeDefinition cilType, JavaClass jclass) { if (jclass.IsInnerClass()) { @@ -363,7 +379,12 @@ public class DotNetImporter { module.Types.Add(cilType); } + } + + + public void LinkCilTypesByClass(TypeDefinition cilType, JavaClass jclass) + { var superName = jclass.Super; if (superName == "java.lang.Enum") superName = null; @@ -379,6 +400,16 @@ public class DotNetImporter cilType.Interfaces.Add( new InterfaceImplementation(cilSuperTypeRef)); } + if (jclass.Name == "java.lang.AutoCloseable") + { + // make java.lang.AutoCloseable extend System.IDisposable, + // to allow use of imported classes in "using" statement. + // see also ConvertInterfaceCall in CodeCall module. + var iDisposableRef = new TypeReference( + "System", "IDisposable", module, module.TypeSystem.CoreLibrary); + cilType.Interfaces.Add( + new InterfaceImplementation(iDisposableRef)); + } } else { @@ -707,7 +738,7 @@ public class DotNetImporter var method = new MethodDefinition( "op_Explicit", attrs, CilTypeReference(JavaType.ClassType)); method.Parameters.Add(new ParameterDefinition( - "type", 0, new TypeReference("System", "Type", + "source", 0, new TypeReference("System", "Type", module, module.TypeSystem.CoreLibrary))); SetCommonMethodBody(method); cilType.Methods.Add(method); diff --git a/Main/src/DotNetPrinter.cs b/Main/src/DotNetPrinter.cs index caf5b34..1747e4e 100644 --- a/Main/src/DotNetPrinter.cs +++ b/Main/src/DotNetPrinter.cs @@ -44,7 +44,7 @@ public class DotNetPrinter { txt.Write("{0}{1}", (comma ? ", " : string.Empty), - intrface.InterfaceType.FullName); + intrface.InterfaceType?.FullName ?? "(null)"); comma = true; } diff --git a/Tests/src/TestArray.cs b/Tests/src/TestArray.cs index b86c7a6..6b9f3a8 100644 --- a/Tests/src/TestArray.cs +++ b/Tests/src/TestArray.cs @@ -383,6 +383,20 @@ namespace Tests { Console.WriteLine("Caught exception " + e.GetType()); } + + // test generic cast + + var tupleArray = new Tuple[3] { + new Tuple(1, "one"), + new Tuple(2, "two"), + new Tuple(3, "three"), + }; + var tupleEnum = + ((System.Collections.Generic.IEnumerable>) tupleArray) + .GetEnumerator(); + while (tupleEnum.MoveNext()) + System.Console.Write(tupleEnum.Current); + System.Console.WriteLine(); } void TestValue(bool selector) @@ -425,6 +439,21 @@ namespace Tests list.Add(arr1); var arr2 = list.ToArray(); PrintArray(arr2[0]); + + var arr3 = new Guid[] { + new Guid("33333333333333333333333333333333"), + new Guid("22222222222222222222222222222222"), + new Guid("11111111111111111111111111111111"), + }; + System.Array.Sort(arr3, new MyComparer()); + PrintArray(arr3); + } + + public class MyComparer : System.Collections.Generic.IComparer + where T : System.IComparable + { + public int Compare(T x, T y) => x.CompareTo(y); + object Clone() => MemberwiseClone(); } } diff --git a/Tests/src/TestGeneric.cs b/Tests/src/TestGeneric.cs index 5d08ff6..8680b4c 100644 --- a/Tests/src/TestGeneric.cs +++ b/Tests/src/TestGeneric.cs @@ -24,6 +24,7 @@ namespace Tests TestGenericInterface2(); TestGenericOverload3(); TestGenericMethodWithEnum(); + TestGenericByRef(); } // @@ -179,7 +180,7 @@ namespace Tests public virtual int Get(T2 v) => 2; } - class MyOv2 : MyOv1 + class MyOv2 : MyOv1 { public override int Get(T1 v) => 3; public override int Get(T2 v) => 4; @@ -190,6 +191,8 @@ namespace Tests var a = new MyOv1(); Console.Write(a.Get(1)); Console.Write(a.Get(true)); + Console.Write(((I1) a).Get(true)); + Console.Write(((I2) a).Get(1)); var b = new MyOv2(); Console.Write(b.Get(1)); Console.WriteLine(b.Get(true)); @@ -224,6 +227,7 @@ namespace Tests public class CccC1 : CccB1 { public override void DoIt(ref T1 a, ref int b) => Console.Write("OK1 "); } public class CccC2 : CccB1 { public override void DoIt(ref T1 a, ref Version b) => Console.Write("OK2 "); } public class CccC3 : CccB1 { public override void DoIt(ref T1 a, ref object b) => Console.Write("OK3 "); } + public class CccC4 : CccB1{ public override void DoIt(ref T1 a, ref T2 b) => Console.Write("OK4 "); } void TestGenericOverload3() { @@ -235,10 +239,12 @@ namespace Tests CccB1 c1 = new CccC1(); CccB1 c2 = new CccC2(); CccB1 c3 = new CccC3(); + CccB1c4 = new CccC4(); c1.DoIt(ref bFalse, ref iZero); c2.DoIt(ref bFalse, ref vZero); c3.DoIt(ref bFalse, ref oZero); + c4.DoIt(ref bFalse, ref oZero); Console.WriteLine(); } @@ -258,5 +264,20 @@ namespace Tests } } + // + // TestGenericByRef + // + + void TestGenericByRef() + { + var guid = new Guid("12345678123456781234567812345678"); + Helper(ref guid, true); + + void Helper(ref T arg, bool cond) where T : IFormattable + { + Console.WriteLine(arg.ToString(cond ? "" : "D", null)); + } + } + } } diff --git a/Tests/src/TestReflection.cs b/Tests/src/TestReflection.cs index 7f07738..4125c09 100644 --- a/Tests/src/TestReflection.cs +++ b/Tests/src/TestReflection.cs @@ -98,6 +98,9 @@ namespace Tests DisplayGenericType(tT, "Type parameter T from Base"); //DisplayGenericType(tF, "Field type, G>"); DisplayGenericType(tNested, "Nested type in Derived"); + + DisplayGenericType(tDerived.GetNestedType("Nested"), + "Nested type in Derived (2)"); } public static void DisplayGenericType(Type t, string caption) diff --git a/Tests/src/TestSystem.cs b/Tests/src/TestSystem.cs index 6e6e403..03cf540 100644 --- a/Tests/src/TestSystem.cs +++ b/Tests/src/TestSystem.cs @@ -13,6 +13,7 @@ namespace Tests { TestSuppressGC(); TestUnhandledException(); + TestUsing(); } // @@ -46,6 +47,26 @@ namespace Tests => Console.WriteLine("In Unhandled Exception Handler"); } + // + // TestUnhandledException + // + + public class MyDisposable : System.IDisposable + { + public void Dispose() => Console.Write("Disposed "); + public void close() => Console.Write("Closing "); + } + + void TestUsing() + { + using (var myDisposable = new MyDisposable()) + { + myDisposable.Dispose(); + myDisposable.close(); + } + Console.WriteLine(); + } + } } diff --git a/USAGE-ANDROID.md b/USAGE-ANDROID.md index 5d23ef5..d90aa0c 100644 --- a/USAGE-ANDROID.md +++ b/USAGE-ANDROID.md @@ -1,16 +1,16 @@ #### Goal -- Set up development of an Android app using a .NET language +- Set up development of an Android app using a .NET language. - Either in Visual Studio or using the command line `dotnet` tool. - Use Android Studio to build the app. - - With a Gradle plugin to compile .NET code. + - With a Gradle task to build the .NET project. - - And a Gradle build task to convert the .NET code to Java compiled form. + - And a Gradle task to convert the .NET code to Java compiled form. - Most of development should be possible on Windows without requiring an Android device. @@ -20,10 +20,10 @@ #### Environment Variables - Set the environment variable `MSBUILD_EXE` to point to `MSBuild.exe` program file. - - For example, `C:\Program Files (x86)\Microsoft Visual Studio\\2019\Community\MSBuild\Current\Bin\MSBuild.exe` + - For example, `C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe` -- Set the environment variable `BLUEBONNET_DIR` to point to a directory containing `Bluebonnet.exe`, `PruneMerge.exe`, `Baselib.jar` and `Android.dll`. +- Set the environment variable `BLUEBONNET_DIR` to point to a directory containing `Bluebonnet.exe`, `Baselib.jar` and `Android.dll`. - These files can be downloaded from the [Bluebonnet releases](https://github.com/spaceflint7/bluebonnet/releases) page. @@ -37,7 +37,7 @@ - You may use some other project type or language instead of a C# console app. - - The project must be named `DotNet`. + - The project should be named `DotNet`. - Edit `DotNet.csproj` to add a reference to Android DLL created by Bluebonnet: @@ -158,12 +158,13 @@ - At the top of the `dependencies` section, insert: implementation files("$buildDir/dotnet/dotnet.jar") + implementation files("${System.env.BLUEBONNET_DIR}/baselib.jar") - After the `dependencies` section, append: task buildDotNet { doLast { - delete("${buildDir}/dotnet/DotNet.jar") + delete("${buildDir}/dotnet/dotnet.jar") exec { workingDir "${project.rootDir}" commandLine System.env.MSBUILD_EXE ?: 'msbuild.exe', @@ -176,16 +177,8 @@ } exec { commandLine "${System.env.BLUEBONNET_DIR}/Bluebonnet.exe", - "${buildDir}/dotnet/DotNet.dll", - "${buildDir}/dotnet/DotNet0.jar" - } - def manifest = new XmlSlurper().parse(android.sourceSets.main.manifest.srcFile) - exec { - commandLine "${System.env.BLUEBONNET_DIR}/PruneMerge.exe", - "${buildDir}/dotnet/DotNet0.jar", - "${System.env.BLUEBONNET_DIR}/Baselib.jar", - "${buildDir}/dotnet/DotNet.jar", - ":${manifest.@package}${manifest.application.activity.'@android:name'}" + "${buildDir}/dotnet/dotnet.dll", + "${buildDir}/dotnet/dotnet.jar" } } } @@ -239,6 +232,7 @@ dependencies { implementation files("$buildDir/dotnet/dotnet.jar") + implementation files("${System.env.BLUEBONNET_DIR}/baselib.jar") implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.4.0' testImplementation 'junit:junit:4.+' @@ -248,8 +242,7 @@ task buildDotNet { doLast { - delete("${buildDir}/dotnet/DotNet.jar") - + delete("${buildDir}/dotnet/dotnet.jar") exec { workingDir "${project.rootDir}" commandLine System.env.MSBUILD_EXE ?: 'msbuild.exe', @@ -262,23 +255,49 @@ } exec { commandLine "${System.env.BLUEBONNET_DIR}/Bluebonnet.exe", - "${buildDir}/dotnet/DotNet.dll", - "${buildDir}/dotnet/DotNet0.jar" - } - def manifest = new XmlSlurper().parse(android.sourceSets.main.manifest.srcFile) - exec { - commandLine "${System.env.BLUEBONNET_DIR}/PruneMerge.exe", - "${buildDir}/dotnet/DotNet0.jar", - "${System.env.BLUEBONNET_DIR}/Baselib.jar", - "${buildDir}/dotnet/DotNet.jar", - ":${manifest.@package}${manifest.application.activity.'@android:name'}" + "${buildDir}/dotnet/dotnet.dll", + "${buildDir}/dotnet/dotnet.jar" } } } preBuild.dependsOn buildDotNet -#### Gradle Build Script - Overview +#### ProGuard Rules + +- If you wish to minify your release build using Android R8 (ProGuard), enter the following settings into your `app/proguard-rules.pro` file: + + # + # these rules prevent discarding of generic types + # + -keepclassmembers class * implements system.IGenericEntity { + public static final java.lang.String ?generic?variance; + public static final *** ?generic?info?class; + private system.RuntimeType ?generic?type; + public static final *** ?generic?info?method (...); + (...); + } + + +- Only if you wish to use F#, then enter the settings below as well. They are needed with C# code. + + # + # F# printf + # + -keepclassmembers class microsoft.fsharp.core.PrintfImpl$ObjectPrinter { + *** GenericToString? (...); + } + -keepclassmembers class microsoft.fsharp.core.PrintfImpl$Specializations* { + *** * (...); + } + -keep class microsoft.fsharp.core.CompilationMappingAttribute { *; } + -keep class **$Tags { *; } + -keepclassmembers class * implements java.io.Serializable { + *** get_* (); + } + -keepattributes InnerClasses + +#### To Summarize of All of the Above - In place of Java source files, a new dependency was added on a JAR file - `dotnet.jar`. @@ -286,17 +305,14 @@ - Run `MSBuild` on the .NET project, in `Release` configuration, with the preprocessor define `ANDROID` - - Run `Bluebonnet` on the resulting `dotnet.dll` to create `dotnet0.jar` + - Run `Bluebonnet` on the resulting `dotnet.dll` to create `dotnet.jar` - - Extract the class name for the main activity from the file `AndroidManifest.xml` - - - Run `PruneMerge` to merge `Baselib.jar` (from Bluebonnet) and `dotnet0.jar` into the final `dotnet.jar` - - - All unreferenced classes are discard from the output. - The `buildDotNet` task was set to execute before the Gradle `preBuild` task. +- ProGuard rules were added to prevent stripping fields and methods used by Bluebonnet to support .NET generic types. + #### Test It - Compile and run the project in Android Studio. diff --git a/USAGE.md b/USAGE.md index de40987..4879947 100644 --- a/USAGE.md +++ b/USAGE.md @@ -31,26 +31,51 @@ If the input assembly references other assemblies, they are searched in (1) the .NET C# code which references Java declarations (using a reference assembly, as described above) may use the following syntax to refer to a Java class: - (java.lang.Class) typeof(sometype) + C#: (java.lang.Class) typeof(sometype) + + F#: java.lang.Class.op_Explicit(typeof) (F#) where `sometype` can be any imported Java class, or non-generic .NET type. -#### Access to a Java class object +#### Delegates to Java functional interface Java functional interfaces are supported via an artificial delegate, for example: - java.lang.Thread.setDefaultUncaughtExceptionHandler( - ( (java.lang.Thread.UncaughtExceptionHandler.Delegate) ( + C#: java.lang.Thread.setDefaultUncaughtExceptionHandler( + ( (java.lang.Thread.UncaughtExceptionHandler.Delegate) ( (java.lang.Thread p1, java.lang.Throwable p2) => - { .... }) ).AsInterface() ); + { ...code... }) ).AsInterface() ); -In this example, `java.lang.Thread.UncaughtExceptionHandler` is the functional interface, which gets an artificial delegate named `Delegate` as a nested type. The C# lambda is cast to this delegate, and then the `AsInterface` method is invoked, to convert the delegate to a Java interface. + F#: java.lang.Thread.setDefaultUncaughtExceptionHandler( + (java.lang.Thread.UncaughtExceptionHandler.Delegate ( + fun (p1: java.lang.Thread) (p2: java.lang.Throwable) -> + ...code... )).AsInterface()) + +In this example, `java.lang.Thread.UncaughtExceptionHandler` is the functional interface, which gets an artificial delegate named `Delegate` as a nested type. The lambda is cast to this delegate, and then the `AsInterface` method is invoked, to convert the delegate to a Java interface. + +#### Java interfaces in F# code + +F# generates [explicit method overrides](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/interfaces/explicit-interface-implementation) for implemented interfaces, which is not compatible with interfaces imported from Java. To work around this, a Java interface must be implemented twice in an F# type: + + type MyType () = + + // Declare the interface so that it is listed as implemented by the type. + // The actual method implementations are discarded. + + interface java.lang.Thread.UncaughtExceptionHandler with + [] member this.uncaughtException (_, _) = () + + // Provide implicit implementations for the necessary methods. + // Parameters types must match the interface methods. + + [] + member this.uncaughtException (p1: java.lang.Thread, p2: java.lang.Throwable) = () # Attributes Bluebonnet recognizes the following attributes: -- `[java.attr.DiscardAttribute]` on a top-level type (class/struct/interface/delegate) to exclude the type from output. For example, [Baselib/`Object.cs`](https://github.com/spaceflint7/bluebonnet/blob/master/Baselib/src/System/Object.cs) declares a `java.lang.Object` type with a `getClass` method, but there is no need to actually emit a Java class for `java.lang.Object`. For an example of this outside of Baselib, see [BNA/`Import.cs`](https://github.com/spaceflint7/bna/blob/master/BNA/src/Import.cs). +- `[java.attr.DiscardAttribute]` on a top-level type (class/struct/interface/delegate) or on a class method to exclude the type or method from output. For example, [Baselib/`Object.cs`](https://github.com/spaceflint7/bluebonnet/blob/master/Baselib/src/System/Object.cs) declares a `java.lang.Object` type with a `getClass` method, but there is no need to actually emit a Java class for `java.lang.Object`. For an example of this outside of Baselib, see [BNA/`Import.cs`](https://github.com/spaceflint7/bna/blob/master/BNA/src/Import.cs). - `[java.attr.RetainTypeAttribute]` on a field data member indicates not to box the field. This is useful for fields that participate in a code hot path, as it eliminates double-references when accessing the field. It should not be used with fields which may be referenced directly from code outside their containing assembly.