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