using System.Collections.Frozen; namespace JCIL.Java.Class; public record struct Version(ushort Minor, ushort Major); [Flags] public enum ClassAccessFlags : ushort { // Declared public; may be accessed from outside its package. Public = 0x0001, // Declared final; no subclasses allowed. Final = 0x0010, // Treat superclass methods specially when invoked by the invokespecial instruction. Super = 0x0020, // Is an interface, not a class. Interface = 0x0200, // Declared abstract; must not be instantiated. Abstract = 0x0400, // Declared synthetic; not present in the source code. Synthetic = 0x1000, // Declared as an annotation type. Annotation = 0x2000, // Declared as an enum type. Enum = 0x4000, } [Flags] public enum FieldAccessFlags : ushort { // Declared public; may be accessed from outside its package. Public = 0x0001, // Declared private; accessible only within the defining class and other classes belonging to the same nest (§5.4.4). Private = 0x0002, // Declared protected; may be accessed within subclasses. Protected = 0x0004, // Declared static. Static = 0x0008, // Declared final; never directly assigned to after object construction (JLS §17.5). Final = 0x0010, // Declared volatile; cannot be cached. Volatile = 0x0040, // Declared transient; not written or read by a persistent object manager. Transient = 0x0080, // Declared synthetic; not present in the source code. Synthetic = 0x1000, // Declared as an element of an enum class. Enum = 0x4000, } public enum IntrinsicType : byte { Void, Boolean, Byte, Char, Short, Int, Long, Float, Double, } public sealed class Field { public readonly FieldAccessFlags AccessFlags; public readonly string Name; public readonly Class Type; public readonly IReadOnlyList Attributes; private Field(FieldAccessFlags flags, string name, Class type, Attribute[] attributes) { AccessFlags = flags; Name = name; Type = type; Attributes = attributes; } public static Field Read(Class parent, Reader reader) { var flags = (FieldAccessFlags)reader.ReadUInt16(); var nameIndex = reader.ReadUInt16(); var descriptorIndex = reader.ReadUInt16(); var attributesCount = reader.ReadUInt16(); var name = (string) parent.GetConstant(nameIndex)!; var descriptor = (string) parent.GetConstant(descriptorIndex)!; var descriptorClass = parent.Loader.GetClass(descriptor.AsMemory()); var attributes = new Attribute[attributesCount]; foreach (ref var attribute in attributes.AsSpan()) attribute = Attribute.Read(parent, reader); return new Field(flags, name, descriptorClass, attributes); } } public interface ISpecialClassMetadata; public sealed record ArrayMetadata(Class ElementClass) : ISpecialClassMetadata; public sealed record IntrinsicMetadata(IntrinsicType Type) : ISpecialClassMetadata; public sealed class Class { public Version Version { get; internal set; } public ClassAccessFlags AccessFlags { get; internal set; } public IReadOnlySet Interfaces { get; internal set; } = FrozenSet.Empty; public IReadOnlyList Fields { get; internal set; } = []; public IReadOnlyList Methods { get; internal set; } = []; public IReadOnlyList Attributes { get; internal set; } = []; public ISpecialClassMetadata? SpecialClassMetadata { get; internal set; } public ReadOnlyMemory Name { get; internal set; } public ReadOnlyMemory Namespace { get; internal set; } public Class? SuperClass { get; internal set { if (ReferenceEquals(value, this)) throw new InvalidOperationException("Super class cannot be self."); field = value; } } = null; private Constant[] _constants = []; public int ConstantCount => _constants.Length; public readonly TypeLoader Loader; internal Class(TypeLoader loader) => Loader = loader; public object? GetConstant(ushort i) { return Constant.Resolve(Loader, _constants, i); } public Method? GetMethod(string name, MethodSignature signature, bool exact = false) { if (exact) { foreach (var method in Methods) if (method.Name == name && ReferenceEquals(method.Type.SpecialClassMetadata, signature)) return method; } else { if (GetMethod(name, signature, true) is {} exactMethod) return exactMethod; foreach (var method in Methods) if (method.Name == name && method.IsCompatibleWith(signature)) return method; } foreach (var interfaceClass in Interfaces) if (interfaceClass.GetMethod(name, signature, exact) is {} method) return method; if(SuperClass is not null && SuperClass != this) return SuperClass.GetMethod(name, signature, exact); return null; } public Field? GetField(string name) { if (Fields.FirstOrDefault(f => f.Name == name) is { } field) return field; if(SuperClass is not null && SuperClass != this) return SuperClass.GetField(name); return null; } public bool IsAssignableTo(Class other) { if (ReferenceEquals(this, other)) return true; if (SuperClass is {} super && super.IsAssignableTo(other)) return true; if (SpecialClassMetadata is IntrinsicMetadata && other == Loader.Object) return true; return Interfaces.Any(i => i.IsAssignableTo(other)); } public void Read(Stream stream) { var reader = new Reader(stream); var magic = reader.ReadUInt32(); if (magic != 0xCAFEBABE) { throw new InvalidDataException( $"Stream does not contain a valid Java class. Expected 0xCAFEBABE, found 0x{magic:X}"); } Version = new Version(reader.ReadUInt16(), reader.ReadUInt16()); var constantPoolCount = reader.ReadUInt16() - 1; var constants = new Constant[constantPoolCount]; _constants = constants; for (var i = 0; i < constantPoolCount; i++) { var constant = Constant.Read(this, Loader, reader); constants[i] = constant; if (constant.Type is ConstantType.Long or ConstantType.Double) constants[i++] = constant; } AccessFlags = (ClassAccessFlags)reader.ReadUInt16(); var thisClass = (string) Constant.Resolve(Loader, constants, (ushort) constants[reader.ReadUInt16() - 1].Value)!; var nsSplit = thisClass.LastIndexOf('/'); if (nsSplit != -1) { Namespace = thisClass.AsMemory()[..nsSplit]; Name = thisClass.AsMemory()[(nsSplit+1)..]; } else { Name = thisClass.AsMemory(); } SuperClass = Constant.Resolve(Loader, constants, reader.ReadUInt16()) as Class ?? (ReferenceEquals(this, Loader.Object) ? null : Loader.Object); var interfacesCount = reader.ReadUInt16(); var interfaces = new HashSet(interfacesCount); Interfaces = interfaces; for (var i = 0; i < interfacesCount; i++) interfaces.Add((Class)GetConstant(reader.ReadUInt16())!); var fieldCount = reader.ReadUInt16(); var fields = new Field[fieldCount]; Fields = fields; foreach (ref var field in fields.AsSpan()) field = Field.Read(this, reader); var methodCount = reader.ReadUInt16(); var methods = new Method[methodCount]; Methods = methods; foreach (ref var method in methods.AsSpan()) method = Method.Read(this, reader); var attributeCount = reader.ReadUInt16(); var attributes = new Attribute[attributeCount]; Attributes = attributes; foreach (ref var attribute in attributes.AsSpan()) attribute = Attribute.Read(this, reader); } public Class WithGenerics(IReadOnlyList generics) { var other = (Class) MemberwiseClone(); other.SuperClass = this; return other; } public override string ToString() => Namespace.IsEmpty ? Name.ToString() : $"{Namespace}/{Name}"; }