253 lines
8.8 KiB
C#
253 lines
8.8 KiB
C#
|
|
using System.Collections.Concurrent;
|
||
|
|
using System.Runtime.CompilerServices;
|
||
|
|
|
||
|
|
namespace JCIL.Java.Class;
|
||
|
|
|
||
|
|
public sealed class TypeLoader
|
||
|
|
{
|
||
|
|
private readonly ConcurrentDictionary<ReadOnlyMemory<char>, string> _files =
|
||
|
|
new(ReadOnlyMemoryCharComparer.Instance);
|
||
|
|
|
||
|
|
private readonly ConcurrentDictionary<ReadOnlyMemory<char>, 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<char> className)
|
||
|
|
{
|
||
|
|
return _classes.TryGetValue(className, out var existing) ? existing : GetClass(className, out _);
|
||
|
|
}
|
||
|
|
|
||
|
|
public Class GetClass(ReadOnlyMemory<char> className, out ReadOnlyMemory<char> 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<Class>();
|
||
|
|
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<Class>();
|
||
|
|
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<char> 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<char> SliceFromSpan(ReadOnlyMemory<char> memory, ReadOnlySpan<char> 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<ReadOnlyMemory<char>>
|
||
|
|
{
|
||
|
|
public static readonly ReadOnlyMemoryCharComparer Instance = new();
|
||
|
|
|
||
|
|
public bool Equals(ReadOnlyMemory<char> x, ReadOnlyMemory<char> y)
|
||
|
|
=> x.Span.SequenceEqual(y.Span);
|
||
|
|
|
||
|
|
public int GetHashCode(ReadOnlyMemory<char> obj)
|
||
|
|
=> string.GetHashCode(obj.Span);
|
||
|
|
}
|