Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/exceptions/throw.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,19 @@ func ThrowEx(which int, msg string, f *frames.Frame) bool {

var fs *list.List
glob.ThreadLock.RLock()
th, ok := glob.Threads[f.Thread].(*object.Object)
rawTh, ok := glob.Threads[f.Thread]
glob.ThreadLock.RUnlock()
if !ok {
errMsg := fmt.Sprintf("[ThrowEx] glob.Threads index not found or entry corrupted, thread index: %d, FQN: %s",
errMsg := fmt.Sprintf("[ThrowEx] glob.Threads index not found, thread index: %d, FQN: %s",
f.Thread, frames.FormatFQN(f))
MinimalAbort(excNames.InternalException, errMsg)
}
th, ok := rawTh.(*object.Object)
if !ok {
errMsg := fmt.Sprintf("[ThrowEx] glob.Threads entry corrupted (expected *object.Object, got %T), thread index: %d, FQN: %s",
rawTh, f.Thread, frames.FormatFQN(f))
MinimalAbort(excNames.InternalException, errMsg)
}
fs = th.FieldTable["framestack"].Fvalue.(*list.List)

// find out if the exception is caught and if so point to the catch code
Expand Down
5 changes: 5 additions & 0 deletions src/gfunction/gfunction.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,12 @@ func MTableLoadGFunctions(MTable *classloader.MT) {
javaMath.Load_Math_Rounding_Mode()

// java/nio/*
javaNio.Load_Nio_File_Attribute_BasicFileAttributes()
javaNio.Load_Nio_File_Attribute_FileTime()
javaNio.Load_Nio_File_Files()
javaNio.Load_Nio_File_FileVisitResult()
javaNio.Load_Nio_File_FileVisitor()
javaNio.Load_Nio_File_SimpleFileVisitor()
javaNio.Load_Nio_File_Path()
javaNio.Load_Nio_File_Paths()

Expand Down
103 changes: 103 additions & 0 deletions src/gfunction/javaNio/javaNioFileAttributeBasicFileAttributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Jacobin VM - A Java virtual machine
* Copyright (c) 2026 by the Jacobin authors. Consult jacobin.org.
* Licensed under Mozilla Public License 2.0 (MPL 2.0) All rights reserved.
*/

package javaNio

import (
"io/fs"
"jacobin/src/gfunction/ghelpers"
"jacobin/src/object"
"jacobin/src/types"
"time"
)

func Load_Nio_File_Attribute_BasicFileAttributes() {
// isRegularFile()Z
ghelpers.MethodSignatures["java/nio/file/attribute/BasicFileAttributes.isRegularFile()Z"] =
ghelpers.GMeth{ParamSlots: 1, GFunction: bfaIsRegularFile}

// isDirectory()Z
ghelpers.MethodSignatures["java/nio/file/attribute/BasicFileAttributes.isDirectory()Z"] =
ghelpers.GMeth{ParamSlots: 1, GFunction: bfaIsDirectory}

// isSymbolicLink()Z
ghelpers.MethodSignatures["java/nio/file/attribute/BasicFileAttributes.isSymbolicLink()Z"] =
ghelpers.GMeth{ParamSlots: 1, GFunction: bfaIsSymbolicLink}

// isOther()Z
ghelpers.MethodSignatures["java/nio/file/attribute/BasicFileAttributes.isOther()Z"] =
ghelpers.GMeth{ParamSlots: 1, GFunction: bfaIsOther}

// size()J
ghelpers.MethodSignatures["java/nio/file/attribute/BasicFileAttributes.size()J"] =
ghelpers.GMeth{ParamSlots: 1, GFunction: bfaSize}

// lastModifiedTime()Ljava/nio/file/attribute/FileTime;
ghelpers.MethodSignatures["java/nio/file/attribute/BasicFileAttributes.lastModifiedTime()Ljava/nio/file/attribute/FileTime;"] =
ghelpers.GMeth{ParamSlots: 1, GFunction: bfaLastModifiedTime}
}

func bfaIsRegularFile(params []interface{}) interface{} {
obj := params[0].(*object.Object)
info := obj.FieldTable["info"].Fvalue.(fs.FileInfo)
return boolToJava(info.Mode().IsRegular())
}

func bfaIsDirectory(params []interface{}) interface{} {
obj := params[0].(*object.Object)
info := obj.FieldTable["info"].Fvalue.(fs.FileInfo)
return boolToJava(info.IsDir())
}

func bfaIsSymbolicLink(params []interface{}) interface{} {
obj := params[0].(*object.Object)
info := obj.FieldTable["info"].Fvalue.(fs.FileInfo)
return boolToJava(info.Mode()&fs.ModeSymlink != 0)
}

func bfaIsOther(params []interface{}) interface{} {
obj := params[0].(*object.Object)
info := obj.FieldTable["info"].Fvalue.(fs.FileInfo)
mode := info.Mode()
return boolToJava(!mode.IsRegular() && !info.IsDir() && mode&fs.ModeSymlink == 0)
}

func bfaSize(params []interface{}) interface{} {
obj := params[0].(*object.Object)
info := obj.FieldTable["info"].Fvalue.(fs.FileInfo)
return info.Size()
}

func bfaLastModifiedTime(params []interface{}) interface{} {
obj := params[0].(*object.Object)
info := obj.FieldTable["info"].Fvalue.(fs.FileInfo)
return newFileTime(info.ModTime())
}

func newBasicFileAttributes(info fs.FileInfo) *object.Object {
className := "java/nio/file/attribute/BasicFileAttributes"
obj := object.MakeEmptyObjectWithClassName(&className)
obj.FieldTable["info"] = object.Field{Ftype: "any", Fvalue: info}
return obj
}

func newFileTime(t time.Time) *object.Object {
className := "java/nio/file/attribute/FileTime"
obj := object.MakeEmptyObjectWithClassName(&className)
obj.FieldTable["value"] = object.Field{Ftype: types.Long, Fvalue: t.UnixMilli()}
return obj
}

func Load_Nio_File_Attribute_FileTime() {
// toMillis()J
ghelpers.MethodSignatures["java/nio/file/attribute/FileTime.toMillis()J"] =
ghelpers.GMeth{ParamSlots: 1, GFunction: fileTimeToMillis}
}

func fileTimeToMillis(params []interface{}) interface{} {
obj := params[0].(*object.Object)
return obj.FieldTable["value"].Fvalue.(int64)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Jacobin VM - A Java virtual machine
* Copyright (c) 2026 by the Jacobin authors. Consult jacobin.org.
* Licensed under Mozilla Public License 2.0 (MPL 2.0) All rights reserved.
*/

package javaNio

import (
"jacobin/src/globals"
"jacobin/src/object"
"os"
"path/filepath"
"testing"
)

func Test_BasicFileAttributes(t *testing.T) {
globals.InitGlobals("test")
Load_Nio_File_Attribute_BasicFileAttributes()
Load_Nio_File_Attribute_FileTime()

dir := t.TempDir()
f := filepath.Join(dir, "test.txt")
data := []byte("hello world")
if err := os.WriteFile(f, data, 0o644); err != nil {
t.Fatalf("prep: %v", err)
}

info, err := os.Stat(f)
if err != nil {
t.Fatalf("stat: %v", err)
}

attrs := newBasicFileAttributes(info)

// Test isRegularFile
if res := bfaIsRegularFile([]interface{}{attrs}); res.(int64) != 1 {
t.Errorf("expected isRegularFile to be true")
}

// Test isDirectory
if res := bfaIsDirectory([]interface{}{attrs}); res.(int64) != 0 {
t.Errorf("expected isDirectory to be false")
}

// Test size
if res := bfaSize([]interface{}{attrs}); res.(int64) != int64(len(data)) {
t.Errorf("expected size %d, got %d", len(data), res.(int64))
}

// Test lastModifiedTime
ft := bfaLastModifiedTime([]interface{}{attrs}).(*object.Object)
milli := fileTimeToMillis([]interface{}{ft})
if milli.(int64) != info.ModTime().UnixMilli() {
t.Errorf("expected lastModifiedTime %d, got %d", info.ModTime().UnixMilli(), milli.(int64))
}

// Test directory
dirInfo, _ := os.Stat(dir)
dirAttrs := newBasicFileAttributes(dirInfo)
if res := bfaIsDirectory([]interface{}{dirAttrs}); res.(int64) != 1 {
t.Errorf("expected isDirectory to be true for directory")
}
if res := bfaIsRegularFile([]interface{}{dirAttrs}); res.(int64) != 0 {
t.Errorf("expected isRegularFile to be false for directory")
}
}
94 changes: 94 additions & 0 deletions src/gfunction/javaNio/javaNioFileFileVisitResult.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Jacobin VM - A Java virtual machine
* Copyright (c) 2026 by the Jacobin authors. Consult jacobin.org.
* Licensed under Mozilla Public License 2.0 (MPL 2.0) All rights reserved.
*/

package javaNio

import (
"jacobin/src/excNames"
"jacobin/src/gfunction/ghelpers"
"jacobin/src/object"
"jacobin/src/statics"
"jacobin/src/types"
"sync"
)

func Load_Nio_File_FileVisitResult() {
ghelpers.MethodSignatures["java/nio/file/FileVisitResult.<clinit>()V"] =
ghelpers.GMeth{
ParamSlots: 0,
GFunction: fvResultClinit,
}

ghelpers.MethodSignatures["java/nio/file/FileVisitResult.valueOf(Ljava/lang/String;)Ljava/nio/file/FileVisitResult;"] =
ghelpers.GMeth{
ParamSlots: 1,
GFunction: fvResultValueOfString,
}

ghelpers.MethodSignatures["java/nio/file/FileVisitResult.values()[Ljava/nio/file/FileVisitResult;"] =
ghelpers.GMeth{
ParamSlots: 0,
GFunction: fvResultValues,
}
}

var fvResultMutex = sync.Mutex{}
var fvResultOnceInitialized bool = false
var fvResultClassName = "java/nio/file/FileVisitResult"
var fvResultNames = []string{"CONTINUE", "TERMINATE", "SKIP_SUBTREE", "SKIP_SIBLINGS"}
var fvResultInstances []*object.Object

func ensureFvResultInited() {
fvResultMutex.Lock()
defer fvResultMutex.Unlock()
if fvResultOnceInitialized {
return
}
fvResultInstances = make([]*object.Object, len(fvResultNames))
for i, nm := range fvResultNames {
obj := object.MakeEmptyObjectWithClassName(&fvResultClassName)
obj.FieldTable["name"] = object.Field{Ftype: types.StringClassRef, Fvalue: object.StringObjectFromGoString(nm)}
obj.FieldTable["ordinal"] = object.Field{Ftype: types.Int, Fvalue: int64(i)}
fvResultInstances[i] = obj
_ = statics.AddStatic(fvResultClassName+"."+nm, statics.Static{Type: "Ljava/nio/file/FileVisitResult;", Value: obj})
}
fvResultOnceInitialized = true
}

func fvResultClinit([]interface{}) interface{} {
ensureFvResultInited()
return nil
}

func fvResultValueOfString(params []interface{}) interface{} {
ensureFvResultInited()
if len(params) < 1 {
return ghelpers.GetGErrBlk(excNames.IllegalArgumentException, "FileVisitResult.valueOf(String): missing argument")
}
strObj, ok := params[0].(*object.Object)
if !ok || object.IsNull(strObj) {
return ghelpers.GetGErrBlk(excNames.NullPointerException, "FileVisitResult.valueOf(String): name is null")
}
if !object.IsStringObject(strObj) {
return ghelpers.GetGErrBlk(excNames.IllegalArgumentException, "FileVisitResult.valueOf(String): argument is not a String")
}
name := object.GoStringFromStringObject(strObj)
for i, nm := range fvResultNames {
if nm == name {
return fvResultInstances[i]
}
}
return ghelpers.GetGErrBlk(excNames.IllegalArgumentException, "FileVisitResult.valueOf(String): no enum constant "+name)
}

func fvResultValues(params []interface{}) interface{} {
ensureFvResultInited()
arr := object.Make1DimRefArray("Ljava/nio/file/FileVisitResult;", int64(len(fvResultInstances)))
slot := arr.FieldTable["value"].Fvalue.([]*object.Object)
copy(slot, fvResultInstances)
arr.FieldTable["value"] = object.Field{Ftype: types.RefArray + "Ljava/nio/file/FileVisitResult;", Fvalue: slot}
return arr
}
85 changes: 85 additions & 0 deletions src/gfunction/javaNio/javaNioFileFileVisitResult_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Jacobin VM - A Java virtual machine
* Copyright (c) 2026 by the Jacobin authors. Consult jacobin.org.
* Licensed under Mozilla Public License 2.0 (MPL 2.0) All rights reserved.
*/

package javaNio

import (
"jacobin/src/excNames"
"jacobin/src/gfunction/ghelpers"
"jacobin/src/globals"
"jacobin/src/object"
"testing"
)

func Test_FileVisitResult_Enum(t *testing.T) {
globals.InitGlobals("test")
Load_Nio_File_FileVisitResult()

// Test clinit
fvResultClinit(nil)

// Test values()
res := fvResultValues(nil)
arr, ok := res.(*object.Object)
if !ok || arr == nil {
t.Fatalf("values() should return array object")
}
vals := arr.FieldTable["value"].Fvalue.([]*object.Object)
if len(vals) != 4 {
t.Fatalf("expected 4 FileVisitResult values, got %d", len(vals))
}

expectedNames := []string{"CONTINUE", "TERMINATE", "SKIP_SUBTREE", "SKIP_SIBLINGS"}
for i, name := range expectedNames {
nmObj := vals[i].FieldTable["name"].Fvalue.(*object.Object)
if object.GoStringFromStringObject(nmObj) != name {
t.Errorf("expected %s at index %d, got %s", name, i, object.GoStringFromStringObject(nmObj))
}
ord := vals[i].FieldTable["ordinal"].Fvalue.(int64)
if ord != int64(i) {
t.Errorf("expected ordinal %d for %s, got %d", i, name, ord)
}
}

// Test valueOf(String) success
for _, name := range expectedNames {
sObj := object.StringObjectFromGoString(name)
v := fvResultValueOfString([]interface{}{sObj})
if v == nil || object.IsNull(v) {
t.Fatalf("valueOf(%s) returned null", name)
}
resObj := v.(*object.Object)
nmObj := resObj.FieldTable["name"].Fvalue.(*object.Object)
if object.GoStringFromStringObject(nmObj) != name {
t.Errorf("valueOf(%s) returned wrong constant: %s", name, object.GoStringFromStringObject(nmObj))
}
}

// Test valueOf(String) error cases
// 1. Missing argument
err1 := fvResultValueOfString([]interface{}{})
if geb, ok := err1.(*ghelpers.GErrBlk); !ok || geb.ExceptionType != excNames.IllegalArgumentException {
t.Errorf("expected IllegalArgumentException for missing arg, got %T", err1)
}

// 2. Null argument
err2 := fvResultValueOfString([]interface{}{object.Null})
if geb, ok := err2.(*ghelpers.GErrBlk); !ok || geb.ExceptionType != excNames.NullPointerException {
t.Errorf("expected NullPointerException for null arg, got %T", err2)
}

// 3. Not a String
err3 := fvResultValueOfString([]interface{}{object.MakeEmptyObjectWithClassName(new(string))})
if geb, ok := err3.(*ghelpers.GErrBlk); !ok || geb.ExceptionType != excNames.IllegalArgumentException {
t.Errorf("expected IllegalArgumentException for non-string arg, got %T", err3)
}

// 4. Invalid name
err4 := fvResultValueOfString([]interface{}{object.StringObjectFromGoString("INVALID")})
if geb, ok := err4.(*ghelpers.GErrBlk); !ok || geb.ExceptionType != excNames.IllegalArgumentException {
t.Errorf("expected IllegalArgumentException for invalid name, got %T", err4)
}
}
Loading
Loading