From aa658e2bd4ab646c35c5165891deb7d89f328308 Mon Sep 17 00:00:00 2001 From: Richard Elkins Date: Fri, 15 May 2026 15:37:03 -0500 Subject: [PATCH] Fill in missing functions for javaIoRandomAccessFile, javaNioFileFiles, javaUtilArrays, and javaUtilZipCrc32 --- .../javaIo/javaIoRandomAccessFile.go | 966 ++++++++++++++++++ .../javaIo/javaIoRandomAccessFile_test.go | 464 +++++++++ src/gfunction/javaNio/javaNioFileFiles.go | 7 +- .../javaNio/javaNioFileFiles_test.go | 18 +- src/gfunction/javaUtil/javaUtilArrays.go | 2 +- .../javaUtil/javaUtilArraysFill_test.go | 14 + src/gfunction/javaUtil/javaUtilZipCrc32.go | 22 +- 7 files changed, 1478 insertions(+), 15 deletions(-) diff --git a/src/gfunction/javaIo/javaIoRandomAccessFile.go b/src/gfunction/javaIo/javaIoRandomAccessFile.go index 8550f594..16c67623 100644 --- a/src/gfunction/javaIo/javaIoRandomAccessFile.go +++ b/src/gfunction/javaIo/javaIoRandomAccessFile.go @@ -7,11 +7,15 @@ package javaIo import ( + "encoding/binary" + "errors" "fmt" + "io" "jacobin/src/excNames" "jacobin/src/gfunction/ghelpers" "jacobin/src/object" "jacobin/src/types" + "math" "os" ) @@ -41,18 +45,54 @@ func Load_Io_RandomAccessFile() { GFunction: fisClose, } + ghelpers.MethodSignatures["java/io/RandomAccessFile.getChannel()Ljava/nio/channels/FileChannel;"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: ghelpers.TrapFunction, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.getFD()Ljava/io/FileDescriptor;"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: ghelpers.TrapFunction, + } + ghelpers.MethodSignatures["java/io/RandomAccessFile.getFilePointer()J"] = ghelpers.GMeth{ ParamSlots: 0, GFunction: rafGetFilePointer, } + ghelpers.MethodSignatures["java/io/RandomAccessFile.length()J"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafLength, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.length0()J"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafLength, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.open0(Ljava/lang/String;I)V"] = + ghelpers.GMeth{ + ParamSlots: 2, + GFunction: rafOpen0, + } + ghelpers.MethodSignatures["java/io/RandomAccessFile.read()I"] = ghelpers.GMeth{ ParamSlots: 0, GFunction: fisReadOne, } + ghelpers.MethodSignatures["java/io/RandomAccessFile.read0()I"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: fisReadOne, + } + ghelpers.MethodSignatures["java/io/RandomAccessFile.read([B)I"] = ghelpers.GMeth{ ParamSlots: 1, @@ -65,6 +105,216 @@ func Load_Io_RandomAccessFile() { GFunction: fisReadByteArrayOffset, } + ghelpers.MethodSignatures["java/io/RandomAccessFile.readBytes([BII)I"] = + ghelpers.GMeth{ + ParamSlots: 3, + GFunction: fisReadByteArrayOffset, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readFully([B)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafReadFully, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readFully([BII)V"] = + ghelpers.GMeth{ + ParamSlots: 3, + GFunction: rafReadFullyOffset, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readBoolean()Z"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadBoolean, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readByte()B"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadByte, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readChar()C"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadChar, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readDouble()D"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadDouble, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readFloat()F"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadFloat, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readInt()I"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadInt, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readLine()Ljava/lang/String;"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadLine, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readLong()J"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadLong, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readShort()S"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadShort, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readUnsignedByte()I"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadUnsignedByte, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readUnsignedShort()I"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadUnsignedShort, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.readUTF()Ljava/lang/String;"] = + ghelpers.GMeth{ + ParamSlots: 0, + GFunction: rafReadUTF, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.seek(J)V"] = + ghelpers.GMeth{ + ParamSlots: 2, + GFunction: rafSeek, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.seek0(J)V"] = + ghelpers.GMeth{ + ParamSlots: 2, + GFunction: rafSeek, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.setLength(J)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafSetLength, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.setLength0(J)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafSetLength, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.write(I)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWrite, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.write0(I)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWrite, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeBoolean(Z)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWriteBoolean, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeByte(I)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWriteByte, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeShort(I)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWriteShort, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeChar(I)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWriteChar, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeInt(I)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWriteInt, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeLong(J)V"] = + ghelpers.GMeth{ + ParamSlots: 2, + GFunction: rafWriteLong, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeFloat(F)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWriteFloat, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeDouble(D)V"] = + ghelpers.GMeth{ + ParamSlots: 2, + GFunction: rafWriteDouble, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeBytes(Ljava/lang/String;)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWriteBytes, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeChars(Ljava/lang/String;)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWriteChars, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeUTF(Ljava/lang/String;)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWriteUTF, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.write([B)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: rafWriteByteArray, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.write([BII)V"] = + ghelpers.GMeth{ + ParamSlots: 3, + GFunction: rafWriteByteArrayOffset, + } + + ghelpers.MethodSignatures["java/io/RandomAccessFile.writeBytes([BII)V"] = + ghelpers.GMeth{ + ParamSlots: 3, + GFunction: rafWriteByteArrayOffset, + } + // ---------------------------------------------------------- // initIDs - ghelpers.JustReturn // This is a private function that call C native functions. @@ -78,6 +328,47 @@ func Load_Io_RandomAccessFile() { } +// "java/io/RandomAccessFile.open0(Ljava/lang/String;I)V" +func rafOpen0(params []interface{}) interface{} { + // Using the argument path string, open the file for read-only. + pathStr := object.GoStringFromStringObject(params[1].(*object.Object)) + + // Mode flags from java.io.RandomAccessFile + // O_RDONLY = 1 + // O_RDWR = 2 + // O_SYNC = 4 + // O_DSYNC = 8 + mode := params[2].(int64) + + var modeInt int + if (mode & 2) != 0 { + modeInt = os.O_RDWR | os.O_CREATE + } else { + modeInt = os.O_RDONLY + } + + if (mode & 4) != 0 { + modeInt |= os.O_SYNC + } + + // Open the file in the specified mode. + osFile, err := os.OpenFile(pathStr, modeInt, ghelpers.CreateFilePermissions) + if err != nil { + errMsg := fmt.Sprintf("rafOpen0: os.OpenFile(%s) failed, reason: %s", pathStr, err.Error()) + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + + // Copy the file path field into the RandomAccessFile object. + fld := object.Field{Ftype: types.ByteArray, Fvalue: []byte(pathStr)} + params[0].(*object.Object).FieldTable[ghelpers.FilePath] = fld + + // Copy the file handle into the RandomAccessFile object. + fld = object.Field{Ftype: ghelpers.FileHandle, Fvalue: osFile} + params[0].(*object.Object).FieldTable[ghelpers.FileHandle] = fld + + return nil +} + // "java/io/RandomAccessFile.(Ljava/lang/String;Ljava/lang/String;)V" // RandomAccessFile raf = new RandomAccessFile(Stringname, Stringmode); func rafInitString(params []interface{}) interface{} { @@ -185,3 +476,678 @@ func rafGetFilePointer(params []interface{}) interface{} { return posn } + +// "java/io/RandomAccessFile.readFully([B)V" +func rafReadFully(params []interface{}) interface{} { + + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + errMsg := "rafReadFully: java/io/RandomAccessFile object is missing the ghelpers.FileHandle field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + var osFile *os.File = fld.Fvalue.(*os.File) + + // Get the byte array value. + arrObj := params[1].(*object.Object) + fld, ok = arrObj.FieldTable["value"] + if !ok { + errMsg := "rafReadFully: byte array lacks a \"value\" field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + javaBytes := fld.Fvalue.([]types.JavaByte) + buffer := object.GoByteArrayFromJavaByteArray(javaBytes) + + // Read until the buffer is full. + _, err := io.ReadFull(osFile, buffer) + if err != nil { + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { + // In Java, readFully throws EOFException if EOF is reached. + // Since EOFException is not in excNames, we use IOException. + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadFully: EOF reached before reading all bytes") + } + errMsg := fmt.Sprintf("rafReadFully: osFile.Read failed, reason: %s", err.Error()) + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + + // Update the byte array object with the read bytes. + javaBytes = object.JavaByteArrayFromGoByteArray(buffer) + arrObj.FieldTable["value"] = object.Field{Ftype: types.ByteArray, Fvalue: javaBytes} + + return nil +} + +// "java/io/RandomAccessFile.setLength(J)V" +func rafSetLength(params []interface{}) interface{} { + + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + errMsg := "rafSetLength: java/io/RandomAccessFile object is missing the ghelpers.FileHandle field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + var osFile *os.File = fld.Fvalue.(*os.File) + + // Get the new length. + newLength := params[1].(int64) + + // Truncate the file to the new length. + err := osFile.Truncate(newLength) + if err != nil { + errMsg := fmt.Sprintf("rafSetLength: osFile.Truncate(%d) failed, reason: %s", newLength, err.Error()) + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + + return nil +} + +// "java/io/RandomAccessFile.length()J" +func rafLength(params []interface{}) interface{} { + + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + errMsg := "rafLength: java/io/RandomAccessFile object is missing the ghelpers.FileHandle field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + var osFile *os.File = fld.Fvalue.(*os.File) + + fi, err := osFile.Stat() + if err != nil { + errMsg := fmt.Sprintf("rafLength: osFile.Stat failed, reason: %s", err.Error()) + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + + return fi.Size() +} + +// "java/io/RandomAccessFile.seek(J)V" +func rafSeek(params []interface{}) interface{} { + + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + errMsg := "rafSeek: java/io/RandomAccessFile object is missing the ghelpers.FileHandle field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + var osFile *os.File = fld.Fvalue.(*os.File) + + // Get the new position. + pos := params[1].(int64) + + _, err := osFile.Seek(pos, io.SeekStart) + if err != nil { + errMsg := fmt.Sprintf("rafSeek: osFile.Seek(%d) failed, reason: %s", pos, err.Error()) + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + + return nil +} + +// "java/io/RandomAccessFile.readFully([BII)V" +func rafReadFullyOffset(params []interface{}) interface{} { + + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + errMsg := "rafReadFullyOffset: java/io/RandomAccessFile object is missing the ghelpers.FileHandle field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + var osFile *os.File = fld.Fvalue.(*os.File) + + // Get the byte array value. + arrObj := params[1].(*object.Object) + fld, ok = arrObj.FieldTable["value"] + if !ok { + errMsg := "rafReadFullyOffset: byte array lacks a \"value\" field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + javaBytes := fld.Fvalue.([]types.JavaByte) + + offset := int(params[2].(int64)) + length := int(params[3].(int64)) + + if offset < 0 || length < 0 || offset+length > len(javaBytes) { + return ghelpers.GetGErrBlk(excNames.IndexOutOfBoundsException, "rafReadFullyOffset: index out of bounds") + } + + buffer := make([]byte, length) + + // Read until the buffer is full. + _, err := io.ReadFull(osFile, buffer) + if err != nil { + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadFullyOffset: EOF reached before reading all bytes") + } + errMsg := fmt.Sprintf("rafReadFullyOffset: osFile.Read failed, reason: %s", err.Error()) + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + + // Update the byte array object with the read bytes. + for i := range length { + javaBytes[offset+i] = types.JavaByte(buffer[i]) + } + + return nil +} + +// "java/io/RandomAccessFile.write(I)V" +func rafWrite(params []interface{}) interface{} { + + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + errMsg := "rafWrite: java/io/RandomAccessFile object is missing the ghelpers.FileHandle field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + var osFile *os.File = fld.Fvalue.(*os.File) + + // Get the byte to write. + b := byte(params[1].(int64)) + + _, err := osFile.Write([]byte{b}) + if err != nil { + errMsg := fmt.Sprintf("rafWrite: osFile.Write failed, reason: %s", err.Error()) + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + + return nil +} + +// "java/io/RandomAccessFile.write([B)V" +func rafWriteByteArray(params []interface{}) interface{} { + + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + errMsg := "rafWriteByteArray: java/io/RandomAccessFile object is missing the ghelpers.FileHandle field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + var osFile *os.File = fld.Fvalue.(*os.File) + + // Get the byte array value. + arrObj := params[1].(*object.Object) + fld, ok = arrObj.FieldTable["value"] + if !ok { + errMsg := "rafWriteByteArray: byte array lacks a \"value\" field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + javaBytes := fld.Fvalue.([]types.JavaByte) + buffer := object.GoByteArrayFromJavaByteArray(javaBytes) + + _, err := osFile.Write(buffer) + if err != nil { + errMsg := fmt.Sprintf("rafWriteByteArray: osFile.Write failed, reason: %s", err.Error()) + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + + return nil +} + +// "java/io/RandomAccessFile.write([BII)V" +func rafWriteByteArrayOffset(params []interface{}) interface{} { + + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + errMsg := "rafWriteByteArrayOffset: java/io/RandomAccessFile object is missing the ghelpers.FileHandle field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + var osFile *os.File = fld.Fvalue.(*os.File) + + // Get the byte array value. + arrObj := params[1].(*object.Object) + fld, ok = arrObj.FieldTable["value"] + if !ok { + errMsg := "rafWriteByteArrayOffset: byte array lacks a \"value\" field" + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + javaBytes := fld.Fvalue.([]types.JavaByte) + + offset := int(params[2].(int64)) + length := int(params[3].(int64)) + + if offset < 0 || length < 0 || offset+length > len(javaBytes) { + return ghelpers.GetGErrBlk(excNames.IndexOutOfBoundsException, "rafWriteByteArrayOffset: index out of bounds") + } + + buffer := make([]byte, length) + for i := range length { + buffer[i] = byte(javaBytes[offset+i]) + } + + _, err := osFile.Write(buffer) + if err != nil { + errMsg := fmt.Sprintf("rafWriteByteArrayOffset: osFile.Write failed, reason: %s", err.Error()) + return ghelpers.GetGErrBlk(excNames.IOException, errMsg) + } + + return nil +} + +// "java/io/RandomAccessFile.readBoolean()Z" +func rafReadBoolean(params []interface{}) interface{} { + val := rafReadUnsignedByte(params) + if err, ok := val.(*object.Object); ok { + return err + } + if val.(int64) != 0 { + return int64(1) // true + } + return int64(0) // false +} + +// "java/io/RandomAccessFile.readByte()B" +func rafReadByte(params []interface{}) interface{} { + val := rafReadUnsignedByte(params) + if err, ok := val.(*object.Object); ok { + return err + } + return int64(int8(val.(int64))) +} + +// "java/io/RandomAccessFile.readChar()C" +func rafReadChar(params []interface{}) interface{} { + return rafReadUnsignedShort(params) +} + +// "java/io/RandomAccessFile.readDouble()D" +func rafReadDouble(params []interface{}) interface{} { + val := rafReadLong(params) + if err, ok := val.(*object.Object); ok { + return err + } + return math.Float64frombits(uint64(val.(int64))) +} + +// "java/io/RandomAccessFile.readFloat()F" +func rafReadFloat(params []interface{}) interface{} { + val := rafReadInt(params) + if err, ok := val.(*object.Object); ok { + return err + } + return float64(math.Float32frombits(uint32(val.(int64)))) +} + +// "java/io/RandomAccessFile.readInt()I" +func rafReadInt(params []interface{}) interface{} { + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadInt: missing FileHandle") + } + osFile := fld.Fvalue.(*os.File) + + var b [4]byte + _, err := io.ReadFull(osFile, b[:]) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadInt: EOF reached") + } + return int64(int32(binary.BigEndian.Uint32(b[:]))) +} + +// "java/io/RandomAccessFile.readLong()J" +func rafReadLong(params []interface{}) interface{} { + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadLong: missing FileHandle") + } + osFile := fld.Fvalue.(*os.File) + + var b [8]byte + _, err := io.ReadFull(osFile, b[:]) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadLong: EOF reached") + } + return int64(binary.BigEndian.Uint64(b[:])) +} + +// "java/io/RandomAccessFile.readShort()S" +func rafReadShort(params []interface{}) interface{} { + val := rafReadUnsignedShort(params) + if err, ok := val.(*object.Object); ok { + return err + } + return int64(int16(val.(int64))) +} + +// "java/io/RandomAccessFile.readUnsignedByte()I" +func rafReadUnsignedByte(params []interface{}) interface{} { + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadUnsignedByte: missing FileHandle") + } + osFile := fld.Fvalue.(*os.File) + + var b [1]byte + _, err := osFile.Read(b[:]) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadUnsignedByte: EOF reached") + } + return int64(b[0]) +} + +// "java/io/RandomAccessFile.readUnsignedShort()I" +func rafReadUnsignedShort(params []interface{}) interface{} { + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadUnsignedShort: missing FileHandle") + } + osFile := fld.Fvalue.(*os.File) + + var b [2]byte + _, err := io.ReadFull(osFile, b[:]) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadUnsignedShort: EOF reached") + } + return int64(binary.BigEndian.Uint16(b[:])) +} + +// "java/io/RandomAccessFile.readLine()Ljava/lang/String;" +func rafReadLine(params []interface{}) interface{} { + // Get file handle. + obj := params[0].(*object.Object) + fld, ok := obj.FieldTable[ghelpers.FileHandle] + if !ok { + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadLine: missing FileHandle") + } + osFile := fld.Fvalue.(*os.File) + + var line []byte + var b [1]byte + for { + _, err := osFile.Read(b[:]) + if err != nil { + if len(line) == 0 { + return object.IsNull(nil) + } + break + } + if b[0] == '\n' { + break + } + if b[0] == '\r' { + // Check for next \n + pos, _ := osFile.Seek(0, io.SeekCurrent) + _, err = osFile.Read(b[:]) + if err == nil && b[0] != '\n' { + osFile.Seek(pos, io.SeekStart) + } + break + } + line = append(line, b[0]) + } + + // In DataInputStream.readLine, it's NOT UTF-8, it's just ISO-8859-1 (one byte per char) + // RandomAccessFile.readLine also follows this. + return object.StringObjectFromGoString(string(line)) +} + +// "java/io/RandomAccessFile.readUTF()Ljava/lang/String;" +func rafReadUTF(params []interface{}) interface{} { + lenVal := rafReadUnsignedShort(params) + if err, ok := lenVal.(*object.Object); ok { + return err + } + utflen := lenVal.(int64) + + // Get file handle. + obj := params[0].(*object.Object) + osFile := obj.FieldTable[ghelpers.FileHandle].Fvalue.(*os.File) + + bytearr := make([]byte, utflen) + _, err := io.ReadFull(osFile, bytearr) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafReadUTF: EOF reached") + } + + // Simple Modified UTF-8 to String conversion. + var chararr = make([]rune, 0, utflen) + var count = 0 + for count < int(utflen) { + c := int(bytearr[count] & 0xff) + if c > 127 { + break + } + count++ + chararr = append(chararr, rune(c)) + } + + for count < int(utflen) { + c := int(bytearr[count] & 0xff) + switch c >> 4 { + case 0, 1, 2, 3, 4, 5, 6, 7: + /* 0xxxxxxx*/ + count++ + chararr = append(chararr, rune(c)) + case 12, 13: + /* 110x xxxx 10xx xxxx*/ + count += 2 + if count > int(utflen) { + return ghelpers.GetGErrBlk(excNames.IOException, "malformed input: partial character at end") + } + char2 := int(bytearr[count-1]) + if (char2 & 0xC0) != 0x80 { + return ghelpers.GetGErrBlk(excNames.IOException, "malformed input around byte "+fmt.Sprint(count)) + } + chararr = append(chararr, rune(((c&0x1F)<<6)|(char2&0x3F))) + case 14: + /* 1110 xxxx 10xx xxxx 10xx xxxx */ + count += 3 + if count > int(utflen) { + return ghelpers.GetGErrBlk(excNames.IOException, "malformed input: partial character at end") + } + char2 := int(bytearr[count-2]) + char3 := int(bytearr[count-1]) + if ((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80) { + return ghelpers.GetGErrBlk(excNames.IOException, "malformed input around byte "+fmt.Sprint(count-1)) + } + chararr = append(chararr, rune(((c&0x0F)<<12)|((char2&0x3F)<<6)|((char3&0x3F)<<0))) + default: + /* 10xx xxxx, 1111 xxxx */ + return ghelpers.GetGErrBlk(excNames.IOException, "malformed input around byte "+fmt.Sprint(count)) + } + } + + return object.StringObjectFromGoString(string(chararr)) +} + +// "java/io/RandomAccessFile.writeBoolean(Z)V" +func rafWriteBoolean(params []interface{}) interface{} { + obj := params[0].(*object.Object) + osFile := obj.FieldTable[ghelpers.FileHandle].Fvalue.(*os.File) + v := params[1].(int64) + var b byte + if v != 0 { + b = 1 + } + _, err := osFile.Write([]byte{b}) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafWriteBoolean: "+err.Error()) + } + return nil +} + +// "java/io/RandomAccessFile.writeByte(I)V" +func rafWriteByte(params []interface{}) interface{} { + return rafWrite(params) +} + +// "java/io/RandomAccessFile.writeShort(I)V" +func rafWriteShort(params []interface{}) interface{} { + obj := params[0].(*object.Object) + osFile := obj.FieldTable[ghelpers.FileHandle].Fvalue.(*os.File) + v := int16(params[1].(int64)) + var b [2]byte + binary.BigEndian.PutUint16(b[:], uint16(v)) + _, err := osFile.Write(b[:]) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafWriteShort: "+err.Error()) + } + return nil +} + +// "java/io/RandomAccessFile.writeChar(I)V" +func rafWriteChar(params []interface{}) interface{} { + obj := params[0].(*object.Object) + osFile := obj.FieldTable[ghelpers.FileHandle].Fvalue.(*os.File) + v := uint16(params[1].(int64)) + var b [2]byte + binary.BigEndian.PutUint16(b[:], v) + _, err := osFile.Write(b[:]) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafWriteChar: "+err.Error()) + } + return nil +} + +// "java/io/RandomAccessFile.writeInt(I)V" +func rafWriteInt(params []interface{}) interface{} { + obj := params[0].(*object.Object) + osFile := obj.FieldTable[ghelpers.FileHandle].Fvalue.(*os.File) + v := int32(params[1].(int64)) + var b [4]byte + binary.BigEndian.PutUint32(b[:], uint32(v)) + _, err := osFile.Write(b[:]) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafWriteInt: "+err.Error()) + } + return nil +} + +// "java/io/RandomAccessFile.writeLong(J)V" +func rafWriteLong(params []interface{}) interface{} { + obj := params[0].(*object.Object) + osFile := obj.FieldTable[ghelpers.FileHandle].Fvalue.(*os.File) + v := params[1].(int64) + var b [8]byte + binary.BigEndian.PutUint64(b[:], uint64(v)) + _, err := osFile.Write(b[:]) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafWriteLong: "+err.Error()) + } + return nil +} + +// "java/io/RandomAccessFile.writeFloat(F)V" +func rafWriteFloat(params []interface{}) interface{} { + obj := params[0].(*object.Object) + osFile := obj.FieldTable[ghelpers.FileHandle].Fvalue.(*os.File) + v := float32(params[1].(float64)) + var b [4]byte + binary.BigEndian.PutUint32(b[:], math.Float32bits(v)) + _, err := osFile.Write(b[:]) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafWriteFloat: "+err.Error()) + } + return nil +} + +// "java/io/RandomAccessFile.writeDouble(D)V" +func rafWriteDouble(params []interface{}) interface{} { + obj := params[0].(*object.Object) + osFile := obj.FieldTable[ghelpers.FileHandle].Fvalue.(*os.File) + v := params[1].(float64) + var b [8]byte + binary.BigEndian.PutUint64(b[:], math.Float64bits(v)) + _, err := osFile.Write(b[:]) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafWriteDouble: "+err.Error()) + } + return nil +} + +// "java/io/RandomAccessFile.writeBytes(Ljava/lang/String;)V" +func rafWriteBytes(params []interface{}) interface{} { + obj := params[0].(*object.Object) + osFile := obj.FieldTable[ghelpers.FileHandle].Fvalue.(*os.File) + s := object.GoStringFromStringObject(params[1].(*object.Object)) + b := make([]byte, len(s)) + for i := range len(s) { + b[i] = byte(s[i]) + } + _, err := osFile.Write(b) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafWriteBytes: "+err.Error()) + } + return nil +} + +// "java/io/RandomAccessFile.writeChars(Ljava/lang/String;)V" +func rafWriteChars(params []interface{}) interface{} { + obj := params[0].(*object.Object) + osFile := obj.FieldTable[ghelpers.FileHandle].Fvalue.(*os.File) + s := object.GoStringFromStringObject(params[1].(*object.Object)) + b := make([]byte, len(s)*2) + for i, r := range s { + binary.BigEndian.PutUint16(b[i*2:], uint16(r)) + } + _, err := osFile.Write(b) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafWriteChars: "+err.Error()) + } + return nil +} + +// "java/io/RandomAccessFile.writeUTF(Ljava/lang/String;)V" +func rafWriteUTF(params []interface{}) interface{} { + obj := params[0].(*object.Object) + osFile := obj.FieldTable[ghelpers.FileHandle].Fvalue.(*os.File) + str := object.GoStringFromStringObject(params[1].(*object.Object)) + + var utflen int + for _, r := range str { + if r >= 0x0001 && r <= 0x007F { + utflen++ + } else if r > 0x07FF { + utflen += 3 + } else { + utflen += 2 + } + } + + if utflen > 65535 { + return ghelpers.GetGErrBlk(excNames.UTFDataFormatException, fmt.Sprintf("encoded string too long: %d bytes", utflen)) + } + + bytearr := make([]byte, utflen+2) + binary.BigEndian.PutUint16(bytearr[0:], uint16(utflen)) + + count := 2 + for _, r := range str { + if r >= 0x0001 && r <= 0x007F { + bytearr[count] = byte(r) + count++ + } else if r > 0x07FF { + bytearr[count] = byte(0xE0 | ((r >> 12) & 0x0F)) + bytearr[count+1] = byte(0x80 | ((r >> 6) & 0x3F)) + bytearr[count+2] = byte(0x80 | ((r >> 0) & 0x3F)) + count += 3 + } else { + bytearr[count] = byte(0xC0 | ((r >> 6) & 0x1F)) + bytearr[count+1] = byte(0x80 | ((r >> 0) & 0x3F)) + count += 2 + } + } + + _, err := osFile.Write(bytearr) + if err != nil { + return ghelpers.GetGErrBlk(excNames.IOException, "rafWriteUTF: "+err.Error()) + } + return nil +} diff --git a/src/gfunction/javaIo/javaIoRandomAccessFile_test.go b/src/gfunction/javaIo/javaIoRandomAccessFile_test.go index 051728d0..762060dd 100644 --- a/src/gfunction/javaIo/javaIoRandomAccessFile_test.go +++ b/src/gfunction/javaIo/javaIoRandomAccessFile_test.go @@ -1,6 +1,8 @@ package javaIo import ( + "bytes" + "encoding/binary" "io" "jacobin/src/gfunction/ghelpers" "jacobin/src/globals" @@ -10,6 +12,38 @@ import ( "testing" ) +func TestRafOpen0(t *testing.T) { + tmpFile, err := os.CreateTemp("", "raf_open0_test") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + tmpFile.Close() + + rafObj := newRAFObject() + pathStrObj := object.StringObjectFromGoString(tmpFile.Name()) + + // Test read mode (O_RDONLY = 1) + params := []interface{}{rafObj, pathStrObj, int64(1)} + ret := rafOpen0(params) + if ret != nil { + t.Fatalf("rafOpen0 (read) returned error: %v", ret) + } + fld, _ := rafObj.FieldTable[ghelpers.FileHandle] + fh := fld.Fvalue.(*os.File) + fh.Close() + + // Test read-write mode (O_RDWR = 2) + params = []interface{}{rafObj, pathStrObj, int64(2)} + ret = rafOpen0(params) + if ret != nil { + t.Fatalf("rafOpen0 (read-write) returned error: %v", ret) + } + fld, _ = rafObj.FieldTable[ghelpers.FileHandle] + fh = fld.Fvalue.(*os.File) + fh.Close() +} + // helper to create a new RandomAccessFile object with initialized FieldTable func newRAFObject() *object.Object { return &object.Object{FieldTable: make(map[string]object.Field)} @@ -247,3 +281,433 @@ func TestFisReadByteArrayOffset(t *testing.T) { t.Errorf("fisReadByteArrayOffset expected read %d bytes, got %d", length, numRead) } } + +func TestRafReadFully(t *testing.T) { + globals.InitStringPool() + tmpFile, err := os.CreateTemp("", "raf_read_fully") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + content := []byte("hello readFully") + if _, err := tmpFile.Write(content); err != nil { + t.Fatal(err) + } + tmpFile.Sync() + tmpFile.Seek(0, io.SeekStart) + + rafObj := newRAFObject() + rafObj.FieldTable[ghelpers.FileHandle] = object.Field{Ftype: ghelpers.FileHandle, Fvalue: tmpFile} + + // Case 1: Read fully successfully + byteArray := make([]types.JavaByte, 5) + javaByteArrayObj := object.MakePrimitiveObject(types.ByteArray, types.ByteArray, byteArray) + params := []interface{}{rafObj, javaByteArrayObj} + ret := rafReadFully(params) + + if ret != nil { + t.Errorf("rafReadFully failed: %v", ret) + } + + readVal := javaByteArrayObj.FieldTable["value"].Fvalue.([]types.JavaByte) + goReadVal := object.GoByteArrayFromJavaByteArray(readVal) + if !bytes.Equal(goReadVal, content[:5]) { + t.Errorf("Expected %v, got %v", content[:5], goReadVal) + } + + // Case 2: Read fully to the end + byteArray2 := make([]types.JavaByte, len(content)-5) + javaByteArrayObj2 := object.MakePrimitiveObject(types.ByteArray, types.ByteArray, byteArray2) + params2 := []interface{}{rafObj, javaByteArrayObj2} + ret2 := rafReadFully(params2) + + if ret2 != nil { + t.Errorf("rafReadFully failed at step 2: %v", ret2) + } + + readVal2 := javaByteArrayObj2.FieldTable["value"].Fvalue.([]types.JavaByte) + goReadVal2 := object.GoByteArrayFromJavaByteArray(readVal2) + if !bytes.Equal(goReadVal2, content[5:]) { + t.Errorf("Expected %v, got %v", content[5:], goReadVal2) + } + + // Case 3: Read fully past EOF - should return error + byteArray3 := make([]types.JavaByte, 1) + javaByteArrayObj3 := object.MakePrimitiveObject(types.ByteArray, types.ByteArray, byteArray3) + params3 := []interface{}{rafObj, javaByteArrayObj3} + ret3 := rafReadFully(params3) + + if ret3 == nil { + t.Errorf("Expected error for readFully past EOF, got nil") + } +} + +func TestRafSetLength(t *testing.T) { + tmpFile, err := os.CreateTemp("", "raf_set_length") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + content := []byte("hello setLength") + if _, err := tmpFile.Write(content); err != nil { + t.Fatal(err) + } + tmpFile.Sync() + + rafObj := newRAFObject() + rafObj.FieldTable[ghelpers.FileHandle] = object.Field{Ftype: ghelpers.FileHandle, Fvalue: tmpFile} + + // Case 1: Shorten the file + newLen := int64(5) + params := []interface{}{rafObj, newLen} + ret := rafSetLength(params) + + if ret != nil { + t.Errorf("rafSetLength failed: %v", ret) + } + + fi, _ := tmpFile.Stat() + if fi.Size() != newLen { + t.Errorf("Expected size %d, got %d", newLen, fi.Size()) + } + + // Case 2: Lengthen the file + newLen = int64(20) + params = []interface{}{rafObj, newLen} + ret = rafSetLength(params) + + if ret != nil { + t.Errorf("rafSetLength failed: %v", ret) + } + + fi, _ = tmpFile.Stat() + if fi.Size() != newLen { + t.Errorf("Expected size %d, got %d", newLen, fi.Size()) + } +} + +func TestRafLengthAndSeek(t *testing.T) { + tmpFile, err := os.CreateTemp("", "raf_length_seek") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + content := []byte("hello length and seek") + if _, err := tmpFile.Write(content); err != nil { + t.Fatal(err) + } + tmpFile.Sync() + + rafObj := newRAFObject() + rafObj.FieldTable[ghelpers.FileHandle] = object.Field{Ftype: ghelpers.FileHandle, Fvalue: tmpFile} + + // Test length() + ret := rafLength([]interface{}{rafObj}) + if ret.(int64) != int64(len(content)) { + t.Errorf("Expected length %d, got %d", len(content), ret) + } + + // Test seek() + rafSeek([]interface{}{rafObj, int64(6)}) + pos := rafGetFilePointer([]interface{}{rafObj}) + if pos.(int64) != 6 { + t.Errorf("Expected position 6, got %d", pos) + } +} + +func TestRafReadFullyOffset(t *testing.T) { + globals.InitStringPool() + tmpFile, err := os.CreateTemp("", "raf_read_fully_offset") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + content := []byte("0123456789") + if _, err := tmpFile.Write(content); err != nil { + t.Fatal(err) + } + tmpFile.Sync() + tmpFile.Seek(0, io.SeekStart) + + rafObj := newRAFObject() + rafObj.FieldTable[ghelpers.FileHandle] = object.Field{Ftype: ghelpers.FileHandle, Fvalue: tmpFile} + + byteArray := make([]types.JavaByte, 10) + javaByteArrayObj := object.MakePrimitiveObject(types.ByteArray, types.ByteArray, byteArray) + + // Read 4 bytes starting from index 2 in the array + params := []interface{}{rafObj, javaByteArrayObj, int64(2), int64(4)} + ret := rafReadFullyOffset(params) + + if ret != nil { + t.Errorf("rafReadFullyOffset failed: %v", ret) + } + + readVal := javaByteArrayObj.FieldTable["value"].Fvalue.([]types.JavaByte) + if readVal[2] != types.JavaByte('0') || readVal[5] != types.JavaByte('3') { + t.Errorf("Unexpected read value: %v", readVal) + } +} + +func TestRafWriteMethods(t *testing.T) { + globals.InitStringPool() + tmpFile, err := os.CreateTemp("", "raf_write_test") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + rafObj := newRAFObject() + rafObj.FieldTable[ghelpers.FileHandle] = object.Field{Ftype: ghelpers.FileHandle, Fvalue: tmpFile} + + // Test write(I)V + rafWrite([]interface{}{rafObj, int64('A')}) + + // Test write([B)V + byteArray := []types.JavaByte{types.JavaByte('B'), types.JavaByte('C')} + javaByteArrayObj := object.MakePrimitiveObject(types.ByteArray, types.ByteArray, byteArray) + rafWriteByteArray([]interface{}{rafObj, javaByteArrayObj}) + + // Test write([BII)V + byteArray2 := []types.JavaByte{types.JavaByte('X'), types.JavaByte('D'), types.JavaByte('E'), types.JavaByte('Y')} + javaByteArrayObj2 := object.MakePrimitiveObject(types.ByteArray, types.ByteArray, byteArray2) + rafWriteByteArrayOffset([]interface{}{rafObj, javaByteArrayObj2, int64(1), int64(2)}) + + tmpFile.Sync() + tmpFile.Seek(0, io.SeekStart) + written, _ := io.ReadAll(tmpFile) + expected := []byte("ABCDE") + if !bytes.Equal(written, expected) { + t.Errorf("Expected %s, got %s", expected, written) + } +} + +func TestRafReadDataMethods(t *testing.T) { + globals.InitStringPool() + tmpFile, err := os.CreateTemp("", "raf_read_data_test") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + // Prepare data + // Boolean(true), Byte(0x12), Char('A'), Double(1.23), Float(4.56), Int(0x12345678), Long(0x1122334455667788), Short(0x1234), UnsignedByte(0xFE), UnsignedShort(0xFEDC) + var buf bytes.Buffer + buf.WriteByte(1) // Boolean true + buf.WriteByte(0x12) // Byte + binary.Write(&buf, binary.BigEndian, uint16('A')) // Char + binary.Write(&buf, binary.BigEndian, 1.23) // Double + binary.Write(&buf, binary.BigEndian, float32(4.56)) // Float + binary.Write(&buf, binary.BigEndian, int32(0x12345678)) + binary.Write(&buf, binary.BigEndian, int64(0x1122334455667788)) + binary.Write(&buf, binary.BigEndian, int16(0x1234)) + buf.WriteByte(0xFE) // UnsignedByte + binary.Write(&buf, binary.BigEndian, uint16(0xFEDC)) // UnsignedShort + buf.WriteString("line1\nline2\rline3\r\n") // readLine data + + if _, err := tmpFile.Write(buf.Bytes()); err != nil { + t.Fatal(err) + } + tmpFile.Sync() + tmpFile.Seek(0, io.SeekStart) + + rafObj := newRAFObject() + rafObj.FieldTable[ghelpers.FileHandle] = object.Field{Ftype: ghelpers.FileHandle, Fvalue: tmpFile} + + params := []interface{}{rafObj} + + // readBoolean + if res := rafReadBoolean(params); res.(int64) != 1 { + t.Errorf("readBoolean: expected 1, got %v", res) + } + // readByte + if res := rafReadByte(params); res.(int64) != 0x12 { + t.Errorf("readByte: expected 0x12, got %v", res) + } + // readChar + if res := rafReadChar(params); res.(int64) != int64('A') { + t.Errorf("readChar: expected %d, got %v", int64('A'), res) + } + // readDouble + if res := rafReadDouble(params); res.(float64) != 1.23 { + t.Errorf("readDouble: expected 1.23, got %v", res) + } + // readFloat + if res := rafReadFloat(params); float32(res.(float64)) != 4.56 { + t.Errorf("readFloat: expected 4.56, got %v", res) + } + // readInt + if res := rafReadInt(params); res.(int64) != 0x12345678 { + t.Errorf("readInt: expected 0x12345678, got %v", res) + } + // readLong + if res := rafReadLong(params); res.(int64) != 0x1122334455667788 { + t.Errorf("readLong: expected 0x1122334455667788, got %v", res) + } + // readShort + if res := rafReadShort(params); res.(int64) != 0x1234 { + t.Errorf("readShort: expected 0x1234, got %v", res) + } + // readUnsignedByte + if res := rafReadUnsignedByte(params); res.(int64) != 0xFE { + t.Errorf("readUnsignedByte: expected 0xFE, got %v", res) + } + // readUnsignedShort + if res := rafReadUnsignedShort(params); res.(int64) != 0xFEDC { + t.Errorf("readUnsignedShort: expected 0xFEDC, got %v", res) + } + + // readLine + if res := rafReadLine(params); object.GoStringFromStringObject(res.(*object.Object)) != "line1" { + t.Errorf("readLine1: expected 'line1', got '%s'", object.GoStringFromStringObject(res.(*object.Object))) + } + if res := rafReadLine(params); object.GoStringFromStringObject(res.(*object.Object)) != "line2" { + t.Errorf("readLine2: expected 'line2', got '%s'", object.GoStringFromStringObject(res.(*object.Object))) + } + if res := rafReadLine(params); object.GoStringFromStringObject(res.(*object.Object)) != "line3" { + t.Errorf("readLine3: expected 'line3', got '%s'", object.GoStringFromStringObject(res.(*object.Object))) + } +} + +func TestRafReadUTF(t *testing.T) { + globals.InitStringPool() + tmpFile, err := os.CreateTemp("", "raf_read_utf_test") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + // Prepare data: 2-byte length, then modified UTF-8 + // Modified UTF-8 for \u0000 is 0xC0 0x80 + // But let's use what Java would produce. + // "Hello, 世界! " is standard UTF-8. + // \u0000 is 0xC0 0x80. + utfBytes := append([]byte("Hello, 世界! "), 0xC0, 0x80) + var buf bytes.Buffer + binary.Write(&buf, binary.BigEndian, uint16(len(utfBytes))) + buf.Write(utfBytes) + + if _, err := tmpFile.Write(buf.Bytes()); err != nil { + t.Fatal(err) + } + tmpFile.Sync() + tmpFile.Seek(0, io.SeekStart) + + rafObj := newRAFObject() + rafObj.FieldTable[ghelpers.FileHandle] = object.Field{Ftype: ghelpers.FileHandle, Fvalue: tmpFile} + + res := rafReadUTF([]interface{}{rafObj}) + gotStr := object.GoStringFromStringObject(res.(*object.Object)) + expectedStr := "Hello, 世界! \u0000" + if gotStr != expectedStr { + t.Errorf("readUTF: expected %q, got %q", expectedStr, gotStr) + } +} + +func TestRafWriteDataMethods(t *testing.T) { + globals.InitStringPool() + tmpFile, err := os.CreateTemp("", "raf_write_data_test") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + rafObj := newRAFObject() + rafObj.FieldTable[ghelpers.FileHandle] = object.Field{Ftype: ghelpers.FileHandle, Fvalue: tmpFile} + + // writeBoolean + rafWriteBoolean([]interface{}{rafObj, int64(1)}) + // writeByte + rafWriteByte([]interface{}{rafObj, int64(0x12)}) + // writeShort + rafWriteShort([]interface{}{rafObj, int64(0x1234)}) + // writeChar + rafWriteChar([]interface{}{rafObj, int64('A')}) + // writeInt + rafWriteInt([]interface{}{rafObj, int64(0x12345678)}) + // writeLong + rafWriteLong([]interface{}{rafObj, int64(0x1122334455667788)}) + // writeFloat + rafWriteFloat([]interface{}{rafObj, 4.56}) + // writeDouble + rafWriteDouble([]interface{}{rafObj, 1.23}) + // writeBytes + rafWriteBytes([]interface{}{rafObj, object.StringObjectFromGoString("abc")}) + // writeChars + rafWriteChars([]interface{}{rafObj, object.StringObjectFromGoString("ABC")}) + // writeUTF + rafWriteUTF([]interface{}{rafObj, object.StringObjectFromGoString("Hello, 世界! \u0000")}) + + tmpFile.Sync() + tmpFile.Seek(0, io.SeekStart) + + // Now read back and verify + params := []interface{}{rafObj} + + // readBoolean + if res := rafReadBoolean(params); res.(int64) != 1 { + t.Errorf("readBoolean: expected 1, got %v", res) + } + // readByte + if res := rafReadByte(params); res.(int64) != 0x12 { + t.Errorf("readByte: expected 0x12, got %v", res) + } + // readShort + if res := rafReadShort(params); res.(int64) != 0x1234 { + t.Errorf("readShort: expected 0x1234, got %v", res) + } + // readChar + if res := rafReadChar(params); res.(int64) != int64('A') { + t.Errorf("readChar: expected %d, got %v", int64('A'), res) + } + // readInt + if res := rafReadInt(params); res.(int64) != 0x12345678 { + t.Errorf("readInt: expected 0x12345678, got %v", res) + } + // readLong + if res := rafReadLong(params); res.(int64) != 0x1122334455667788 { + t.Errorf("readLong: expected 0x1122334455667788, got %v", res) + } + // readFloat + if res := rafReadFloat(params); float32(res.(float64)) != 4.56 { + t.Errorf("readFloat: expected 4.56, got %v", res) + } + // readDouble + if res := rafReadDouble(params); res.(float64) != 1.23 { + t.Errorf("readDouble: expected 1.23, got %v", res) + } + + // writeBytes verification (read back 3 bytes) + b3 := make([]byte, 3) + io.ReadFull(tmpFile, b3) + if string(b3) != "abc" { + t.Errorf("writeBytes: expected 'abc', got %q", string(b3)) + } + + // writeChars verification (read back 3 chars = 6 bytes) + b6 := make([]byte, 6) + io.ReadFull(tmpFile, b6) + if string(b6) != "\x00A\x00B\x00C" { + t.Errorf("writeChars: unexpected content %v", b6) + } + + // writeUTF verification + res := rafReadUTF(params) + gotStr := object.GoStringFromStringObject(res.(*object.Object)) + expectedStr := "Hello, 世界! \u0000" + if gotStr != expectedStr { + t.Errorf("writeUTF/readUTF: expected %q, got %q", expectedStr, gotStr) + } +} diff --git a/src/gfunction/javaNio/javaNioFileFiles.go b/src/gfunction/javaNio/javaNioFileFiles.go index 9143d15e..1cc9f8e9 100644 --- a/src/gfunction/javaNio/javaNioFileFiles.go +++ b/src/gfunction/javaNio/javaNioFileFiles.go @@ -18,11 +18,6 @@ import ( "jacobin/src/types" ) -// Load_Nio_File_Files registers implementations for java.nio.file.Files public methods (Java 21). -// Policy agreed with user: -// - Implement core filesystem ops. -// - Stream-returning and complex attribute-view methods remain trapped. -// - Where platform limitations apply, return UnsupportedOperationException. func Load_Nio_File_Files() { // @@ -422,7 +417,7 @@ func filesReadAllBytes(params []interface{}) interface{} { if err != nil { return ghelpers.GetGErrBlk(excNames.IOException, fmt.Sprintf("Files.readAllBytes: %s", err.Error())) } - return object.JavaByteArrayFromGoByteArray(data) + return object.MakeArrayFromRawArray(object.JavaByteArrayFromGoByteArray(data)) } func filesWriteBytes(params []interface{}) interface{} { diff --git a/src/gfunction/javaNio/javaNioFileFiles_test.go b/src/gfunction/javaNio/javaNioFileFiles_test.go index 8af1a1d8..e116054c 100644 --- a/src/gfunction/javaNio/javaNioFileFiles_test.go +++ b/src/gfunction/javaNio/javaNioFileFiles_test.go @@ -20,7 +20,7 @@ import ( func Test_Files_Exists_And_NotExists(t *testing.T) { // Ensure string pool and related globals are initialized for object/string creation - globals.InitStringPool() + globals.InitGlobals("test") dir := t.TempDir() f := filepath.Join(dir, "a.txt") if err := os.WriteFile(f, []byte("x"), 0o644); err != nil { @@ -45,6 +45,7 @@ func Test_Files_Exists_And_NotExists(t *testing.T) { } func Test_Files_IsDirectory_IsRegularFile(t *testing.T) { + globals.InitGlobals("test") dir := t.TempDir() dpath := newPath(dir) f := filepath.Join(dir, "b.bin") @@ -68,6 +69,7 @@ func Test_Files_IsDirectory_IsRegularFile(t *testing.T) { } func Test_Files_Size(t *testing.T) { + globals.InitGlobals("test") dir := t.TempDir() f := filepath.Join(dir, "c.txt") data := []byte("hello") @@ -86,6 +88,7 @@ func Test_Files_Size(t *testing.T) { } func Test_Files_CreateFile_Directory_Delete_DeleteIfExists(t *testing.T) { + globals.InitGlobals("test") dir := t.TempDir() f := newPath(filepath.Join(dir, "d.txt")) res := filesCreateFile([]interface{}{f, object.Null}) @@ -126,6 +129,7 @@ func Test_Files_CreateFile_Directory_Delete_DeleteIfExists(t *testing.T) { } func Test_Files_Copy_And_Move(t *testing.T) { + globals.InitGlobals("test") dir := t.TempDir() s := filepath.Join(dir, "src.txt") if err := os.WriteFile(s, []byte("abc"), 0o644); err != nil { @@ -163,6 +167,7 @@ func Test_Files_Copy_And_Move(t *testing.T) { } func Test_Files_NewInputStream_NewOutputStream(t *testing.T) { + globals.InitGlobals("test") dir := t.TempDir() // InputStream error path (no file) bad := filesNewInputStream([]interface{}{newPath(filepath.Join(dir, "nope")), object.Null}) @@ -196,6 +201,7 @@ func Test_Files_NewInputStream_NewOutputStream(t *testing.T) { } func Test_Files_ReadAllBytes_WriteBytes(t *testing.T) { + globals.InitGlobals("test") dir := t.TempDir() f := filepath.Join(dir, "rw.bin") jb := object.JavaByteArrayFromGoByteArray([]byte{9, 8, 7}) @@ -203,11 +209,8 @@ func Test_Files_ReadAllBytes_WriteBytes(t *testing.T) { if _, ok := wr.(*object.Object); !ok { t.Fatalf("write should return Path") } - rd := filesReadAllBytes([]interface{}{newPath(f)}) - arr, ok := rd.([]types.JavaByte) - if !ok { - t.Fatalf("readAllBytes did not return byte[]: %T", rd) - } + rd := filesReadAllBytes([]interface{}{newPath(f)}).(*object.Object) + arr := rd.FieldTable["value"].Fvalue.([]types.JavaByte) gb := object.GoByteArrayFromJavaByteArray(arr) if len(gb) != 3 || gb[0] != 9 || gb[1] != 8 || gb[2] != 7 { t.Fatalf("bytes mismatch: %v", gb) @@ -221,6 +224,7 @@ func Test_Files_ReadAllBytes_WriteBytes(t *testing.T) { } func Test_Files_ReadString_WriteString_ReadAllLines(t *testing.T) { + globals.InitGlobals("test") dir := t.TempDir() f := filepath.Join(dir, "rw.txt") s := object.StringObjectFromGoString("line1\nline2") @@ -245,6 +249,7 @@ func Test_Files_ReadString_WriteString_ReadAllLines(t *testing.T) { } func Test_Files_IsSameFile_And_Temps(t *testing.T) { + globals.InitGlobals("test") dir := t.TempDir() f := filepath.Join(dir, "x.txt") if err := os.WriteFile(f, []byte("x"), 0o644); err != nil { @@ -273,6 +278,7 @@ func Test_Files_IsSameFile_And_Temps(t *testing.T) { } func Test_Files_Symlink_Paths(t *testing.T) { + globals.InitGlobals("test") dir := t.TempDir() tgt := filepath.Join(dir, "t.txt") if err := os.WriteFile(tgt, []byte("z"), 0o644); err != nil { diff --git a/src/gfunction/javaUtil/javaUtilArrays.go b/src/gfunction/javaUtil/javaUtilArrays.go index fb8007e5..4fb31d92 100644 --- a/src/gfunction/javaUtil/javaUtilArrays.go +++ b/src/gfunction/javaUtil/javaUtilArrays.go @@ -113,7 +113,7 @@ func Load_Util_Arrays() { // fill ghelpers.MethodSignatures["java/util/Arrays.fill([BB)V"] = ghelpers.GMeth{ParamSlots: 2, GFunction: utilArraysFill} - ghelpers.MethodSignatures["java/util/Arrays.fill([BBII)V"] = ghelpers.GMeth{ParamSlots: 4, GFunction: utilArraysFill} + ghelpers.MethodSignatures["java/util/Arrays.fill([BIIB)V"] = ghelpers.GMeth{ParamSlots: 4, GFunction: utilArraysFill} ghelpers.MethodSignatures["java/util/Arrays.fill([CC)V"] = ghelpers.GMeth{ParamSlots: 2, GFunction: utilArraysFill} ghelpers.MethodSignatures["java/util/Arrays.fill([CIIC)V"] = ghelpers.GMeth{ParamSlots: 4, GFunction: utilArraysFill} ghelpers.MethodSignatures["java/util/Arrays.fill([DD)V"] = ghelpers.GMeth{ParamSlots: 2, GFunction: utilArraysFill} diff --git a/src/gfunction/javaUtil/javaUtilArraysFill_test.go b/src/gfunction/javaUtil/javaUtilArraysFill_test.go index 588a7b3e..05ae0790 100644 --- a/src/gfunction/javaUtil/javaUtilArraysFill_test.go +++ b/src/gfunction/javaUtil/javaUtilArraysFill_test.go @@ -66,3 +66,17 @@ func TestFill_Boolean(t *testing.T) { } } } + +func TestFill_Byte(t *testing.T) { + globals.InitGlobals("test") + stringPool.PreloadArrayClassesToStringPool() + + arrObj := object.Make1DimArray(object.T_BYTE, 5) + arr := arrObj.FieldTable["value"].Fvalue.([]types.JavaByte) + + // Test fill([BIIB)V + utilArraysFill([]interface{}{arrObj, int64(1), int64(4), int64(7)}) + if arr[0] != 0 || arr[1] != 7 || arr[2] != 7 || arr[3] != 7 || arr[4] != 0 { + t.Errorf("Byte range fill failed: %v", arr) + } +} diff --git a/src/gfunction/javaUtil/javaUtilZipCrc32.go b/src/gfunction/javaUtil/javaUtilZipCrc32.go index 27ee06e6..2b3a68cc 100644 --- a/src/gfunction/javaUtil/javaUtilZipCrc32.go +++ b/src/gfunction/javaUtil/javaUtilZipCrc32.go @@ -81,6 +81,18 @@ func Load_Util_Zip_Crc32_Crc32c() { GFunction: crc32UpdateFromArray, } + ghelpers.MethodSignatures["java/util/zip/CRC32.update([B)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: crc32UpdateFromArray, + } + + ghelpers.MethodSignatures["java/util/zip/CRC32C.update([B)V"] = + ghelpers.GMeth{ + ParamSlots: 1, + GFunction: crc32UpdateFromArray, + } + ghelpers.MethodSignatures["java/util/zip/CRC32.update(I)V"] = ghelpers.GMeth{ ParamSlots: 1, @@ -149,12 +161,18 @@ func crc32Reset(params []interface{}) interface{} { // Update the current CRC32 value from an array of bytes. func crc32UpdateFromArray(params []interface{}) interface{} { + var offset, length int64 // Collect parameters. obj := params[0].(*object.Object) objBB := params[1].(*object.Object) bbWhole := objBB.FieldTable["value"].Fvalue.([]types.JavaByte) - offset := params[2].(int64) - length := params[3].(int64) + if len(params) > 2 { + offset = params[2].(int64) + length = params[3].(int64) + } else { + offset = 0 + length = int64(len(bbWhole)) + } bbSubset := bbWhole[offset : offset+length] // Get current CRC32 value.