From bf31dd1a5eff23ffab5d02f206c5112720d7d11f Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Wed, 7 Sep 2016 18:27:36 +0200 Subject: [PATCH] bind,cmd/gomobile: add a new generator for Java API wrappers Using the new Java class analyzer API, scan the bound packages for references to Java classes and interfaces and generate Go wrappers for them. This is the second part of the implementation of proposal golang/go#16876. For golang/go#16876 Change-Id: I59ec0ebdae0081a615dc34d450f344c20c03f871 Reviewed-on: https://go-review.googlesource.com/28596 Reviewed-by: David Crawshaw --- bind/bind_test.go | 141 +++++- bind/gen.go | 7 + bind/genclasses.go | 727 +++++++++++++++++++++++++++++++ bind/java/ClassesTest.java | 68 +++ bind/java/seq.h | 5 + bind/java/seq_android.c.support | 33 ++ bind/java/seq_test.go | 11 + bind/testdata/java.go | 17 + bind/testdata/java.go.golden | 179 ++++++++ bind/testdata/java.java.c.golden | 89 ++++ bind/testdata/java.java.golden | 96 ++++ bind/testdata/java.java.h.golden | 75 ++++ bind/testpkg/javapkg/java.go | 57 +++ cmd/gomobile/bind.go | 95 +++- cmd/gomobile/bind_androidapp.go | 26 +- cmd/gomobile/bind_test.go | 9 +- 16 files changed, 1617 insertions(+), 18 deletions(-) create mode 100644 bind/genclasses.go create mode 100644 bind/java/ClassesTest.java create mode 100644 bind/testdata/java.go create mode 100644 bind/testdata/java.go.golden create mode 100644 bind/testdata/java.java.c.golden create mode 100644 bind/testdata/java.java.golden create mode 100644 bind/testdata/java.java.h.golden create mode 100644 bind/testpkg/javapkg/java.go diff --git a/bind/bind_test.go b/bind/bind_test.go index 4f8a825..d5fd0c5 100644 --- a/bind/bind_test.go +++ b/bind/bind_test.go @@ -4,6 +4,8 @@ import ( "bytes" "flag" "go/ast" + "go/build" + "go/importer" "go/parser" "go/token" "go/types" @@ -16,6 +18,9 @@ import ( "runtime" "strings" "testing" + + "golang.org/x/mobile/internal/importers" + "golang.org/x/mobile/internal/importers/java" ) func init() { @@ -37,9 +42,25 @@ var tests = []string{ "testdata/ignore.go", } +var javaTests = []string{ + "testdata/java.go", +} + var fset = token.NewFileSet() -func typeCheck(t *testing.T, filename string) *types.Package { +func fileRefs(t *testing.T, filename string) *importers.References { + f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) + if err != nil { + t.Fatalf("%s: %v", filename, err) + } + refs, err := importers.AnalyzeFile(f, "Java/") + if err != nil { + t.Fatalf("%s: %v", filename, err) + } + return refs +} + +func typeCheck(t *testing.T, filename string, gopath string) *types.Package { f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) if err != nil { t.Fatalf("%s: %v", filename, err) @@ -53,6 +74,12 @@ func typeCheck(t *testing.T, filename string) *types.Package { conf.Error = func(err error) { t.Error(err) } + if gopath != "" { + conf.Importer = importer.Default() + oldDefault := build.Default + defer func() { build.Default = oldDefault }() + build.Default.GOPATH = gopath + } pkg, err := conf.Check(pkgName, fset, []*ast.File{f}, nil) if err != nil { t.Fatal(err) @@ -98,7 +125,7 @@ func TestGenObjc(t *testing.T) { } for _, filename := range tests { - pkg := typeCheck(t, filename) + pkg := typeCheck(t, filename, "") for typ, suffix := range suffixes { var buf bytes.Buffer @@ -129,10 +156,74 @@ func TestGenObjc(t *testing.T) { } } +func genJavaPackages(t *testing.T, dir string, classes []*java.Class, buf *bytes.Buffer) *ClassGen { + cg := &ClassGen{ + Printer: &Printer{ + IndentEach: []byte("\t"), + Buf: buf, + }, + } + cg.Init(classes) + pkgBase := filepath.Join(dir, "src", "Java") + if err := os.MkdirAll(pkgBase, 0700); err != nil { + t.Fatal(err) + } + for i, jpkg := range cg.Packages() { + pkgDir := filepath.Join(pkgBase, jpkg) + if err := os.MkdirAll(pkgDir, 0700); err != nil { + t.Fatal(err) + } + pkgFile := filepath.Join(pkgDir, "package.go") + buf.Reset() + cg.GenPackage(i) + if err := ioutil.WriteFile(pkgFile, buf.Bytes(), 0600); err != nil { + t.Fatal(err) + } + } + buf.Reset() + cg.GenInterfaces() + clsFile := filepath.Join(pkgBase, "interfaces.go") + if err := ioutil.WriteFile(clsFile, buf.Bytes(), 0600); err != nil { + t.Fatal(err) + } + + cmd := exec.Command( + "go", + "install", + "-pkgdir="+filepath.Join(dir, "pkg", build.Default.GOOS+"_"+build.Default.GOARCH), + "Java/...", + ) + cmd.Env = append(cmd.Env, "GOPATH="+dir) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to go install the generated Java wrappers: %v: %s", err, string(out)) + } + return cg +} + func TestGenJava(t *testing.T) { - for _, filename := range tests { - pkg := typeCheck(t, filename) + allTests := tests + if java.IsAvailable() { + allTests = append(append([]string{}, allTests...), javaTests...) + } + for _, filename := range allTests { + refs := fileRefs(t, filename) + classes, err := java.Import("", refs) + if err != nil { + t.Fatal(err) + } + var cg *ClassGen + tmpGopath := "" var buf bytes.Buffer + if len(classes) > 0 { + tmpGopath, err = ioutil.TempDir(os.TempDir(), "gomobile-bind-test-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpGopath) + cg = genJavaPackages(t, tmpGopath, classes, new(bytes.Buffer)) + cg.Buf = &buf + } + pkg := typeCheck(t, filename, tmpGopath) g := &JavaGen{ Generator: &Generator{ Printer: &Printer{Buf: &buf, IndentEach: []byte(" ")}, @@ -159,11 +250,21 @@ func TestGenJava(t *testing.T) { }, { ".java.c.golden", - func() error { return g.GenC() }, + func() error { + if cg != nil { + cg.GenC() + } + return g.GenC() + }, }, { ".java.h.golden", - func() error { return g.GenH() }, + func() error { + if cg != nil { + cg.GenH() + } + return g.GenH() + }, }, } @@ -192,15 +293,37 @@ func TestGenJava(t *testing.T) { } func TestGenGo(t *testing.T) { - for _, filename := range tests { + allTests := tests + if java.IsAvailable() { + allTests = append(append([]string{}, allTests...), javaTests...) + } + for _, filename := range allTests { var buf bytes.Buffer - pkg := typeCheck(t, filename) + refs := fileRefs(t, filename) + classes, err := java.Import("", refs) + if err != nil { + t.Fatal(err) + } + tmpGopath := "" + var cg *ClassGen + if len(classes) > 0 { + tmpGopath, err = ioutil.TempDir(os.TempDir(), "gomobile-bind-test-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpGopath) + cg = genJavaPackages(t, tmpGopath, classes, &buf) + } + pkg := typeCheck(t, filename, tmpGopath) conf := &GeneratorConfig{ Writer: &buf, Fset: fset, Pkg: pkg, AllPkg: []*types.Package{pkg}, } + if cg != nil { + cg.GenGo() + } if err := GenGo(conf); err != nil { t.Errorf("%s: %v", filename, err) continue @@ -224,7 +347,7 @@ func TestGenGo(t *testing.T) { func TestCustomPrefix(t *testing.T) { const datafile = "testdata/customprefix.go" const isHeader = true - pkg := typeCheck(t, datafile) + pkg := typeCheck(t, datafile, "") conf := &GeneratorConfig{ Fset: fset, diff --git a/bind/gen.go b/bind/gen.go index 8d482d7..64a8b3c 100644 --- a/bind/gen.go +++ b/bind/gen.go @@ -165,6 +165,13 @@ func (_ *Generator) toCFlag(v bool) int { return 0 } +func toCFlag(v bool) int { + if v { + return 1 + } + return 0 +} + func (g *Generator) errorf(format string, args ...interface{}) { g.err = append(g.err, fmt.Errorf(format, args...)) } diff --git a/bind/genclasses.go b/bind/genclasses.go new file mode 100644 index 0000000..1b797c3 --- /dev/null +++ b/bind/genclasses.go @@ -0,0 +1,727 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bind + +import ( + "fmt" + "path/filepath" + "strings" + "unicode" + "unicode/utf8" + + "golang.org/x/mobile/internal/importers/java" +) + +type ( + // ClassGen generates Go and C stubs for Java classes so import statements + // on the form + // + // + // import "Java/classpath/to/Class" + // + // will work. + ClassGen struct { + *Printer + imported map[string]struct{} + // The list of imported Java classes + classes []*java.Class + // The list of Go package paths with Java interfaces inside + jpkgs []string + // For each Go package path, the list of Java classes. + typePkgs map[string][]*java.Class + // For each Go package path, the Java class with static functions + // or constants. + clsPkgs map[string]*java.Class + } +) + +func (g *ClassGen) isSupported(t *java.Type) bool { + switch t.Kind { + case java.Array: + // TODO: Support all array types + return t.Elem.Kind == java.Byte + default: + return true + } +} + +func (g *ClassGen) isFuncSupported(f *java.Func) bool { + for _, a := range f.Params { + if !g.isSupported(a) { + return false + } + } + if f.Ret != nil { + return g.isSupported(f.Ret) + } + return true +} + +func (g *ClassGen) goType(t *java.Type, local bool) string { + switch t.Kind { + case java.Int: + return "int32" + case java.Boolean: + return "bool" + case java.Short: + return "int16" + case java.Char: + return "uint16" + case java.Byte: + return "byte" + case java.Long: + return "int64" + case java.Float: + return "float32" + case java.Double: + return "float64" + case java.String: + return "string" + case java.Array: + return "[]" + g.goType(t.Elem, local) + case java.Object: + if _, exists := g.imported[t.Class]; !exists { + return "interface{}" + } + name := goClsName(t.Class) + if !local { + name = "Java." + name + } + return name + default: + panic("invalid kind") + } +} + +func (g *ClassGen) Init(classes []*java.Class) { + g.classes = classes + g.imported = make(map[string]struct{}) + g.typePkgs = make(map[string][]*java.Class) + g.clsPkgs = make(map[string]*java.Class) + pkgSet := make(map[string]struct{}) + for _, cls := range classes { + g.imported[cls.Name] = struct{}{} + clsPkg := strings.Replace(cls.Name, ".", "/", -1) + g.clsPkgs[clsPkg] = cls + typePkg := filepath.Dir(clsPkg) + g.typePkgs[typePkg] = append(g.typePkgs[typePkg], cls) + if _, exists := pkgSet[clsPkg]; !exists { + pkgSet[clsPkg] = struct{}{} + g.jpkgs = append(g.jpkgs, clsPkg) + } + if _, exists := pkgSet[typePkg]; !exists { + pkgSet[typePkg] = struct{}{} + g.jpkgs = append(g.jpkgs, typePkg) + } + } +} + +// Packages return the list of Go packages to be generated. +func (g *ClassGen) Packages() []string { + return g.jpkgs +} + +func (g *ClassGen) GenPackage(idx int) { + jpkg := g.jpkgs[idx] + g.Printf("// File is generated by gobind. Do not edit.\n\n") + g.Printf("package %s\n\n", filepath.Base(jpkg)) + g.Printf("import \"Java\"\n\n") + g.Printf("const _ = Java.Dummy\n\n") + for _, cls := range g.typePkgs[jpkg] { + g.Printf("type %s Java.%s\n", cls.PkgName, goClsName(cls.Name)) + } + if cls, ok := g.clsPkgs[jpkg]; ok { + g.Printf("const (\n") + g.Indent() + // Constants + for _, v := range cls.Vars { + if g.isSupported(v.Type) && v.Constant() { + g.Printf("%s = %s\n", initialUpper(v.Name), v.Val) + } + } + g.Outdent() + g.Printf(")\n\n") + + g.Printf("var (\n") + g.Indent() + // Functions + for _, f := range cls.Funcs { + if !f.Public || !g.isFuncSupported(f) { + continue + } + g.Printf("%s func", f.GoName) + g.genFuncDecl(false, f) + g.Printf("\n") + } + g.Outdent() + g.Printf(")\n\n") + } +} + +func (g *ClassGen) GenGo() { + g.Printf(classesGoHeader) + impUnsafe := false + for _, cls := range g.classes { + for _, f := range cls.Funcs { + if f.Public && g.isFuncSupported(f) { + impUnsafe = true + pkgName := strings.Replace(cls.Name, ".", "/", -1) + g.Printf("import %q\n", "Java/"+pkgName) + break + } + } + } + if impUnsafe { + g.Printf("import \"unsafe\"\n\n") + } + g.Printf("type proxy interface { Bind_proxy_refnum__() int32 }\n\n") + g.Printf("// Suppress unused package error\n\n") + g.Printf("var _ = _seq.FromRefNum\n") + g.Printf("const _ = Java.Dummy\n\n") + g.Printf("//export initClasses\n") + g.Printf("func initClasses() {\n") + g.Indent() + g.Printf("C.init_proxies()\n") + for _, cls := range g.classes { + g.Printf("init_%s()\n", cls.JNIName) + } + g.Outdent() + g.Printf("}\n\n") + for _, cls := range g.classes { + g.genGo(cls) + } +} + +func (g *ClassGen) GenH() { + g.Printf(classesHHeader) + for _, tn := range []string{"jint", "jboolean", "jshort", "jchar", "jbyte", "jlong", "jfloat", "jdouble", "nstring", "nbyteslice"} { + g.Printf("typedef struct ret_%s {\n", tn) + g.Printf(" %s res;\n", tn) + g.Printf(" jint exc;\n") + g.Printf("} ret_%s;\n", tn) + } + g.Printf("\n") + for _, cls := range g.classes { + for _, f := range cls.AllMethods { + if !g.isFuncSupported(f) { + continue + } + g.Printf("extern ") + g.genCMethodDecl("cproxy", cls.JNIName, f) + g.Printf(";\n") + if cls.HasSuper() { + g.Printf("extern ") + g.genCMethodDecl("csuper", cls.JNIName, f) + g.Printf(";\n") + } + } + } + for _, cls := range g.classes { + g.genH(cls) + } +} + +func (g *ClassGen) GenC() { + g.Printf(classesCHeader) + for _, cls := range g.classes { + g.genC(cls) + g.Printf("static jclass class_%s;\n", cls.JNIName) + for _, f := range cls.AllMethods { + if g.isFuncSupported(f) { + g.Printf("static jmethodID m_%s_%s;\n", cls.JNIName, f.JNIName) + } + } + } + g.Printf("\n") + g.Printf("void init_proxies() {\n") + g.Indent() + g.Printf("JNIEnv *env = go_seq_push_local_frame(%d);\n", len(g.classes)) + g.Printf("jclass clazz;\n") + for _, cls := range g.classes { + g.Printf("clazz = (*env)->FindClass(env, %q);\n", strings.Replace(cls.FindName, ".", "/", -1)) + g.Printf("class_%s = (*env)->NewGlobalRef(env, clazz);\n", cls.JNIName) + for _, f := range cls.AllMethods { + if g.isFuncSupported(f) { + g.Printf("m_%s_%s = go_seq_get_method_id(clazz, %q, %q);\n", cls.JNIName, f.JNIName, f.Name, f.Desc) + } + } + } + g.Printf("go_seq_pop_local_frame(env);\n") + g.Outdent() + g.Printf("}\n\n") + for _, cls := range g.classes { + for _, f := range cls.AllMethods { + if !g.isFuncSupported(f) { + continue + } + g.genCMethodDecl("cproxy", cls.JNIName, f) + g.genCMethodBody(cls, f, false) + if cls.HasSuper() { + g.genCMethodDecl("csuper", cls.JNIName, f) + g.genCMethodBody(cls, f, true) + } + } + } +} + +func (g *ClassGen) GenInterfaces() { + g.Printf(classesPkgHeader) + for _, cls := range g.classes { + g.genInterface(cls) + } +} + +func (g *ClassGen) genCMethodBody(cls *java.Class, f *java.Func, virtual bool) { + g.Printf(" {\n") + g.Indent() + // Add 1 for the 'this' argument + g.Printf("JNIEnv *env = go_seq_push_local_frame(%d);\n", len(f.Params)+1) + g.Printf("// Must be a Java object\n") + g.Printf("jobject _this = go_seq_from_refnum(env, this, NULL, NULL);\n") + for i, a := range f.Params { + g.genCToJava(fmt.Sprintf("a%d", i), a) + } + if f.Ret != nil { + g.Printf("%s res = ", f.Ret.JNIType()) + } + g.Printf("(*env)->Call") + if virtual { + g.Printf("Nonvirtual") + } + if f.Ret != nil { + g.Printf(f.Ret.JNICallType()) + } else { + g.Printf("Void") + } + g.Printf("Method(env, _this, ") + if virtual { + g.Printf("class_%s, ", cls.JNIName) + } + g.Printf("m_%s_%s", cls.JNIName, f.JNIName) + for i := range f.Params { + g.Printf(", _a%d", i) + } + g.Printf(");\n") + g.Printf("jobject _exc = go_seq_wrap_exception(env);\n") + g.Printf("int32_t _exc_ref = go_seq_to_refnum(env, _exc);\n") + if f.Ret != nil { + g.genJavaToC("res", f.Ret) + } + g.Printf("go_seq_pop_local_frame(env);\n") + if f.Ret != nil { + g.Printf("ret_%s __res = {_res, _exc_ref};\n", f.Ret.CType()) + g.Printf("return __res;\n") + } else { + g.Printf("return _exc_ref;\n") + } + g.Outdent() + g.Printf("}\n\n") +} + +func initialUpper(s string) string { + if s == "" { + return "" + } + r, n := utf8.DecodeRuneInString(s) + return string(unicode.ToUpper(r)) + s[n:] +} + +func (g *ClassGen) genFuncDecl(local bool, f *java.Func) { + g.Printf("(") + for i, a := range f.Params { + if i > 0 { + g.Printf(", ") + } + g.Printf("a%d %s", i, g.goType(a, local)) + } + g.Printf(")") + if f.Throws != "" { + if f.Ret != nil { + g.Printf(" (%s, error)", g.goType(f.Ret, local)) + } else { + g.Printf(" error") + } + } else if f.Ret != nil { + g.Printf(" %s", g.goType(f.Ret, local)) + } +} + +func (g *ClassGen) genC(cls *java.Class) { + for _, f := range cls.Funcs { + if !f.Public || !g.isFuncSupported(f) { + continue + } + g.genCFuncDecl(cls.JNIName, f) + g.Printf(" {\n") + g.Indent() + g.Printf("JNIEnv *env = go_seq_push_local_frame(%d);\n", len(f.Params)) + for i, a := range f.Params { + g.genCToJava(fmt.Sprintf("a%d", i), a) + } + if f.Constructor { + g.Printf("jobject res = (*env)->NewObject(env") + } else if f.Ret != nil { + g.Printf("%s res = (*env)->CallStatic%sMethod(env", f.Ret.JNIType(), f.Ret.JNICallType()) + } else { + g.Printf("(*env)->CallStaticVoidMethod(env") + } + g.Printf(", clazz, m") + for i := range f.Params { + g.Printf(", _a%d", i) + } + g.Printf(");\n") + g.Printf("jobject _exc = go_seq_wrap_exception(env);\n") + g.Printf("int32_t _exc_ref = go_seq_to_refnum(env, _exc);\n") + if f.Ret != nil { + g.genJavaToC("res", f.Ret) + } + g.Printf("go_seq_pop_local_frame(env);\n") + if f.Ret != nil { + g.Printf("ret_%s __res = {_res, _exc_ref};\n", f.Ret.CType()) + g.Printf("return __res;\n") + } else { + g.Printf("return _exc_ref;\n") + } + g.Outdent() + g.Printf("}\n\n") + } +} + +func (g *ClassGen) genH(cls *java.Class) { + for _, f := range cls.Funcs { + if !f.Public || !g.isFuncSupported(f) { + continue + } + g.Printf("extern ") + g.genCFuncDecl(cls.JNIName, f) + g.Printf(";\n") + } +} + +func (g *ClassGen) genCMethodDecl(prefix, jniName string, f *java.Func) { + if f.Ret != nil { + g.Printf("ret_%s", f.Ret.CType()) + } else { + // Return only the exception, if any + g.Printf("jint") + } + g.Printf(" %s_%s_%s(jint this", prefix, jniName, f.JNIName) + for i, a := range f.Params { + g.Printf(", %s a%d", a.CType(), i) + } + g.Printf(")") +} + +func (g *ClassGen) genCFuncDecl(jniName string, f *java.Func) { + if f.Ret != nil { + g.Printf("ret_%s", f.Ret.CType()) + } else { + // Return only the exception, if any + g.Printf("jint") + } + g.Printf(" cproxy_s_%s_%s(jclass clazz, jmethodID m", jniName, f.JNIName) + for i, a := range f.Params { + g.Printf(", %s a%d", a.CType(), i) + } + g.Printf(")") +} + +func (g *ClassGen) genGo(cls *java.Class) { + g.Printf("func init_%s() {\n", cls.JNIName) + g.Indent() + g.Printf("cls := C.CString(%q)\n", strings.Replace(cls.FindName, ".", "/", -1)) + g.Printf("clazz := C.go_seq_find_class(cls)\n") + g.Printf("C.free(unsafe.Pointer(cls))\n") + g.Printf("if clazz == nil {\n") + g.Printf(" return\n") + g.Printf("}\n") + for _, f := range cls.Funcs { + if !f.Public || !g.isFuncSupported(f) { + continue + } + g.Printf("{\n") + g.Indent() + name := f.Name + if f.Constructor { + name = "" + } + g.Printf("fn := C.CString(%q)\n", name) + g.Printf("fd := C.CString(%q)\n", f.Desc) + if f.Constructor { + g.Printf("m := C.go_seq_get_method_id(clazz, fn, fd)\n") + } else { + g.Printf("m := C.go_seq_get_static_method_id(clazz, fn, fd)\n") + } + g.Printf("C.free(unsafe.Pointer(fn))\n") + g.Printf("C.free(unsafe.Pointer(fd))\n") + g.Printf("if m != nil {\n") + g.Indent() + g.Printf("%s.%s = func", cls.PkgName, f.GoName) + g.genFuncDecl(false, f) + g.Printf(" {\n") + g.Indent() + for i, a := range f.Params { + g.genWrite(fmt.Sprintf("a%d", i), a, modeTransient) + } + g.Printf("res := C.cproxy_s_%s_%s(clazz, m", cls.JNIName, f.JNIName) + for i := range f.Params { + g.Printf(", _a%d", i) + } + g.Printf(")\n") + g.genFuncRet(f) + g.Outdent() + g.Printf("}\n") + g.Outdent() + g.Printf("}\n") + g.Outdent() + g.Printf("}\n") + } + g.Outdent() + g.Printf("}\n\n") + g.Printf("type proxy_class_%s _seq.Ref\n\n", cls.JNIName) + g.Printf("func (p *proxy_class_%s) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() }\n\n", cls.JNIName) + for _, f := range cls.AllMethods { + if !g.isFuncSupported(f) { + continue + } + g.Printf("func (p *proxy_class_%s) %s", cls.JNIName, f.GoName) + g.genFuncDecl(false, f) + g.genFuncBody(cls, f, "cproxy") + } + if cls.Throwable { + g.Printf("func (p *proxy_class_%s) Error() string {\n", cls.JNIName) + g.Printf(" return p.ToString()\n") + g.Printf("}\n") + } + if cls.HasSuper() { + g.Printf("func (p *proxy_class_%s) Super() Java.%s {\n", cls.JNIName, goClsName(cls.Name)) + g.Printf(" return &super_%s{p}\n", cls.JNIName) + g.Printf("}\n\n") + g.Printf("type super_%s struct {*proxy_class_%[1]s}\n\n", cls.JNIName) + for _, f := range cls.AllMethods { + if !g.isFuncSupported(f) { + continue + } + g.Printf("func (p *super_%s) %s", cls.JNIName, f.GoName) + g.genFuncDecl(false, f) + g.genFuncBody(cls, f, "csuper") + } + } +} + +func (g *ClassGen) genFuncBody(cls *java.Class, f *java.Func, prefix string) { + g.Printf(" {\n") + g.Indent() + for i, a := range f.Params { + g.genWrite(fmt.Sprintf("a%d", i), a, modeTransient) + } + g.Printf("res := C.%s_%s_%s(C.jint(p.Bind_proxy_refnum__())", prefix, cls.JNIName, f.JNIName) + for i := range f.Params { + g.Printf(", _a%d", i) + } + g.Printf(")\n") + g.genFuncRet(f) + g.Outdent() + g.Printf("}\n\n") +} + +func (g *ClassGen) genFuncRet(f *java.Func) { + if f.Ret != nil { + g.genRead("_res", "res.res", f.Ret, modeRetained) + g.genRefRead("_exc", "res.exc", "error", "proxy_error", true) + } else { + g.genRefRead("_exc", "res", "error", "proxy_error", true) + } + if f.Throws == "" { + g.Printf("if (_exc != nil) { panic(_exc) }\n") + if f.Ret != nil { + g.Printf("return _res\n") + } + } else { + if f.Ret != nil { + g.Printf("return _res, _exc\n") + } else { + g.Printf("return _exc\n") + } + } +} + +func (g *ClassGen) genRead(to, from string, t *java.Type, mode varMode) { + switch t.Kind { + case java.Int, java.Short, java.Char, java.Byte, java.Long, java.Float, java.Double: + g.Printf("%s := %s(%s)\n", to, g.goType(t, false), from) + case java.Boolean: + g.Printf("%s := %s != C.JNI_FALSE\n", to, from) + case java.String: + g.Printf("%s := decodeString(%s)\n", to, from) + case java.Array: + if t.Elem.Kind != java.Byte { + panic("unsupported array type") + } + g.Printf("%s := toSlice(%s, %v)\n", to, from, mode == modeRetained) + case java.Object: + _, hasProxy := g.imported[t.Class] + g.genRefRead(to, from, g.goType(t, false), "proxy_class_"+flattenName(t.Class), hasProxy) + default: + panic("invalid kind") + } +} + +func (g *ClassGen) genRefRead(to, from string, intfName, proxyName string, hasProxy bool) { + g.Printf("var %s %s\n", to, intfName) + g.Printf("%s_ref := _seq.FromRefNum(int32(%s))\n", to, from) + g.Printf("if %s_ref != nil {\n", to) + g.Printf(" if %s < 0 { // go object\n", from) + g.Printf(" %s = %s_ref.Get().(%s)\n", to, to, intfName) + if hasProxy { + g.Printf(" } else { // foreign object\n") + g.Printf(" %s = (*%s)(%s_ref)\n", to, proxyName, to) + } + g.Printf(" }\n") + g.Printf("}\n") +} + +func (g *ClassGen) genWrite(v string, t *java.Type, mode varMode) { + switch t.Kind { + case java.Int, java.Short, java.Char, java.Byte, java.Long, java.Float, java.Double: + g.Printf("_%s := C.%s(%s)\n", v, t.CType(), v) + case java.Boolean: + g.Printf("_%s := C.jboolean(C.JNI_FALSE)\n", v) + g.Printf("if %s {\n", v) + g.Printf(" _%s = C.jboolean(C.JNI_TRUE)\n", v) + g.Printf("}\n") + case java.String: + g.Printf("_%s := encodeString(%s)\n", v, v) + case java.Array: + if t.Elem.Kind != java.Byte { + panic("unsupported array type") + } + g.Printf("_%s := fromSlice(%s, %v)\n", v, v, mode == modeRetained) + case java.Object: + g.Printf("var _%s C.jint = _seq.NullRefNum\n", v) + g.Printf("if %s != nil {\n", v) + g.Printf(" _%s = C.jint(_seq.ToRefNum(%s))\n", v, v) + g.Printf("}\n") + default: + panic("invalid kind") + } +} + +func (g *ClassGen) genJavaToC(v string, t *java.Type) { + switch t.Kind { + case java.Int, java.Short, java.Char, java.Byte, java.Long, java.Float, java.Double, java.Boolean: + g.Printf("%s _%s = %s;\n", t.JNIType(), v, v) + case java.String: + g.Printf("nstring _%s = go_seq_from_java_string(env, %s);\n", v, v) + case java.Array: + if t.Elem.Kind != java.Byte { + panic("unsupported array type") + } + g.Printf("nbyteslice _%s = go_seq_from_java_bytearray(env, %s, 1);\n", v, v) + case java.Object: + g.Printf("jint _%s = go_seq_to_refnum(env, %s);\n", v, v) + default: + panic("invalid kind") + } +} + +func (g *ClassGen) genCToJava(v string, t *java.Type) { + switch t.Kind { + case java.Int, java.Short, java.Char, java.Byte, java.Long, java.Float, java.Double, java.Boolean: + g.Printf("%s _%s = %s;\n", t.JNIType(), v, v) + case java.String: + g.Printf("jstring _%s = go_seq_to_java_string(env, %s);\n", v, v) + case java.Array: + if t.Elem.Kind != java.Byte { + panic("unsupported array type") + } + g.Printf("jbyteArray _%s = go_seq_to_java_bytearray(env, %s, 0);\n", v, v) + case java.Object: + g.Printf("jobject _%s = go_seq_from_refnum(env, %s, NULL, NULL);\n", v, v) + default: + panic("invalid kind") + } +} + +func goClsName(n string) string { + return initialUpper(strings.Replace(n, ".", "_", -1)) +} + +func (g *ClassGen) genInterface(cls *java.Class) { + g.Printf("type %s interface {\n", goClsName(cls.Name)) + g.Indent() + // Methods + for _, f := range cls.AllMethods { + if !g.isFuncSupported(f) { + continue + } + g.Printf(f.GoName) + g.genFuncDecl(true, f) + g.Printf("\n") + } + if cls.HasSuper() { + g.Printf("Super() %s\n", goClsName(cls.Name)) + } + if cls.Throwable { + g.Printf("Error() string\n") + } + g.Outdent() + g.Printf("}\n\n") +} + +// Flatten java class names. "java.package.Class$Inner" is converted to +// "java_package_Class_Inner" +func flattenName(n string) string { + return strings.Replace(strings.Replace(n, ".", "_", -1), "$", "_", -1) +} + +var ( + classesPkgHeader = `// File is generated by gobind. Do not edit. + +package Java + +// Used to silence this package not used errors +const Dummy = 0 + +` + classesCHeader = `// File is generated by gobind. Do not edit. + +#include +#include "seq.h" +#include "classes.h" + +` + classesHHeader = `// File is generated by gobind. Do not edit. + +#include +#include "seq.h" + +extern void init_proxies(); + +` + + javaImplHeader = `// File is generated by gobind. Do not edit. + +` + + classesGoHeader = `// File is generated by gobind. Do not edit. + +package gomobile_bind + +/* +#include // for free() +#include +#include "seq.h" +#include "classes.h" +*/ +import "C" + +import ( + "Java" + _seq "golang.org/x/mobile/bind/seq" +) + +` +) diff --git a/bind/java/ClassesTest.java b/bind/java/ClassesTest.java new file mode 100644 index 0000000..abe4bac --- /dev/null +++ b/bind/java/ClassesTest.java @@ -0,0 +1,68 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package go; + +import android.test.InstrumentationTestCase; +import android.test.MoreAsserts; + +import java.io.InputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Random; + +import go.javapkg.Javapkg; + +public class ClassesTest extends InstrumentationTestCase { + public void testConst() { + assertEquals("const Float", Float.MIN_VALUE, Javapkg.floatMin()); + assertEquals("const String", java.util.jar.JarFile.MANIFEST_NAME, Javapkg.manifestName()); + assertEquals("const Int", 7, Integer.SIZE, Javapkg.integerBytes()); + } + + public void testFunction() { + Javapkg.systemCurrentTimeMillis(); + } + + public void testMethod() { + try { + assertEquals("Integer.decode", 0xff, Javapkg.integerDecode("0xff")); + } catch (Exception e) { + throw new RuntimeException(e); + } + Exception exc = null; + try { + Javapkg.integerDecode("obviously wrong"); + } catch (Exception e) { + exc = e; + } + assertNotNull("IntegerDecode Exception", exc); + } + + public void testOverloadedMethod() { + try { + assertEquals("Integer.parseInt", 0xc4, Javapkg.integerParseInt("c4", 16)); + } catch (Exception e) { + throw new RuntimeException(e); + } + Exception exc = null; + try { + Javapkg.integerParseInt("wrong", 16); + } catch (Exception e) { + exc = e; + } + assertNotNull("integerParseInt Exception", exc); + assertEquals("Integer.valueOf", 42, Javapkg.integerValueOf(42)); + } + + public void testException() { + Exception exc = null; + try { + Javapkg.provokeRuntimeException(); + } catch (Exception e) { + exc = e; + } + assertNotNull("RuntimeException", exc); + } +} diff --git a/bind/java/seq.h b/bind/java/seq.h index 0b336cd..ce18a46 100644 --- a/bind/java/seq.h +++ b/bind/java/seq.h @@ -54,4 +54,9 @@ extern JNIEnv *go_seq_push_local_frame(jint cap); // Pop the current local frame, releasing all JNI local references in it extern void go_seq_pop_local_frame(JNIEnv *env); +// Return a global reference to the given class. Return NULL and clear exception if not found. +extern jclass go_seq_find_class(const char *name); +extern jmethodID go_seq_get_static_method_id(jclass clazz, const char *name, const char *sig); +extern jmethodID go_seq_get_method_id(jclass clazz, const char *name, const char *sig); + #endif // __GO_SEQ_HDR__ diff --git a/bind/java/seq_android.c.support b/bind/java/seq_android.c.support index 4539f9c..c0f3647 100644 --- a/bind/java/seq_android.c.support +++ b/bind/java/seq_android.c.support @@ -332,6 +332,7 @@ Java_go_Seq_init(JNIEnv *env, jclass clazz) { if (ref_objField == NULL) { LOG_FATAL("failed to find the Seq.Ref.obj field"); } + initClasses(); } JNIEXPORT void JNICALL @@ -343,3 +344,35 @@ JNIEXPORT void JNICALL Java_go_Seq_incGoRef(JNIEnv *env, jclass clazz, jint refnum) { IncGoRef(refnum); } + +jclass go_seq_find_class(const char *name) { + JNIEnv *env = go_seq_push_local_frame(0); + jclass clazz = (*env)->FindClass(env, name); + if (clazz == NULL) { + (*env)->ExceptionClear(env); + } else { + clazz = (*env)->NewGlobalRef(env, clazz); + } + go_seq_pop_local_frame(env); + return clazz; +} + +jmethodID go_seq_get_static_method_id(jclass clazz, const char *name, const char *sig) { + JNIEnv *env = go_seq_push_local_frame(0); + jmethodID m = (*env)->GetStaticMethodID(env, clazz, name, sig); + if (m == NULL) { + (*env)->ExceptionClear(env); + } + go_seq_pop_local_frame(env); + return m; +} + +jmethodID go_seq_get_method_id(jclass clazz, const char *name, const char *sig) { + JNIEnv *env = go_seq_push_local_frame(0); + jmethodID m = (*env)->GetMethodID(env, clazz, name, sig); + if (m == NULL) { + (*env)->ExceptionClear(env); + } + go_seq_pop_local_frame(env); + return m; +} diff --git a/bind/java/seq_test.go b/bind/java/seq_test.go index 254e0bf..7a2a062 100644 --- a/bind/java/seq_test.go +++ b/bind/java/seq_test.go @@ -14,8 +14,19 @@ import ( "strings" "testing" "time" + + "golang.org/x/mobile/internal/importers/java" ) +func TestClasses(t *testing.T) { + if !java.IsAvailable() { + t.Skipf("java importer is not available") + } + runTest(t, []string{ + "golang.org/x/mobile/bind/testpkg/javapkg", + }, "", "ClassesTest") +} + func TestCustomPkg(t *testing.T) { runTest(t, []string{ "golang.org/x/mobile/bind/testpkg", diff --git a/bind/testdata/java.go b/bind/testdata/java.go new file mode 100644 index 0000000..997e3ee --- /dev/null +++ b/bind/testdata/java.go @@ -0,0 +1,17 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package java + +import ( + "Java/java/lang" + "Java/java/lang/Character" +) + +type ( + F lang.Float + O lang.Object + R lang.Runnable + S Character.Subset // Inner class +) diff --git a/bind/testdata/java.go.golden b/bind/testdata/java.go.golden new file mode 100644 index 0000000..e7914ea --- /dev/null +++ b/bind/testdata/java.go.golden @@ -0,0 +1,179 @@ +// File is generated by gobind. Do not edit. + +package Java + +// Used to silence this package not used errors +const Dummy = 0 + +type Java_lang_Float interface { +} + +type Java_lang_Object interface { + Super() Java_lang_Object +} + +type Java_lang_Runnable interface { +} + +type Java_lang_Character interface { +} + +type Java_lang_Character_Subset interface { + Super() Java_lang_Character_Subset +} + +// File is generated by gobind. Do not edit. + +package gomobile_bind + +/* +#include // for free() +#include +#include "seq.h" +#include "classes.h" +*/ +import "C" + +import ( + "Java" + _seq "golang.org/x/mobile/bind/seq" +) + +type proxy interface { Bind_proxy_refnum__() int32 } + +// Suppress unused package error + +var _ = _seq.FromRefNum +const _ = Java.Dummy + +//export initClasses +func initClasses() { + C.init_proxies() + init_java_lang_Float() + init_java_lang_Object() + init_java_lang_Runnable() + init_java_lang_Character() + init_java_lang_Character_Subset() +} + +func init_java_lang_Float() { + cls := C.CString("java/lang/Float") + clazz := C.go_seq_find_class(cls) + C.free(unsafe.Pointer(cls)) + if clazz == nil { + return + } +} + +type proxy_class_java_lang_Float _seq.Ref + +func (p *proxy_class_java_lang_Float) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() } + +func init_java_lang_Object() { + cls := C.CString("java/lang/Object") + clazz := C.go_seq_find_class(cls) + C.free(unsafe.Pointer(cls)) + if clazz == nil { + return + } +} + +type proxy_class_java_lang_Object _seq.Ref + +func (p *proxy_class_java_lang_Object) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() } + +func (p *proxy_class_java_lang_Object) Super() Java.Java_lang_Object { + return &super_java_lang_Object{p} +} + +type super_java_lang_Object struct {*proxy_class_java_lang_Object} + +func init_java_lang_Runnable() { + cls := C.CString("java/lang/Runnable") + clazz := C.go_seq_find_class(cls) + C.free(unsafe.Pointer(cls)) + if clazz == nil { + return + } +} + +type proxy_class_java_lang_Runnable _seq.Ref + +func (p *proxy_class_java_lang_Runnable) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() } + +func init_java_lang_Character() { + cls := C.CString("java/lang/Character") + clazz := C.go_seq_find_class(cls) + C.free(unsafe.Pointer(cls)) + if clazz == nil { + return + } +} + +type proxy_class_java_lang_Character _seq.Ref + +func (p *proxy_class_java_lang_Character) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() } + +func init_java_lang_Character_Subset() { + cls := C.CString("java/lang/Character$Subset") + clazz := C.go_seq_find_class(cls) + C.free(unsafe.Pointer(cls)) + if clazz == nil { + return + } +} + +type proxy_class_java_lang_Character_Subset _seq.Ref + +func (p *proxy_class_java_lang_Character_Subset) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() } + +func (p *proxy_class_java_lang_Character_Subset) Super() Java.Java_lang_Character_Subset { + return &super_java_lang_Character_Subset{p} +} + +type super_java_lang_Character_Subset struct {*proxy_class_java_lang_Character_Subset} + +// Package gomobile_bind is an autogenerated binder stub for package java. +// gobind -lang=go java +// +// File is generated by gobind. Do not edit. +package gomobile_bind + +/* +#include +#include +#include "seq.h" +#include "java.h" + +*/ +import "C" + +import ( + _seq "golang.org/x/mobile/bind/seq" +) + +// suppress the error if seq ends up unused +var _ = _seq.FromRefNum + +type proxyjava_F _seq.Ref + +func (p *proxyjava_F) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() } + +// skipped method O.Super with unsupported parameter or return types + +type proxyjava_O _seq.Ref + +func (p *proxyjava_O) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() } + +// skipped method O.Super with unsupported parameter or result types +type proxyjava_R _seq.Ref + +func (p *proxyjava_R) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() } + +// skipped method S.Super with unsupported parameter or return types + +type proxyjava_S _seq.Ref + +func (p *proxyjava_S) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() } + +// skipped method S.Super with unsupported parameter or result types diff --git a/bind/testdata/java.java.c.golden b/bind/testdata/java.java.c.golden new file mode 100644 index 0000000..3cef5e8 --- /dev/null +++ b/bind/testdata/java.java.c.golden @@ -0,0 +1,89 @@ +// File is generated by gobind. Do not edit. + +#include +#include "seq.h" +#include "classes.h" + +static jclass class_java_lang_Float; +static jclass class_java_lang_Object; +static jclass class_java_lang_Runnable; +static jclass class_java_lang_Character; +static jclass class_java_lang_Character_Subset; + +void init_proxies() { + JNIEnv *env = go_seq_push_local_frame(5); + jclass clazz; + clazz = (*env)->FindClass(env, "java/lang/Float"); + class_java_lang_Float = (*env)->NewGlobalRef(env, clazz); + clazz = (*env)->FindClass(env, "java/lang/Object"); + class_java_lang_Object = (*env)->NewGlobalRef(env, clazz); + clazz = (*env)->FindClass(env, "java/lang/Runnable"); + class_java_lang_Runnable = (*env)->NewGlobalRef(env, clazz); + clazz = (*env)->FindClass(env, "java/lang/Character"); + class_java_lang_Character = (*env)->NewGlobalRef(env, clazz); + clazz = (*env)->FindClass(env, "java/lang/Character$Subset"); + class_java_lang_Character_Subset = (*env)->NewGlobalRef(env, clazz); + go_seq_pop_local_frame(env); +} + +// JNI functions for the Go <=> Java bridge. +// gobind -lang=java java +// +// File is generated by gobind. Do not edit. + +#include +#include +#include "seq.h" +#include "_cgo_export.h" +#include "java.h" + +jclass proxy_class_java_F; +jmethodID proxy_class_java_F_cons; +jclass proxy_class_java_O; +jmethodID proxy_class_java_O_cons; +// skipped method O.Super with unsupported parameter or return types + +jclass proxy_class_java_R; +jmethodID proxy_class_java_R_cons; +jclass proxy_class_java_S; +jmethodID proxy_class_java_S_cons; +// skipped method S.Super with unsupported parameter or return types + + +JNIEXPORT void JNICALL +Java_go_java_Java__1init(JNIEnv *env, jclass _unused) { + jclass clazz; + clazz = (*env)->FindClass(env, "go/java/Java$proxyF"); + proxy_class_java_F = (*env)->NewGlobalRef(env, clazz); + proxy_class_java_F_cons = (*env)->GetMethodID(env, clazz, "", "(Lgo/Seq$Ref;)V"); + clazz = (*env)->FindClass(env, "go/java/F"); + + clazz = (*env)->FindClass(env, "go/java/Java$proxyO"); + proxy_class_java_O = (*env)->NewGlobalRef(env, clazz); + proxy_class_java_O_cons = (*env)->GetMethodID(env, clazz, "", "(Lgo/Seq$Ref;)V"); + clazz = (*env)->FindClass(env, "go/java/O"); + // skipped method O.Super with unsupported parameter or return types + + + clazz = (*env)->FindClass(env, "go/java/Java$proxyR"); + proxy_class_java_R = (*env)->NewGlobalRef(env, clazz); + proxy_class_java_R_cons = (*env)->GetMethodID(env, clazz, "", "(Lgo/Seq$Ref;)V"); + clazz = (*env)->FindClass(env, "go/java/R"); + + clazz = (*env)->FindClass(env, "go/java/Java$proxyS"); + proxy_class_java_S = (*env)->NewGlobalRef(env, clazz); + proxy_class_java_S_cons = (*env)->GetMethodID(env, clazz, "", "(Lgo/Seq$Ref;)V"); + clazz = (*env)->FindClass(env, "go/java/S"); + // skipped method S.Super with unsupported parameter or return types + + +} + +// skipped function Super with unsupported parameter or return types + +// skipped method O with unsupported parameter or return types + +// skipped function Super with unsupported parameter or return types + +// skipped method S with unsupported parameter or return types + diff --git a/bind/testdata/java.java.golden b/bind/testdata/java.java.golden new file mode 100644 index 0000000..f678fb8 --- /dev/null +++ b/bind/testdata/java.java.golden @@ -0,0 +1,96 @@ +// Java class go.java.F is a proxy for talking to a Go program. +// gobind -lang=java java +// +// File is generated by gobind. Do not edit. +package go.java; + +import go.Seq; + +public interface F { + +} + +// Java class go.java.O is a proxy for talking to a Go program. +// gobind -lang=java java +// +// File is generated by gobind. Do not edit. +package go.java; + +import go.Seq; + +public interface O extends F, R { + // skipped method O.Super with unsupported parameter or return types + + +} + +// Java class go.java.R is a proxy for talking to a Go program. +// gobind -lang=java java +// +// File is generated by gobind. Do not edit. +package go.java; + +import go.Seq; + +public interface R { + +} + +// Java class go.java.S is a proxy for talking to a Go program. +// gobind -lang=java java +// +// File is generated by gobind. Do not edit. +package go.java; + +import go.Seq; + +public interface S extends F, R { + // skipped method S.Super with unsupported parameter or return types + + +} + +// Java class go.java.Java is a proxy for talking to a Go program. +// gobind -lang=java java +// +// File is generated by gobind. Do not edit. +package go.java; + +import go.Seq; + +public abstract class Java { + static { + Seq.touch(); // for loading the native library + _init(); + } + + private Java() {} // uninstantiable + + // touch is called from other bound packages to initialize this package + public static void touch() {} + + private static native void _init(); + + private static final class proxyF extends Seq.Proxy implements F { + proxyF(Seq.Ref ref) { super(ref); } + + } + private static final class proxyO extends Seq.Proxy implements O { + proxyO(Seq.Ref ref) { super(ref); } + + // skipped method O.Super with unsupported parameter or return types + + } + private static final class proxyR extends Seq.Proxy implements R { + proxyR(Seq.Ref ref) { super(ref); } + + } + private static final class proxyS extends Seq.Proxy implements S { + proxyS(Seq.Ref ref) { super(ref); } + + // skipped method S.Super with unsupported parameter or return types + + } + + +} diff --git a/bind/testdata/java.java.h.golden b/bind/testdata/java.java.h.golden new file mode 100644 index 0000000..03bbe6f --- /dev/null +++ b/bind/testdata/java.java.h.golden @@ -0,0 +1,75 @@ +// File is generated by gobind. Do not edit. + +#include +#include "seq.h" + +extern void init_proxies(); + +typedef struct ret_jint { + jint res; + jint exc; +} ret_jint; +typedef struct ret_jboolean { + jboolean res; + jint exc; +} ret_jboolean; +typedef struct ret_jshort { + jshort res; + jint exc; +} ret_jshort; +typedef struct ret_jchar { + jchar res; + jint exc; +} ret_jchar; +typedef struct ret_jbyte { + jbyte res; + jint exc; +} ret_jbyte; +typedef struct ret_jlong { + jlong res; + jint exc; +} ret_jlong; +typedef struct ret_jfloat { + jfloat res; + jint exc; +} ret_jfloat; +typedef struct ret_jdouble { + jdouble res; + jint exc; +} ret_jdouble; +typedef struct ret_nstring { + nstring res; + jint exc; +} ret_nstring; +typedef struct ret_nbyteslice { + nbyteslice res; + jint exc; +} ret_nbyteslice; + +// JNI function headers for the Go <=> Java bridge. +// gobind -lang=java java +// +// File is generated by gobind. Do not edit. + +#ifndef __Java_H__ +#define __Java_H__ + +#include + +extern jclass proxy_class_java_F; +extern jmethodID proxy_class_java_F_cons; + +extern jclass proxy_class_java_O; +extern jmethodID proxy_class_java_O_cons; + +// skipped method O.Super with unsupported parameter or return types + +extern jclass proxy_class_java_R; +extern jmethodID proxy_class_java_R_cons; + +extern jclass proxy_class_java_S; +extern jmethodID proxy_class_java_S_cons; + +// skipped method S.Super with unsupported parameter or return types + +#endif diff --git a/bind/testpkg/javapkg/java.go b/bind/testpkg/javapkg/java.go new file mode 100644 index 0000000..fa2a3c6 --- /dev/null +++ b/bind/testpkg/javapkg/java.go @@ -0,0 +1,57 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package javapkg + +import ( + "Java/java/lang/Float" + "Java/java/lang/Integer" + "Java/java/lang/System" + "Java/java/util/Collections" + "Java/java/util/jar/JarFile" + "fmt" +) + +func SystemCurrentTimeMillis() int64 { + return System.CurrentTimeMillis() +} + +func FloatMin() float32 { + return Float.MIN_VALUE +} + +func ManifestName() string { + return JarFile.MANIFEST_NAME +} + +func IntegerBytes() int { + return Integer.SIZE +} + +func IntegerValueOf(v int32) int32 { + i := Integer.ValueOf_I(v) + return i.IntValue() +} + +func IntegerDecode(v string) (int32, error) { + i, err := Integer.Decode(v) + if err != nil { + return 0, fmt.Errorf("wrapped error: %v", err) + } + // Call methods from super class + i.HashCode() + return i.IntValue(), nil +} + +func IntegerParseInt(v string, radix int32) (int32, error) { + return Integer.ParseInt2(v, radix) +} + +func ProvokeRuntimeException() (err error) { + defer func() { + err = recover().(error) + }() + Collections.Copy(nil, nil) + return +} diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go index 18de5d0..8488ed5 100644 --- a/cmd/gomobile/bind.go +++ b/cmd/gomobile/bind.go @@ -20,6 +20,8 @@ import ( "strings" "golang.org/x/mobile/bind" + "golang.org/x/mobile/internal/importers" + "golang.org/x/mobile/internal/importers/java" ) // ctx, pkg, tmpdir in build.go @@ -252,7 +254,98 @@ func (b *binder) GenJavaSupport(outdir string) error { return copyFile(filepath.Join(outdir, "seq.h"), filepath.Join(javaPkg.Dir, "seq.h")) } -func (b *binder) GenJava(pkg *types.Package, allPkg []*types.Package, outdir, javadir string) error { +func GenClasses(pkgs []*build.Package, srcDir, jpkgSrc string) ([]*java.Class, error) { + apiPath, err := androidAPIPath() + if err != nil { + return nil, err + } + refs, err := importers.AnalyzePackages(pkgs, "Java/") + if err != nil { + return nil, err + } + classes, err := java.Import(filepath.Join(apiPath, "android.jar"), refs) + if err != nil { + return nil, err + } + var buf bytes.Buffer + g := &bind.ClassGen{ + Printer: &bind.Printer{ + IndentEach: []byte("\t"), + Buf: &buf, + }, + } + g.Init(classes) + for i, jpkg := range g.Packages() { + pkgDir := filepath.Join(jpkgSrc, "src", "Java", jpkg) + if err := os.MkdirAll(pkgDir, 0700); err != nil { + return nil, err + } + pkgFile := filepath.Join(pkgDir, "package.go") + generate := func(w io.Writer) error { + if buildN { + return nil + } + buf.Reset() + g.GenPackage(i) + _, err := io.Copy(w, &buf) + return err + } + if err := writeFile(pkgFile, generate); err != nil { + return nil, fmt.Errorf("failed to create the Java wrapper package %s: %v", jpkg, err) + } + } + generate := func(w io.Writer) error { + if buildN { + return nil + } + buf.Reset() + g.GenGo() + _, err := io.Copy(w, &buf) + return err + } + if err := writeFile(filepath.Join(srcDir, "classes.go"), generate); err != nil { + return nil, fmt.Errorf("failed to create the Java classes Go file: %v", err) + } + generate = func(w io.Writer) error { + if buildN { + return nil + } + buf.Reset() + g.GenH() + _, err := io.Copy(w, &buf) + return err + } + if err := writeFile(filepath.Join(srcDir, "classes.h"), generate); err != nil { + return nil, fmt.Errorf("failed to create the Java classes header file: %v", err) + } + generate = func(w io.Writer) error { + if buildN { + return nil + } + buf.Reset() + g.GenC() + _, err := io.Copy(w, &buf) + return err + } + if err := writeFile(filepath.Join(srcDir, "classes.c"), generate); err != nil { + return nil, fmt.Errorf("failed to create the Java classes C file: %v", err) + } + generate = func(w io.Writer) error { + if buildN { + return nil + } + buf.Reset() + g.GenInterfaces() + _, err := io.Copy(w, &buf) + return err + } + if err := writeFile(filepath.Join(jpkgSrc, "src", "Java", "interfaces.go"), generate); err != nil { + return nil, fmt.Errorf("failed to create the Java classes interfaces file: %v", err) + } + return classes, nil +} + +func (b *binder) GenJava(pkg *types.Package, allPkg []*types.Package, classes []*java.Class, outdir, javadir string) error { var className string pkgName := "" pkgPath := "" diff --git a/cmd/gomobile/bind_androidapp.go b/cmd/gomobile/bind_androidapp.go index 60f9166..b35c779 100644 --- a/cmd/gomobile/bind_androidapp.go +++ b/cmd/gomobile/bind_androidapp.go @@ -35,11 +35,15 @@ func goAndroidBind(pkgs []*build.Package, androidArchs []string) error { androidDir := filepath.Join(tmpdir, "android") mainFile := filepath.Join(tmpdir, "androidlib/main.go") + jpkgSrc := filepath.Join(tmpdir, "gen") // Generate binding code and java source code only when processing the first package. first := true for _, arch := range androidArchs { env := androidEnv[arch] + // Add the generated Java class wrapper packages to GOPATH + gopath := fmt.Sprintf("GOPATH=%s%c%s", jpkgSrc, filepath.ListSeparator, os.Getenv("GOPATH")) + env = append(env, gopath) toolchain := ndk.Toolchain(arch) if !first { @@ -60,6 +64,16 @@ func goAndroidBind(pkgs []*build.Package, androidArchs []string) error { } first = false + srcDir := filepath.Join(tmpdir, "gomobile_bind") + if err := mkdir(srcDir); err != nil { + return err + } + + classes, err := GenClasses(pkgs, srcDir, jpkgSrc) + if err != nil { + return err + } + typesPkgs, err := loadExportData(pkgs, env, androidArgs...) if err != nil { return fmt.Errorf("loadExportData failed %v", err) @@ -70,7 +84,6 @@ func goAndroidBind(pkgs []*build.Package, androidArchs []string) error { return err } - srcDir := filepath.Join(tmpdir, "gomobile_bind") for _, pkg := range binder.pkgs { if err := binder.GenGo(pkg, binder.pkgs, srcDir); err != nil { return err @@ -96,15 +109,16 @@ func goAndroidBind(pkgs []*build.Package, androidArchs []string) error { repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory. for _, pkg := range binder.pkgs { - pkgpath := strings.Replace(bindJavaPkg, ".", "/", -1) - if bindJavaPkg == "" { - pkgpath = "go/" + pkg.Name() + pkgpath := bindJavaPkg + if pkgpath == "" { + pkgpath = "go." + pkg.Name() } - if err := binder.GenJava(pkg, binder.pkgs, srcDir, filepath.Join(androidDir, "src/main/java/"+pkgpath)); err != nil { + jclsDir := filepath.Join(androidDir, "src/main/java/"+strings.Replace(pkgpath, ".", "/", -1)) + if err := binder.GenJava(pkg, binder.pkgs, classes, srcDir, jclsDir); err != nil { return err } } - if err := binder.GenJava(nil, binder.pkgs, srcDir, filepath.Join(androidDir, "src/main/java/go")); err != nil { + if err := binder.GenJava(nil, binder.pkgs, classes, srcDir, filepath.Join(androidDir, "src/main/java/go")); err != nil { return err } if err := binder.GenJavaSupport(srcDir); err != nil { diff --git a/cmd/gomobile/bind_test.go b/cmd/gomobile/bind_test.go index 9430537..0e4e708 100644 --- a/cmd/gomobile/bind_test.go +++ b/cmd/gomobile/bind_test.go @@ -101,7 +101,12 @@ func TestBindAndroid(t *testing.T) { var bindAndroidTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile WORK=$WORK -GOOS=android GOARCH=arm CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-clang CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-clang++ CGO_CFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_CPPFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_LDFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_ENABLED=1 GOARM=7 go install -pkgdir=$GOMOBILE/pkg_android_arm -tags="" -x golang.org/x/mobile/asset +mkdir -p $WORK/gomobile_bind +mkdir -p $WORK/gomobile_bind +mkdir -p $WORK/gomobile_bind +mkdir -p $WORK/gomobile_bind +mkdir -p $WORK/gen/src/Java +GOOS=android GOARCH=arm CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-clang CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-clang++ CGO_CFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_CPPFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_LDFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_ENABLED=1 GOARM=7 GOPATH=$WORK/gen:$GOPATH go install -pkgdir=$GOMOBILE/pkg_android_arm -tags="" -x golang.org/x/mobile/asset rm -r -f "$WORK/fakegopath" mkdir -p $WORK/fakegopath/pkg cp $GOMOBILE/pkg_android_arm/golang.org/x/mobile/asset.a $WORK/fakegopath/pkg/android_arm/golang.org/x/mobile/asset.a @@ -129,7 +134,7 @@ mkdir -p $WORK/gomobile_bind cp $GOPATH/src/golang.org/x/mobile/bind/seq.go.support $WORK/gomobile_bind/seq.go mkdir -p $WORK/gomobile_bind mkdir -p $WORK/android/src/main/java/go -GOOS=android GOARCH=arm CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-clang{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-clang++{{.EXE}} CGO_CFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_CPPFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_LDFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_ENABLED=1 GOARM=7 go build -pkgdir=$GOMOBILE/pkg_android_arm -tags="" -x -buildmode=c-shared -o=$WORK/android/src/main/jniLibs/armeabi-v7a/libgojni.so $WORK/androidlib/main.go +GOOS=android GOARCH=arm CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-clang{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-clang++{{.EXE}} CGO_CFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_CPPFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_LDFLAGS=-target armv7a-none-linux-androideabi --sysroot $GOMOBILE/android-{{.NDK}}/arm/sysroot CGO_ENABLED=1 GOARM=7 GOPATH=$WORK/gen:$GOPATH go build -pkgdir=$GOMOBILE/pkg_android_arm -tags="" -x -buildmode=c-shared -o=$WORK/android/src/main/jniLibs/armeabi-v7a/libgojni.so $WORK/androidlib/main.go rm $WORK/android/src/main/java/go/Seq.java ln -s $GOPATH/src/golang.org/x/mobile/bind/java/Seq.java $WORK/android/src/main/java/go/Seq.java rm $WORK/android/src/main/java/go/LoadJNI.java