From 9181b2ab62748ca3b7a164438c615867866d78f1 Mon Sep 17 00:00:00 2001 From: spaceflint <> Date: Sat, 29 Aug 2020 19:53:56 +0300 Subject: [PATCH] Several small changes - Tidy-up value-type methods - Improved enums - Support default interface methods - Re-added executable directory as a search directory --- .gitignore | 1 + Baselib/src/System/Array.cs | 6 +- Baselib/src/System/Boolean.cs | 3 +- Baselib/src/System/Byte.cs | 3 +- Baselib/src/System/Char.cs | 3 +- Baselib/src/System/Double.cs | 3 +- Baselib/src/System/Enum.cs | 410 +++++++++--------- Baselib/src/System/GenericType.cs | 2 +- Baselib/src/System/Int16.cs | 3 +- Baselib/src/System/Int32.cs | 3 +- Baselib/src/System/Int64.cs | 3 +- Baselib/src/System/Reference.cs | 3 +- .../System/Reflection/BindingFlagsIterator.cs | 139 ++++++ .../src/System/Reflection/RuntimeFieldInfo.cs | 97 +++++ .../System/Reflection/RuntimeMethodInfo.cs | 2 +- Baselib/src/System/RuntimeHandles.cs | 15 + Baselib/src/System/RuntimeType.cs | 4 +- Baselib/src/System/Single.cs | 3 +- Baselib/src/System/ValueType.cs | 7 +- CilToJava/src/CilInterface.cs | 7 + CilToJava/src/CilMethod.cs | 8 +- CilToJava/src/CilType.cs | 11 + CilToJava/src/CodeArray.cs | 3 +- CilToJava/src/CodeCall.cs | 20 + CilToJava/src/CodeLocals.cs | 2 + CilToJava/src/GenericUtil.cs | 5 +- CilToJava/src/TypeBuilder.cs | 2 +- CilToJava/src/ValueUtil.cs | 47 +- Main/src/CilTool.cs | 4 +- Tests/src/TestValue.cs | 24 + USAGE.md | 40 +- 31 files changed, 611 insertions(+), 272 deletions(-) create mode 100644 Baselib/src/System/Reflection/BindingFlagsIterator.cs create mode 100644 Baselib/src/System/Reflection/RuntimeFieldInfo.cs create mode 100644 Baselib/src/System/RuntimeHandles.cs diff --git a/.gitignore b/.gitignore index 5fc8bba..13d7896 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .obj/ .vs/ packages/ +/TODO diff --git a/Baselib/src/System/Array.cs b/Baselib/src/System/Array.cs index c2a82a4..ecbc643 100644 --- a/Baselib/src/System/Array.cs +++ b/Baselib/src/System/Array.cs @@ -182,7 +182,7 @@ namespace system { srcObj = (ValueType) java.lang.reflect.Array.get(srcArr, sourceIndex + length); dstObj = (ValueType) java.lang.reflect.Array.get(dstArr, destinationIndex + length); - ((ValueMethod) ((ValueType) srcObj)).CopyInto((ValueType) dstObj); + ((ValueMethod) ((ValueType) srcObj)).CopyTo((ValueType) dstObj); } } else @@ -191,7 +191,7 @@ namespace system { srcObj = (ValueType) java.lang.reflect.Array.get(srcArr, sourceIndex + idx); dstObj = (ValueType) java.lang.reflect.Array.get(dstArr, destinationIndex + idx); - ((ValueMethod) ((ValueType) srcObj)).CopyInto((ValueType) dstObj); + ((ValueMethod) ((ValueType) srcObj)).CopyTo((ValueType) dstObj); } } } @@ -1028,7 +1028,7 @@ namespace system // this will throw invalid cast if the types do not match. // but note that it will succeed with generic instance types // even when the type arguments do not match. - ((ValueMethod) ((ValueType) valueArray[index])).CopyInto((ValueType) value); + ((ValueMethod) ((ValueType) valueArray[index])).CopyTo((ValueType) value); break; case object[] objectArray: diff --git a/Baselib/src/System/Boolean.cs b/Baselib/src/System/Boolean.cs index 8f1ed5b..f9e0c9f 100644 --- a/Baselib/src/System/Boolean.cs +++ b/Baselib/src/System/Boolean.cs @@ -38,8 +38,7 @@ namespace system void ValueMethod.Clear() => Set(0); - void ValueMethod.CopyFrom(ValueType from) => Set(((Boolean) from).Get()); - void ValueMethod.CopyInto(ValueType into) => ((Boolean) into).Set(Get()); + void ValueMethod.CopyTo(ValueType into) => ((Boolean) into).Set(Get()); ValueType ValueMethod.Clone() => Box(Get()); diff --git a/Baselib/src/System/Byte.cs b/Baselib/src/System/Byte.cs index 0ed6fd3..e0922b1 100644 --- a/Baselib/src/System/Byte.cs +++ b/Baselib/src/System/Byte.cs @@ -51,8 +51,7 @@ namespace system void ValueMethod.Clear() => Set(0); - void ValueMethod.CopyFrom(ValueType from) => Set(((SByte) from).Get()); - void ValueMethod.CopyInto(ValueType into) => ((SByte) into).Set(Get()); + void ValueMethod.CopyTo(ValueType into) => ((SByte) into).Set(Get()); ValueType ValueMethod.Clone() => Box(Get()); diff --git a/Baselib/src/System/Char.cs b/Baselib/src/System/Char.cs index 89d1a67..4f94049 100644 --- a/Baselib/src/System/Char.cs +++ b/Baselib/src/System/Char.cs @@ -56,8 +56,7 @@ namespace system void ValueMethod.Clear() => Set(0); - void ValueMethod.CopyFrom(ValueType from) => Set(((Char) from).Get()); - void ValueMethod.CopyInto(ValueType into) => ((Char) into).Set(Get()); + void ValueMethod.CopyTo(ValueType into) => ((Char) into).Set(Get()); ValueType ValueMethod.Clone() => Box(Get()); diff --git a/Baselib/src/System/Double.cs b/Baselib/src/System/Double.cs index 1d31406..10c04c6 100644 --- a/Baselib/src/System/Double.cs +++ b/Baselib/src/System/Double.cs @@ -59,8 +59,7 @@ namespace system void ValueMethod.Clear() => Set(0); - void ValueMethod.CopyFrom(ValueType from) => Set(((Double) from).Get()); - void ValueMethod.CopyInto(ValueType into) => ((Double) into).Set(Get()); + void ValueMethod.CopyTo(ValueType into) => ((Double) into).Set(Get()); ValueType ValueMethod.Clone() => Box(Get()); diff --git a/Baselib/src/System/Enum.cs b/Baselib/src/System/Enum.cs index 16e2491..a9e9e6a 100644 --- a/Baselib/src/System/Enum.cs +++ b/Baselib/src/System/Enum.cs @@ -5,109 +5,68 @@ namespace system interface EnumFlags { } [System.Serializable] - public abstract class Enum : system.ValueType, system.ValueMethod, System.IComparable - //System.IFormattable, System.IConvertible + public abstract class Enum : system.ValueType, system.ValueMethod, System.IComparable, + System.IFormattable //System.IConvertible { - public static object Box(int v, java.lang.Class cls) - { - var obj = (system.Enum) cls.newInstance(); - Enum.Set(v, obj, cls); - return obj; - } + // + // getters and setters + // + + [System.Runtime.CompilerServices.SpecialName] // discard "Long" in name; see CilMethod + public abstract long GetLong(); + + [System.Runtime.CompilerServices.SpecialName] // discard "Long" in name; see CilMethod + public abstract void SetLong(long v); + + public virtual int Get() => (int) GetLong(); + public virtual void Set(int v) => SetLong((long) v); + public static void Set(int v, Enum e) => e.SetLong((long) v); + public static void Set(long v, Enum e) => e.SetLong(v); + + public virtual int VolatileGet() => throw new System.NotSupportedException(); + public virtual void VolatileSet(int v) => throw new System.NotSupportedException(); + public virtual void VolatileSet(long v) => throw new System.NotSupportedException(); + public static void VolatileSet(int v, Enum e) => throw new System.NotSupportedException(); + public static void VolatileSet(long v, Enum e) => throw new System.NotSupportedException(); + public static object Box(long v, java.lang.Class cls) { - var obj = cls.newInstance(); - Util.GetEnumField(cls).setLong(obj, v); + var constructor = cls.getConstructor(null); + constructor.setAccessible(true); + var obj = (system.Enum) constructor.newInstance(null); + obj.SetLong(v); return obj; } - - - public virtual int Get() => - Util.GetEnumField(((java.lang.Object) (object) this).getClass()).getInt(this); - - [System.Runtime.CompilerServices.SpecialName] // discard "Long" in name; see CilMethod - public virtual long GetLong() => - Util.GetEnumField(((java.lang.Object) (object) this).getClass()).getLong(this); - - public virtual int VolatileGet() => throw new System.NotSupportedException(); - - [System.Runtime.CompilerServices.SpecialName] // discard "Long" in name; see CilMethod - public virtual long VolatileGetLong() => throw new System.NotSupportedException(); - - public virtual void Set(int v) => Set(v, this); - public virtual void VolatileSet(int v) => throw new System.NotSupportedException(); - - public virtual void Set(long v) => Set(v, this); - public virtual void VolatileSet(long v) => throw new System.NotSupportedException(); - - public static void Set(int v, Int32 o) => o.Set(v); - public static void VolatileSet(int v, Int32 o) => o.VolatileSet(v); + public static object Box(int v, java.lang.Class cls) => Box((long) v, cls); - public static void Set(int v, system.Enum obj) - => Set(v, obj, ((java.lang.Object) (object) obj).getClass()); - - public static void Set(long v, system.Enum obj) - => Set(v, obj, ((java.lang.Object) (object) obj).getClass()); - - - - public static void Set(int v, system.Enum obj, java.lang.Class cls) + public int CompareTo(object other) { - var fld = Util.GetEnumField(cls); - cls = fld.getType(); - - if (cls == java.lang.Integer.TYPE) - fld.setInt(obj, v); - else if (cls == java.lang.Short.TYPE) - fld.setShort(obj, (short) v); - else if (cls == java.lang.Byte.TYPE) - fld.setByte(obj, (sbyte) v); - else - fld.setLong(obj, (long) v); - } - - - - public static void Set(long v, system.Enum obj, java.lang.Class cls) - { - Util.GetEnumField(cls).setLong(obj, v); - } - - - - public int CompareTo(object obj) - { - if (obj == null) + if (other == null) return (this != null) ? 1 : 0; var cls = ((java.lang.Object) (object) this).getClass(); - if (cls != ((java.lang.Object) obj).getClass()) + if (cls != ((java.lang.Object) other).getClass()) throw new System.ArgumentException(); - var fld = Util.GetEnumField(cls); - var v1 = fld.getLong(this); - var v2 = fld.getLong(obj); - return java.lang.Long.signum(v1 - v2); + return java.lang.Long.signum( + GetLong() - ((Enum) other).GetLong()); } - public override bool Equals(object obj) + public override bool Equals(object other) { - if (obj != null) + if (other != null) { var cls = ((java.lang.Object) (object) this).getClass(); - if (cls == ((java.lang.Object) obj).getClass()) + if (cls == ((java.lang.Object) other).getClass()) { - var fld = Util.GetEnumField(cls); - var v1 = fld.getLong(this); - var v2 = fld.getLong(obj); - if (v1 == v2) + if (GetLong() == ((Enum) other).GetLong()) return true; } } @@ -116,138 +75,191 @@ namespace system - public override int GetHashCode() + public override int GetHashCode() => java.lang.Long.hashCode(GetLong()); + + + + // + // GetUnderlyingType + // + + public static System.Type GetUnderlyingType(System.Type enumType) { - var cls = ((java.lang.Object) (object) this).getClass(); - return java.lang.Long.hashCode(Util.GetEnumField(cls).getLong(this)); - } - - - - public override string ToString() - { - var cls = ((java.lang.Object) (object) this).getClass(); - var fld = Util.GetEnumField(cls); - if (fld != null) - { - var v = fld.getLong(this); - string s; - - var flds = cls.getDeclaredFields(); - int n = flds.Length; - - if (this is EnumFlags) - s = Util.EnumToStringMulti(v, flds, n); - else - s = Util.EnumToStringSingle(v, flds, n); - - return s ?? java.lang.Long.toString(v); - } - Util.BadEnum(cls); - return default(string); - } - - - - void ValueMethod.Clear() => Set(0); - void ValueMethod.CopyFrom(ValueType from) => Set(((Enum) from).Get()); - void ValueMethod.CopyInto(ValueType into) => ((Enum) into).Set(Get()); - ValueType ValueMethod.Clone() => (ValueType) Box(Get(), ((java.lang.Object) (object) this).getClass()); - - } - - - - /* - * - * Enum Util - * - */ - - - - public static partial class Util - { - - [java.attr.RetainType] private static java.util.concurrent.ConcurrentHashMap enumMap = - new java.util.concurrent.ConcurrentHashMap(); - - - - internal static void BadEnum(java.lang.Class cls) - { - throw new System.ArgumentException("Bad enum " + cls); - } - - - - internal static java.lang.reflect.Field GetEnumField(java.lang.Class cls) - { - var field = enumMap.get(cls); - if (field != null) - return (java.lang.reflect.Field) field; - - foreach (var f in cls.getDeclaredFields()) - { - if (f.getModifiers() == java.lang.reflect.Modifier.PUBLIC) - { - enumMap.put(cls, f); - return f; - } - } - - BadEnum(cls); - return null; - } - - - - internal static string EnumToStringSingle(long v, java.lang.reflect.Field[] fields, int n) - { - 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.getLong(null) == v) - return f.getName(); - } - } - return null; - } - - - - internal static string EnumToStringMulti(long v, java.lang.reflect.Field[] fields, int n) - { - return "???"; + ThrowHelper.ThrowIfNull(enumType); + return enumType.GetEnumUnderlyingType(); #if false - var sb = new java.lang.StringBuilder(); - bool comma = false; - - for (int i = 0; i < n; i++) + if (enumType.IsEnum && enumType is RuntimeType enumRuntimeType) { - var f = fields[i]; - if (f.getModifiers() == ( java.lang.reflect.Modifier.PUBLIC - | java.lang.reflect.Modifier.STATIC)) + var fields = enumRuntimeType.JavaClassForArray().getDeclaredFields(); + if (fields.Length > 1) { - var fv = f.getLong(null); - if ((fv & v) == fv) + var f = fields[0]; + if ((f.getModifiers() & java.lang.reflect.Modifier.STATIC) == 0) { - v &= ~fv; - if (comma) - sb.append(", "); - sb.append(f.getName()); - comma = true; + var fldType = system.RuntimeType.GetType(f.getType()); + var typeCode = (int) System.Type.GetTypeCode(fldType); + if (typeCode >= 4 && typeCode <= 12) + return fldType; } } } - - return (v == 0) ? sb.ToString() : null; + throw new System.ArgumentException(); #endif } + + + // + // ToString and Format + // + + public override string ToString() => ToString((string) null); + + public string ToString(System.IFormatProvider provider) + => ToString((string) null); + + public string ToString(string format, System.IFormatProvider formatProvider) + => ToString(format); + + public string ToString(string format) + { + if (format == null || format.Length == 0) + format = "G"; + return Format(format, GetLong(), ((java.lang.Object) (object) this).getClass()); + } + + public static string Format(System.Type enumType, object value, string format) + { + ThrowHelper.ThrowIfNull(enumType); + if (! enumType.IsEnum) + throw new System.ArgumentException(); + ThrowHelper.ThrowIfNull(value, format); + + if (enumType is RuntimeType enumRuntimeType) + { + var enumUnderlyingType = GetUnderlyingType(enumType); + var enumCls = enumRuntimeType.JavaClassForArray(); + var valueType = value.GetType(); + + if (value is Enum valueEnum) + { + var valueUnderlyingType = GetUnderlyingType(valueType); + if (object.ReferenceEquals(enumUnderlyingType, valueUnderlyingType)) + return Format(format, valueEnum.GetLong(), enumCls); + } + else if (object.ReferenceEquals(enumUnderlyingType, valueType)) + { + switch (value) + { + case byte byteValue: return Format(format, (long) byteValue, enumCls); + case char charValue: return Format(format, (long) charValue, enumCls); + case short shortValue: return Format(format, (long) shortValue, enumCls); + case int intValue: return Format(format, (long) intValue, enumCls); + case long longValue: return Format(format, longValue, enumCls); + } + } + } + throw new System.ArgumentException(); + } + + private static string Format(string format, long value, java.lang.Class cls) + { + if (format.Length == 1) + { + bool asFlags = false; + switch (format[0]) + { + case 'f': case 'F': + asFlags = true; + goto case 'G'; + + case 'g': case 'G': + if (! asFlags) + { + asFlags = ((java.lang.Class) typeof (EnumFlags)) + .isAssignableFrom(cls); + } + return FormatNames(cls, asFlags, value); + + case 'd': case 'D': + return java.lang.Long.toString(value); + + case 'x': case 'X': + return java.lang.Long.toHexString(value); + } + } + throw new System.FormatException(); + + + + static string FormatNames(java.lang.Class cls, bool asFlags, long v) + { + var fields = cls.getDeclaredFields(); + int n = fields.Length; + + var s = asFlags + ? ToStringMulti(v, fields, n) + : ToStringSingle(v, fields, n); + return s ?? java.lang.Long.toString(v); + } + + static string ToStringSingle(long v, java.lang.reflect.Field[] fields, int n) + { + for (int i = 0; i < n; i++) + { + var f = fields[i]; + if (f.getModifiers() == ( java.lang.reflect.Modifier.PUBLIC + | java.lang.reflect.Modifier.STATIC)) + { + f.setAccessible(true); + if (f.getLong(null) == v) + return f.getName(); + } + } + return null; + } + + static string ToStringMulti(long v, java.lang.reflect.Field[] fields, int n) + { + var v0 = v; + var sb = new java.lang.StringBuilder(); + bool comma = false; + + for (int i = 0; i < n; i++) + { + var f = fields[i]; + if (f.getModifiers() == ( java.lang.reflect.Modifier.PUBLIC + | java.lang.reflect.Modifier.STATIC)) + { + f.setAccessible(true); + var fv = f.getLong(null); + + if ((fv & v) == fv) + { + if (fv == 0 && v0 != 0) + { + // skip field for zero, if value was not zero + continue; + } + v &= ~fv; + if (comma) + sb.append(", "); + sb.append(f.getName()); + comma = true; + } + } + } + + return (v == 0) ? sb.ToString() : null; + } + } + + + + void ValueMethod.Clear() => SetLong(0L); + void ValueMethod.CopyTo(ValueType into) => ((Enum) into).SetLong(GetLong()); + ValueType ValueMethod.Clone() => (ValueType) this.MemberwiseClone(); + } } diff --git a/Baselib/src/System/GenericType.cs b/Baselib/src/System/GenericType.cs index aeab5aa..c632415 100644 --- a/Baselib/src/System/GenericType.cs +++ b/Baselib/src/System/GenericType.cs @@ -27,7 +27,7 @@ namespace system } else if (fromObj is ValueType fromValue) { - ((ValueMethod) fromValue).CopyInto((ValueType) toObj); + ((ValueMethod) fromValue).CopyTo((ValueType) toObj); } else if (fromObj is java.lang.Comparable) { diff --git a/Baselib/src/System/Int16.cs b/Baselib/src/System/Int16.cs index caf44c2..4d9c780 100644 --- a/Baselib/src/System/Int16.cs +++ b/Baselib/src/System/Int16.cs @@ -53,8 +53,7 @@ namespace system void ValueMethod.Clear() => Set(0); - void ValueMethod.CopyFrom(ValueType from) => Set(((Int16) from).Get()); - void ValueMethod.CopyInto(ValueType into) => ((Int16) into).Set(Get()); + void ValueMethod.CopyTo(ValueType into) => ((Int16) into).Set(Get()); ValueType ValueMethod.Clone() => Box(Get()); diff --git a/Baselib/src/System/Int32.cs b/Baselib/src/System/Int32.cs index 9025c91..12306ca 100644 --- a/Baselib/src/System/Int32.cs +++ b/Baselib/src/System/Int32.cs @@ -87,8 +87,7 @@ namespace system void ValueMethod.Clear() => Set(0); - void ValueMethod.CopyFrom(ValueType from) => Set(((Int32) from).Get()); - void ValueMethod.CopyInto(ValueType into) => ((Int32) into).Set(Get()); + void ValueMethod.CopyTo(ValueType into) => ((Int32) into).Set(Get()); ValueType ValueMethod.Clone() => Box(Get()); diff --git a/Baselib/src/System/Int64.cs b/Baselib/src/System/Int64.cs index 129c74a..8cf23d9 100644 --- a/Baselib/src/System/Int64.cs +++ b/Baselib/src/System/Int64.cs @@ -77,8 +77,7 @@ namespace system void ValueMethod.Clear() => Set(0); - void ValueMethod.CopyFrom(ValueType from) => Set(((Int64) from).Get()); - void ValueMethod.CopyInto(ValueType into) => ((Int64) into).Set(Get()); + void ValueMethod.CopyTo(ValueType into) => ((Int64) into).Set(Get()); ValueType ValueMethod.Clone() => Box(Get()); diff --git a/Baselib/src/System/Reference.cs b/Baselib/src/System/Reference.cs index 4acb8fd..458459b 100644 --- a/Baselib/src/System/Reference.cs +++ b/Baselib/src/System/Reference.cs @@ -47,8 +47,7 @@ namespace system void ValueMethod.Clear() => Set(null); - void ValueMethod.CopyFrom(ValueType from) => Set(((Reference) from).Get()); - void ValueMethod.CopyInto(ValueType into) => ((Reference) into).Set(Get()); + void ValueMethod.CopyTo(ValueType into) => ((Reference) into).Set(Get()); ValueType ValueMethod.Clone() => Box(Get()); diff --git a/Baselib/src/System/Reflection/BindingFlagsIterator.cs b/Baselib/src/System/Reflection/BindingFlagsIterator.cs new file mode 100644 index 0000000..db96ded --- /dev/null +++ b/Baselib/src/System/Reflection/BindingFlagsIterator.cs @@ -0,0 +1,139 @@ + +using System.Reflection; + +namespace system.reflection +{ + + public sealed class BindingFlagsIterator + { + + // entrypoint + + public static void Run(BindingFlags bindingAttr, + RuntimeType initialType, + MemberTypes memberType, + System.Predicate callback) + { + int modifierMask = 0; + int modifierValue = 0; + + BindingFlags chk = bindingAttr & (BindingFlags.Public | BindingFlags.NonPublic); + if (chk != (BindingFlags.Public | BindingFlags.NonPublic)) + { + if (chk == 0) // if neither, no methods will match + return; + // methods with internal access are converted to public access, + // so we cannot honor the distinction between Public and NonPublic + /* + modifierMask |= java.lang.reflect.Modifier.PUBLIC; + if (chk == BindingFlags.Public) + modifierValue |= java.lang.reflect.Modifier.PUBLIC; + */ + } + bindingAttr &= ~chk; + + chk = bindingAttr & (BindingFlags.Static | BindingFlags.Instance); + if (chk != (BindingFlags.Static | BindingFlags.Instance)) + { + if (chk == 0) // if neither, no methods will match + return; + modifierMask |= java.lang.reflect.Modifier.STATIC; + if (chk == BindingFlags.Static) + modifierValue |= java.lang.reflect.Modifier.STATIC; + } + bindingAttr &= ~chk; + + RuntimeType stopAtType = null; + if ((bindingAttr & BindingFlags.DeclaredOnly) != 0) + { + stopAtType = initialType; + bindingAttr &= ~BindingFlags.DeclaredOnly; + } + + if (bindingAttr != 0) + { + throw new System.PlatformNotSupportedException( + "bad binding flags " + bindingAttr); + } + + switch (memberType) + { + case MemberTypes.Method: + RunMethods(modifierMask, modifierValue, initialType, stopAtType, callback); + return; + + case MemberTypes.Field: + RunFields(modifierMask, modifierValue, initialType, stopAtType, callback); + return; + } + + throw new System.ArgumentException(); + } + + // + // method iterator + // + + static void RunMethods(int modifierMask, int modifierValue, + RuntimeType initialType, RuntimeType stopAtType, + System.Predicate callback) + { + var currentType = initialType; + for (;;) + { + #pragma warning disable 0436 + java.lang.reflect.Method[] javaMethods = + (java.lang.reflect.Method[]) (object) + currentType.JavaClassForArray().getDeclaredMethods(); + #pragma warning restore 0436 + + foreach (var javaMethod in javaMethods) + { + int jmodifiers = javaMethod.getModifiers(); + if ((jmodifiers & modifierMask) == modifierValue) + { + if (! callback(javaMethod)) + return; + } + } + + currentType = (system.RuntimeType) currentType.BaseType; + if (currentType == stopAtType) + break; + } + } + + // + // field iterator + // + + static void RunFields(int modifierMask, int modifierValue, + RuntimeType initialType, RuntimeType stopAtType, + System.Predicate callback) + { + var currentType = initialType; + for (;;) + { + java.lang.reflect.Field[] javaFields = + (java.lang.reflect.Field[]) (object) + currentType.JavaClassForArray().getDeclaredFields(); + + foreach (var javaField in javaFields) + { + int jmodifiers = javaField.getModifiers(); + if ((jmodifiers & modifierMask) == modifierValue) + { + if (! callback(javaField)) + return; + } + } + + currentType = (system.RuntimeType) currentType.BaseType; + if (currentType == stopAtType) + break; + } + } + + } + +} diff --git a/Baselib/src/System/Reflection/RuntimeFieldInfo.cs b/Baselib/src/System/Reflection/RuntimeFieldInfo.cs new file mode 100644 index 0000000..3d9c688 --- /dev/null +++ b/Baselib/src/System/Reflection/RuntimeFieldInfo.cs @@ -0,0 +1,97 @@ + +using System; +using System.Reflection; +using System.Globalization; +using System.Runtime.Serialization; + +namespace system.reflection +{ + + [System.Serializable] + public sealed class RuntimeFieldInfo : FieldInfo, ISerializable + { + [java.attr.RetainType] public java.lang.reflect.Field JavaField; + [java.attr.RetainType] public system.RuntimeType reflectedType; + + // + // constructor + // + + private RuntimeFieldInfo(java.lang.reflect.Field javaField, + system.RuntimeType reflectedType) + { + this.JavaField = javaField; + this.reflectedType = reflectedType; + } + + // + // GetFields (called by system.RuntimeType.GetFields(GetFields) + // + + public static FieldInfo[] GetFields(BindingFlags bindingAttr, RuntimeType initialType) + { + var list = new System.Collections.Generic.List(); + + BindingFlagsIterator.Run(bindingAttr, initialType, MemberTypes.Field, + (javaAccessibleObject) => + { + list.Add(new RuntimeFieldInfo((java.lang.reflect.Field) javaAccessibleObject, + initialType)); + return true; + }); + + return list.ToArray(); + } + + public override System.Type FieldType + => system.RuntimeType.GetType(JavaField.getType()); + + // + // + // + + public override object GetValue(object obj) + => throw new PlatformNotSupportedException(); + + 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 System.Type DeclaringType + => throw new PlatformNotSupportedException(); + + public override System.Type ReflectedType + => throw new PlatformNotSupportedException(); + + public override string Name + => throw new PlatformNotSupportedException(); + + public override System.RuntimeFieldHandle FieldHandle + => throw new PlatformNotSupportedException(); + + // + // 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) + => throw new PlatformNotSupportedException(); + + // + // ISerializable + // + + public void GetObjectData(SerializationInfo info, StreamingContext context) + => throw new PlatformNotSupportedException(); + + } + +} diff --git a/Baselib/src/System/Reflection/RuntimeMethodInfo.cs b/Baselib/src/System/Reflection/RuntimeMethodInfo.cs index fbdb654..fef36f0 100644 --- a/Baselib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/Baselib/src/System/Reflection/RuntimeMethodInfo.cs @@ -356,7 +356,7 @@ namespace system.reflection public override string Name => strippedName; - public override RuntimeMethodHandle MethodHandle + public override System.RuntimeMethodHandle MethodHandle => throw new PlatformNotSupportedException(); public override ParameterInfo[] GetParameters() diff --git a/Baselib/src/System/RuntimeHandles.cs b/Baselib/src/System/RuntimeHandles.cs new file mode 100644 index 0000000..6148e56 --- /dev/null +++ b/Baselib/src/System/RuntimeHandles.cs @@ -0,0 +1,15 @@ + +namespace system +{ + + [System.Serializable] + public struct RuntimeFieldHandle + { + } + + [System.Serializable] + public struct RuntimeMethodHandle + { + } + +} diff --git a/Baselib/src/System/RuntimeType.cs b/Baselib/src/System/RuntimeType.cs index bee23a6..2efbfdb 100644 --- a/Baselib/src/System/RuntimeType.cs +++ b/Baselib/src/System/RuntimeType.cs @@ -1488,7 +1488,7 @@ namespace system public override FieldInfo[] GetFields(BindingFlags bindingAttr) { - throw new PlatformNotSupportedException(); + return system.reflection.RuntimeFieldInfo.GetFields(bindingAttr, this); } public override MemberInfo[] GetMembers(BindingFlags bindingAttr) @@ -1532,7 +1532,6 @@ namespace system public virtual Type[] GetGenericParameterConstraints() public virtual Array GetEnumValues() - public virtual Type GetEnumUnderlyingType() internal virtual string FormatTypeName(bool serialization) public virtual InterfaceMapping GetInterfaceMap(Type interfaceType) @@ -1564,6 +1563,7 @@ namespace system public virtual bool IsInstanceOfType(Object o) public virtual bool IsEquivalentTo(Type other) + public virtual Type GetEnumUnderlyingType() public virtual string[] GetEnumNames() public virtual bool IsEnumDefined(object value) public virtual string GetEnumName(object value) diff --git a/Baselib/src/System/Single.cs b/Baselib/src/System/Single.cs index d8e3bb8..157da55 100644 --- a/Baselib/src/System/Single.cs +++ b/Baselib/src/System/Single.cs @@ -59,8 +59,7 @@ namespace system void ValueMethod.Clear() => Set(0); - void ValueMethod.CopyFrom(ValueType from) => Set(((Single) from).Get()); - void ValueMethod.CopyInto(ValueType into) => ((Single) into).Set(Get()); + void ValueMethod.CopyTo(ValueType into) => ((Single) into).Set(Get()); ValueType ValueMethod.Clone() => Box(Get()); diff --git a/Baselib/src/System/ValueType.cs b/Baselib/src/System/ValueType.cs index 4256a2b..c7e7265 100644 --- a/Baselib/src/System/ValueType.cs +++ b/Baselib/src/System/ValueType.cs @@ -52,7 +52,6 @@ namespace system public override int GetHashCode() { - var fields = ((java.lang.Object) (object) this).getClass().getDeclaredFields(); int numFields = fields.Length; @@ -78,8 +77,7 @@ namespace system void ValueMethod.Clear() {} - void ValueMethod.CopyFrom(ValueType from) {} - void ValueMethod.CopyInto(ValueType into) {} + void ValueMethod.CopyTo(ValueType other) {} ValueType ValueMethod.Clone() => null; } @@ -89,8 +87,7 @@ namespace system interface ValueMethod { void Clear(); - void CopyFrom(ValueType from); - void CopyInto(ValueType into); + void CopyTo(ValueType other); ValueType Clone(); } diff --git a/CilToJava/src/CilInterface.cs b/CilToJava/src/CilInterface.cs index 792c8d3..11b268d 100644 --- a/CilToJava/src/CilInterface.cs +++ b/CilToJava/src/CilInterface.cs @@ -331,6 +331,13 @@ namespace SpaceFlint.CilToJava if ( (fromMethod.IsPublic || fromMethod.HasOverrides) && ! (fromMethod.IsStatic || fromMethod.IsConstructor)) { + if (fromType.IsInterface && fromMethod.HasBody) + { + // skip default interface methods, they are not needed + // in the context of resolving interface implementations + continue; + } + var genericMark = CilMain.GenericStack.Mark(); var inputMethod = CilMain.GenericStack.EnterMethod(fromMethod); diff --git a/CilToJava/src/CilMethod.cs b/CilToJava/src/CilMethod.cs index 56ac201..c9a743e 100644 --- a/CilToJava/src/CilMethod.cs +++ b/CilToJava/src/CilMethod.cs @@ -831,12 +831,8 @@ namespace SpaceFlint.CilToJava internal static readonly JavaMethodRef ValueClear = new JavaMethod("system-ValueMethod-Clear", JavaType.VoidType); - internal static readonly JavaMethodRef ValueCopyFrom = - new JavaMethod("system-ValueMethod-CopyFrom", - JavaType.VoidType, CilType.SystemValueType); - - internal static readonly JavaMethodRef ValueCopyInto = - new JavaMethod("system-ValueMethod-CopyInto", + internal static readonly JavaMethodRef ValueCopyTo = + new JavaMethod("system-ValueMethod-CopyTo", JavaType.VoidType, CilType.SystemValueType); internal static readonly JavaMethodRef ValueClone = diff --git a/CilToJava/src/CilType.cs b/CilToJava/src/CilType.cs index a980eba..204e012 100644 --- a/CilToJava/src/CilType.cs +++ b/CilToJava/src/CilType.cs @@ -179,6 +179,14 @@ namespace SpaceFlint.CilToJava Flags &= ~VALUE; return; } + /* + if (fromType.Name == "RuntimeFieldHandle") + { + CopyFrom(ReflectFieldType); + JavaName = ClassName; + Flags &= ~VALUE; + return; + } if (fromType.Name == "RuntimeMethodHandle") { CopyFrom(ReflectMethodType); @@ -186,6 +194,7 @@ namespace SpaceFlint.CilToJava Flags &= ~VALUE; return; } + */ } } else @@ -659,6 +668,8 @@ namespace SpaceFlint.CilToJava internal static readonly JavaType SystemUtilType = new JavaType(0, 0, "system.Util"); internal static readonly JavaType SystemValueType = new JavaType(0, 0, "system.ValueType"); + /*internal static readonly CilType ReflectFieldType = + CilType.From(new JavaType(0, 0, "java.lang.reflect.Field"));*/ internal static readonly CilType ReflectMethodType = CilType.From(new JavaType(0, 0, "java.lang.reflect.Method")); } diff --git a/CilToJava/src/CodeArray.cs b/CilToJava/src/CodeArray.cs index e9ba200..441253a 100644 --- a/CilToJava/src/CodeArray.cs +++ b/CilToJava/src/CodeArray.cs @@ -347,7 +347,8 @@ namespace SpaceFlint.CilToJava code.NewInstruction(elemType.LoadOpcode, null, localIndex); locals.FreeTempIndex(localIndex); - CilMethod.ValueMethod(CilMethod.ValueCopyFrom, code); + // we can pass any type that is not a generic parameter + GenericUtil.ValueCopy(CilType.SystemTypeType, code, true); } else if (arrayType.ArrayRank > 1) { diff --git a/CilToJava/src/CodeCall.cs b/CilToJava/src/CodeCall.cs index d6ae9b9..77d4547 100644 --- a/CilToJava/src/CodeCall.cs +++ b/CilToJava/src/CodeCall.cs @@ -406,6 +406,26 @@ namespace SpaceFlint.CilToJava return true; } + + if ( callClass.JavaName == null + && callClass.Equals(JavaType.ClassType) + && callMethod.Name == "GetRuntimeType") + { + // convert virtual call to RuntimeTypeHandle.GetRuntimeType + // to a static call to system.RuntimeType + + code.NewInstruction(0xB8 /* invokestatic */, + CilType.SystemRuntimeTypeType, + new JavaMethodRef( + callMethod.Name, callMethod.ReturnType, + JavaType.ObjectType)); + + ClearMethodArguments(callMethod, false); + PushMethodReturnType(callMethod); + + return true; + } + return false; } diff --git a/CilToJava/src/CodeLocals.cs b/CilToJava/src/CodeLocals.cs index 13ffe23..0642b11 100644 --- a/CilToJava/src/CodeLocals.cs +++ b/CilToJava/src/CodeLocals.cs @@ -330,8 +330,10 @@ namespace SpaceFlint.CilToJava if (boxedType != null) { boxedType.BoxValue(code); + #if false if (plainType.IsEnum) code.NewInstruction(0xC0 /* checkcast */, boxedType, null); + #endif } else if (plainType.IsGenericParameter) { diff --git a/CilToJava/src/GenericUtil.cs b/CilToJava/src/GenericUtil.cs index 2237931..b6b664e 100644 --- a/CilToJava/src/GenericUtil.cs +++ b/CilToJava/src/GenericUtil.cs @@ -704,8 +704,9 @@ namespace SpaceFlint.CilToJava } else { - var method = swap ? CilMethod.ValueCopyFrom : CilMethod.ValueCopyInto; - CilMethod.ValueMethod(method, code); + if (swap) + code.NewInstruction(0x5F /* swap */, null, null); + CilMethod.ValueMethod(CilMethod.ValueCopyTo, code); } } diff --git a/CilToJava/src/TypeBuilder.cs b/CilToJava/src/TypeBuilder.cs index 080bbfa..9d21049 100644 --- a/CilToJava/src/TypeBuilder.cs +++ b/CilToJava/src/TypeBuilder.cs @@ -52,7 +52,7 @@ namespace SpaceFlint.CilToJava else if (myType.IsEnum) { ValueUtil.MakeEnumClass(jclass, myType, - cilType.HasCustomAttribute("System.FlagsAttribute")); + cilType.HasCustomAttribute("System.FlagsAttribute", true)); } else if (myType.IsDelegate) diff --git a/CilToJava/src/ValueUtil.cs b/CilToJava/src/ValueUtil.cs index 799f4f3..f060cb3 100644 --- a/CilToJava/src/ValueUtil.cs +++ b/CilToJava/src/ValueUtil.cs @@ -85,8 +85,7 @@ namespace SpaceFlint.CilToJava static void CreateValueMethods(JavaClass valueClass, CilType fromType) { CreateValueClearMethod(valueClass, fromType); - CreateValueCopyMethod(valueClass, fromType, CilMethod.ValueCopyFrom, 1, 0); - CreateValueCopyMethod(valueClass, fromType, CilMethod.ValueCopyInto, 0, 1); + CreateValueCopyToMethod(valueClass, fromType); CreateValueCloneMethod(valueClass, fromType); // @@ -108,7 +107,8 @@ namespace SpaceFlint.CilToJava { code.NewInstruction(0x19 /* aload */, null, (int) 0); code.NewInstruction(0xB4 /* getfield */, fromType, fld); - code.NewInstruction(0xB6 /* invokevirtual */, fldType, CilMethod.ValueClear); + code.NewInstruction(0xB6 /* invokevirtual */, + fldType, CilMethod.ValueClear); } else @@ -124,15 +124,14 @@ namespace SpaceFlint.CilToJava } // - // builds a system-ValueMethod-CopyXxxx() method, which takes a value + // builds a system-ValueMethod-CopyTo() method, which takes a value // type object parameter, along with a 'this' object reference, and - // copies each field from one object to the other + // copies each field from 'this' object to the other object // - void CreateValueCopyMethod(JavaClass valueClass, CilType fromType, - JavaMethodRef model, int localFrom, int localInto) + void CreateValueCopyToMethod(JavaClass valueClass, CilType fromType) { - var code = CilMain.CreateHelperMethod(valueClass, model, 2, 2); + var code = CilMain.CreateHelperMethod(valueClass, CilMethod.ValueCopyTo, 2, 2); bool atLeastOneField = false; if (valueClass.Fields != null && valueClass.Fields.Count != 0) @@ -157,13 +156,14 @@ namespace SpaceFlint.CilToJava code.NewInstruction(0xB4 /* getfield */, fromType, fld); code.NewInstruction(0x19 /* aload */, null, (int) 1); code.NewInstruction(0xB4 /* getfield */, fromType, fld); - code.NewInstruction(0xB6 /* invokevirtual */, fldType, model); + code.NewInstruction(0xB6 /* invokevirtual */, + fldType, CilMethod.ValueCopyTo); } else { - code.NewInstruction(0x19 /* aload */, null, localInto); - code.NewInstruction(0x19 /* aload */, null, localFrom); + code.NewInstruction(0x19 /* aload */, null, (int) 1); + code.NewInstruction(0x19 /* aload */, null, (int) 0); code.NewInstruction(0xB4 /* getfield */, fromType, fld); code.NewInstruction(0xB5 /* putfield */, fromType, fld); } @@ -229,6 +229,31 @@ namespace SpaceFlint.CilToJava if (isFlags) enumClass.AddInterface("system.EnumFlags"); + + // + // create getter and setter + // + + var theClass = new JavaType(0, 0, enumClass.Name); + var theField = enumClass.Fields[0]; + + var code = CilMain.CreateHelperMethod(enumClass, + new JavaMethodRef("Get", JavaType.LongType), 1, 2); + code.NewInstruction(0x19 /* aload */, null, (int) 0); + code.NewInstruction(0xB4 /* getfield */, theClass, theField); + if (enumType.Category == 1) + code.NewInstruction(0x85 /* i2l */, null, null); + code.NewInstruction(code.Method.ReturnType.ReturnOpcode, null, null); + + code = CilMain.CreateHelperMethod(enumClass, + new JavaMethodRef("Set", JavaType.VoidType), 3, 3); + code.Method.Parameters.Add(new JavaFieldRef("", JavaType.LongType)); + code.NewInstruction(0x19 /* aload */, null, (int) 0); + code.NewInstruction(0x16 /* lload */, null, (int) 1); + if (enumType.Category == 1) + code.NewInstruction(0x88 /* l2i */, null, null); + code.NewInstruction(0xB5 /* putfield */, theClass, theField); + code.NewInstruction(code.Method.ReturnType.ReturnOpcode, null, null); } diff --git a/Main/src/CilTool.cs b/Main/src/CilTool.cs index 721ce05..0a21882 100644 --- a/Main/src/CilTool.cs +++ b/Main/src/CilTool.cs @@ -489,9 +489,9 @@ public class CilTool AddSearchDirectory(Directory.GetCurrentDirectory()); - /*MainModuleFileName = + MainModuleFileName = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; - AddSearchDirectory(Path.GetDirectoryName(MainModuleFileName));*/ + AddSearchDirectory(Path.GetDirectoryName(MainModuleFileName)); foreach (var dir in Environment.GetEnvironmentVariable("PATH") ?.Split(Path.PathSeparator)) diff --git a/Tests/src/TestValue.cs b/Tests/src/TestValue.cs index 9eee1d0..6ea0b76 100644 --- a/Tests/src/TestValue.cs +++ b/Tests/src/TestValue.cs @@ -32,6 +32,7 @@ namespace Tests { TestBoxing(); TestObject(); + TestEnum(); } void TestBoxing() @@ -77,5 +78,28 @@ namespace Tests } } + // + // Enum + // + + [Flags] enum MyEnum : sbyte { None, First = 1, Second = 8, Third = 32 }; + + void TestEnum() + { + var x = MyEnum.First | MyEnum.Second; + object y = (object) x; + TestEnum2(ref x); + Console.Write("\t"); + Console.Write(y); + Console.Write($"\t{x:F},{x:G},{x:D},{x:X},{x.GetType().GetEnumUnderlyingType()}"); + Console.WriteLine(); + + void TestEnum2(ref T e) + { + e = (T) (object) MyEnum.Third; + Console.Write(e); + } + } + } } diff --git a/USAGE.md b/USAGE.md index c227f7a..b6aacb0 100644 --- a/USAGE.md +++ b/USAGE.md @@ -23,7 +23,7 @@ If `input_file` is a .NET assembly, then the output is a Java archive containing If the output is an existing archive, it will be updated, not overwritten. The output can alternatively be (1) a directory, in which case unzipped classes will be generated in that directory; or (2) a Java `.class` file, if the (possibly filtered) input can translate to a single class. -If the input assembly references other assemblies, they are searched in (1) the directory containing the input assembly; (2) the current directory; (4) each directory listed in the `PATH` environment variable; (5) the directories within `%PROGRAMFILES%\DotNet\Shared\Microsoft.NETCore.App` (with later .NET Core versions taking precedence over earlier ones). +If the input assembly references other assemblies, they are searched in (1) the directory containing the input assembly; (2) the current directory; (3) the directory containing the Bluebonnet executable (for `Javalib.dll`); (4) each directory listed in the `PATH` environment variable; (5) the directories within `%PROGRAMFILES%\DotNet\Shared\Microsoft.NETCore.App` (with later .NET Core versions taking precedence over earlier ones). # Java Interoperability @@ -50,48 +50,48 @@ In this example, `java.lang.Thread.UncaughtExceptionHandler` is the functional i Here are some known differences, deficiencies and incompatibilities of the Bluebonnet .NET implementation, compared to a proper .NET implementation, in no particular order. -- For desktop applications, the Java entry point 'main' (lowercase) must be defined as public static void main(string[] args). +- For desktop applications, the Java entry point `main` (lowercase) must be defined as `public static void main(string[] args)`. - Namespace names are translated to lowercase package names, as required by Java. Reflection capitalizes the first letter in each namespace component. - Value type objects are implemented as normal JVM class objects, and are not copied into the operand stack by value. Instead, a properly translated method duplicates any non-ref value type arguments that it modifies. -- Delegate Method property is not supported, and throws MemberAccessException. +- `Delegate.Method` property is not supported, and throws `MemberAccessException`. -- Reflection information for generic types depends on the existence of the Signature attribute in java class files. +- Reflection information for generic types depends on the existence of the `Signature` attribute in java class files. -- BeforeFieldInit is not honored; the static initializer for a class will be called at the discretion of the JVM. if it is a generic class, the static initializer is called when the generic type is first referenced. +- `BeforeFieldInit` is not honored; the static initializer for a class will be called at the discretion of the JVM. if it is a generic class, the static initializer is called when the generic type is first referenced. - The type system is weaker than .NET when it comes to generic types, and in some casts and assignments are permitted between generic objects that differ only in their type arguments. -- ConditionalWeakTable is an ephemeron table where (possibly indirect) references from values to keys in the same table are treated as weak references. The JVM does not provide such a mechanism. +- `ConditionalWeakTable` is an ephemeron table in .NET, where (possibly indirect) references from values to keys in the same table are treated as weak references. The JVM does not provide such a mechanism. -- Limited pointer indirection is permitted, but pointer arithmetic is prohibited. Stackalloc buffers are permitted, but must be allocated and accessed using the same type, and may only be assigned to a System.Span of the same type. +- Limited pointer indirection is permitted, but pointer arithmetic is prohibited. `stackalloc` buffers are permitted, but must be allocated and accessed using the same type, and may only be assigned to a `System.Span` of the same type. -- Exceptions originating from Java (JVM or library) are translated to equivalent CLR exception types only when caught. Additionally, System.Exception.ToString prints a stack trace, while java.lang.Throwable.toString does not. This means that uncaught exceptions print the stack twice. +- Exceptions originating from Java (JVM or library) are translated to equivalent CLR exception types only when caught. Additionally, `System.Exception.ToString` prints a stack trace, while `java.lang.Throwable.toString` does not. This means that uncaught exceptions print the stack twice. -- Rectangular multidimensional arrays [,] are implemented as jagged arrays [][] i.e., like Java arrays. typeof(int[,]) == typeof(int[][]). GetArrayRank() returns the actual rank; in .NET it returns 1 for jagged arrays. GetElementType() returns the basic element; in .NET it returns an array type for jagged arrays. All arrays implement the generic interfaces; in .NET only single-dimension and jagged arrays. +- Rectangular multidimensional arrays [,] are implemented as jagged arrays [][] i.e., like Java arrays. `typeof(int[,]) == typeof(int[][])`. `GetArrayRank()` returns the actual rank; in .NET it returns 1 for jagged arrays. `GetElementType()` returns the basic element; in .NET it returns an array type for jagged arrays. All arrays implement the generic interfaces; in .NET only single-dimension and jagged arrays. -- Non-zero lower bounds are not supported in System.Array::CreateInstance. +- Non-zero lower bounds are not supported in `System.Array.CreateInstance`. -- Casting an array object to System.Array, or to an interface, will result in a reference to a helper/proxy object which implements this interface. The program can detect that object.ReferenceEquals(proxy, array) is false. The proxy cannot be cast back to the original array. +- Casting an array object to `System.Array`, or to an interface, will result in a reference to a helper/proxy object which implements this interface. The program can detect that `object.ReferenceEquals(proxy, array)` is false. The proxy cannot be cast back to the original array. -- IEnumerable.Current implemented for an array of primitive integers will always return signed objects (System.Int32), never unsigned (System.UInt32). +- `IEnumerable.Current` implemented for an array of primitive integers always returns signed objects (e.g `System.Int32`), never unsigned (`System.UInt32`). This does not make a difference in the typical case where the result is used as a primitive value. -- System.MarshalByRefObject is translated to java.lang.Object. +- `System.MarshalByRefObject` is translated to `java.lang.Object`. -- Attribute [MethodImplOptions.Synchronized] is not supported on constructors. +- Attribute `[MethodImplOptions.Synchronized]` is not supported on constructors. - Module-level and assembly-level constructors/initializers are not supported. -- AbandonedMutexException is not supported. Any mutex objects still held at time of thread death will never be released. +- `AbandonedMutexException` is not supported. Any mutex objects still held at time of thread death will never be released. -- The GetHashCode implementions attempt to match those in the .NET Framework, which are not necessarily the same as the implementations in .NET Core. +- The `GetHashCode` implementions attempt to match those in the .NET Framework, which are not necessarily the same as the implementations in .NET Core. -- StringBuilder does not support a maximum capacity. +- `StringBuilder` does not support a maximum capacity. -- Standard numeric format strings are supported, but a custom format, or a non-null IFormatProvider parameter in ToString functions will throw an exception. +- Standard numeric format strings are supported, but a custom format, or an `IFormatProvider` which also implements `NumberFormatInfo` will cause `ToString` functions to throw an exception. -- Non-ordinal, culture-dependant string comparisons and casing are not 100% same, because of differences between Windows NLS and the java.text package. +- Non-ordinal, culture-dependant string comparisons and casing are not 100% same, because of differences between Windows NLS and the `java.text` package. -- String is not castable to IConvertible or to the non-generic IComparable interface. +- `String` is not castable to `IConvertible` or to the non-generic `IComparable` interface.