using System.Collections.Concurrent; using System.Runtime.CompilerServices; namespace JCIL.Java.Class; public sealed class TypeLoader { private readonly ConcurrentDictionary, string> _files = new(ReadOnlyMemoryCharComparer.Instance); private readonly ConcurrentDictionary, Class> _classes = new(ReadOnlyMemoryCharComparer.Instance); public Class Object { get { field ??= LoadClass("java/lang/Object".AsMemory()); return field; } } public void AddPath(string path) { var basePath = path.AsSpan().TrimEnd(Path.DirectorySeparatorChar); foreach (var file in Directory.EnumerateFiles(path, "*.class", SearchOption.AllDirectories)) { var name = file[(basePath.Length + 1)..^6]; _files.AddOrUpdate(name.AsMemory(), file, (_, v) => v); } } public Class GetClass(ReadOnlyMemory className) { return _classes.TryGetValue(className, out var existing) ? existing : GetClass(className, out _); } public Class GetClass(ReadOnlyMemory className, out ReadOnlyMemory rest) { var firstChar = className.Span[0]; rest = className[1..]; switch (firstChar) { // Init intrinsics case 'Z': return _classes.GetOrAdd("Z".AsMemory(), _ => new Class(this) { Name = "boolean".AsMemory(), Namespace = "java/lang".AsMemory(), SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Boolean) }); case 'B': return _classes.GetOrAdd("B".AsMemory(), _ => new Class(this) { Name = "byte".AsMemory(), Namespace = "java/lang".AsMemory(), SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Byte) }); case 'C': return _classes.GetOrAdd("C".AsMemory(), _ => new Class(this) { Name = "char".AsMemory(), Namespace = "java/lang".AsMemory(), SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Char) }); case 'S': return _classes.GetOrAdd("S".AsMemory(), _ => new Class(this) { Name = "short".AsMemory(), Namespace = "java/lang".AsMemory(), SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Short) }); case 'I': return _classes.GetOrAdd("I".AsMemory(), _ => new Class(this) { Name = "int".AsMemory(), Namespace = "java/lang".AsMemory(), SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Int) }); case 'J': return _classes.GetOrAdd("J".AsMemory(), _ => new Class(this) { Name = "long".AsMemory(), Namespace = "java/lang".AsMemory(), SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Long) }); case 'F': return _classes.GetOrAdd("F".AsMemory(), _ => new Class(this) { Name = "float".AsMemory(), Namespace = "java/lang".AsMemory(), SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Float) }); case 'D': return _classes.GetOrAdd("D".AsMemory(), _ => new Class(this) { Name = "double".AsMemory(), Namespace = "java/lang".AsMemory(), SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Double) }); case 'V': return _classes.GetOrAdd("V".AsMemory(), _ => new Class(this) { Name = "void".AsMemory(), Namespace = "java/lang".AsMemory(), SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Void) }); case 'L' when className.Span.IndexOf('<') is var genericsStart and > 0: { var theName = className.Span; var semicolon = theName.IndexOf(';'); if (semicolon < genericsStart) { className = className[..(semicolon + 1)]; rest = rest[semicolon..]; return GetClass(className); } className = rest[..(genericsStart - 1)]; rest = rest[genericsStart..]; var loop = true; var generics = new List(); while (loop) { switch (rest.Span[0]) { case '>': loop = false; break; case '*': generics.Add(Object); rest = rest[1..]; break; default: generics.Add(GetClass(rest, out rest)); break; } } rest = rest[2..]; return LoadClass(className).WithGenerics(generics); } case 'L': { className = rest[..rest.Span.IndexOf(';')]; rest = rest[(className.Length + 1)..]; return LoadClass(className); } case 'T': { className = rest[..rest.Span.IndexOf(';')]; rest = rest[(className.Length + 1)..]; // TODO Actually load a type from context return Object; } case '[': { var arrayName = rest; var arrayElem = GetClass(arrayName, out rest); arrayName = arrayName[..^rest.Length]; arrayName = className[..(arrayName.Length + 1)]; return _classes.GetOrAdd(arrayName, _ => { var arrClass = new Class(this) { Name = arrayName, SuperClass = Object, SpecialClassMetadata = new ArrayMetadata(arrayElem), }; return arrClass; }); } case '(': { var args = new List(); while (rest.Span[0] != ')') args.Add(GetClass(rest, out rest)); rest = rest[1..]; var retClass = GetClass(rest, out rest); className = className[..^rest.Length]; return _classes.GetOrAdd(className, _ => new Class(this) { SpecialClassMetadata = new MethodSignature(retClass, args) }); } // TODO Handle this somehow case '+': return GetClass(rest, out rest); case '-': return GetClass(rest, out rest); default: { rest = default; return LoadClass(className); } } } public Class LoadClass(ReadOnlyMemory className) { var mustLoad = false; var javaClass = _classes.GetOrAdd(className, _ => { mustLoad = true; var javaClass = new Class(this); Monitor.Enter(javaClass); return javaClass; }); if (mustLoad) { if (!_files.TryGetValue(className, out var classFile)) throw new FileNotFoundException($"{className}.class was not found"); // Console.WriteLine($"Loading class {className}"); using var file = File.OpenRead(classFile); using var reader = new BufferedStream(file); javaClass.Read(reader); Monitor.Exit(javaClass); } else { lock (javaClass) { // Wait for it to be loaded } } return javaClass; } private static ReadOnlyMemory SliceFromSpan(ReadOnlyMemory memory, ReadOnlySpan span) { var mem = memory.Span; var start = Unsafe.ByteOffset(in mem[0], in span[0]) / sizeof(char); return memory.Slice((int)start, span.Length); } } internal class ReadOnlyMemoryCharComparer : IEqualityComparer> { public static readonly ReadOnlyMemoryCharComparer Instance = new(); public bool Equals(ReadOnlyMemory x, ReadOnlyMemory y) => x.Span.SequenceEqual(y.Span); public int GetHashCode(ReadOnlyMemory obj) => string.GetHashCode(obj.Span); }