From b2e453e1cda693a5a97d5c97cc9f3016a64b7dfa Mon Sep 17 00:00:00 2001 From: Hyang-Ah Hana Kim Date: Fri, 19 Dec 2014 12:59:43 -0500 Subject: [PATCH] bind/java: support byte arrays. Fixes golang/go#9338. Change-Id: I6e2af67cdf7f923963fa525b944613a91aac994e Reviewed-on: https://go-review.googlesource.com/1884 Reviewed-by: David Crawshaw --- bind/java/SeqTest.java | 29 +++++++++++++++ bind/java/seq_android.c | 43 ++++++++++++++++++++++ bind/java/testpkg/Testpkg.java | 26 +++++++++---- bind/java/testpkg/go_testpkg/go_testpkg.go | 22 +++++++---- bind/java/testpkg/testpkg.go | 4 ++ bind/seq.go | 17 ++++++++- bind/seq/buffer.go | 31 ++++++++++++++++ bind/testdata/basictypes.go | 2 + bind/testdata/basictypes.go.golden | 13 +++++-- bind/testdata/basictypes.java.golden | 17 +++++++-- 10 files changed, 182 insertions(+), 22 deletions(-) diff --git a/bind/java/SeqTest.java b/bind/java/SeqTest.java index 3718382..a3622aa 100644 --- a/bind/java/SeqTest.java +++ b/bind/java/SeqTest.java @@ -1,6 +1,9 @@ package go; import android.test.suitebuilder.annotation.Suppress; +import android.test.MoreAsserts; +import java.util.Arrays; +import java.util.Random; import go.testpkg.Testpkg; @@ -52,6 +55,32 @@ public class SeqTest extends TestCase { } } + public void testByteArray() { + for (int i = 0; i < 2048; i++) { + if (i == 0) { + byte[] got = Testpkg.BytesAppend(null, null); + assertEquals("Bytes(null+null) should match", (byte[])null, got); + got = Testpkg.BytesAppend(new byte[0], new byte[0]); + assertEquals("Bytes(empty+empty) should match", (byte[])null, got); + continue; + } + + byte[] want = new byte[i]; + new Random().nextBytes(want); + + byte[] s1 = null; + byte[] s2 = null; + if (i > 0) { + s1 = Arrays.copyOfRange(want, 0, 1); + } + if (i > 1) { + s2 = Arrays.copyOfRange(want, 1, i); + } + byte[] got = Testpkg.BytesAppend(s1, s2); + MoreAsserts.assertEquals("Bytes(len="+i+") should match", want, got); + } + } + public void testGoRefGC() { Testpkg.S s = Testpkg.New(); runGC(); diff --git a/bind/java/seq_android.c b/bind/java/seq_android.c index e2eeb48..f0c9bfd 100644 --- a/bind/java/seq_android.c +++ b/bind/java/seq_android.c @@ -203,6 +203,20 @@ Java_go_Seq_readUTF16(JNIEnv *env, jobject obj) { return (*env)->NewString(env, (jchar*)mem_read(env, obj, 2*size), size); } +JNIEXPORT jbyteArray JNICALL +Java_go_Seq_readByteArray(JNIEnv *env, jobject obj) { + // Send the (array length, pointer) pair encoded as two int64. + // The pointer value is omitted if array length is 0. + jlong size = Java_go_Seq_readInt64(env, obj); + if (size == 0) { + return NULL; + } + jbyteArray res = (*env)->NewByteArray(env, size); + jlong ptr = Java_go_Seq_readInt64(env, obj); + (*env)->SetByteArrayRegion(env, res, 0, size, (jbyte*)(intptr_t)(ptr)); + return res; +} + #define MEM_WRITE(ty) (*(ty*)mem_write(env, obj, sizeof(ty))) JNIEXPORT void JNICALL @@ -246,6 +260,35 @@ Java_go_Seq_writeUTF16(JNIEnv *env, jobject obj, jstring v) { (*env)->GetStringRegion(env, v, 0, size, (jchar*)mem_write(env, obj, 2*size)); } +JNIEXPORT void JNICALL +Java_go_Seq_writeByteArray(JNIEnv *env, jobject obj, jbyteArray v) { + // For Byte array, we pass only the (array length, pointer) pair + // encoded as two int64 values. If the array length is 0, + // the pointer value is omitted. + if (v == NULL) { + MEM_WRITE(int64_t) = 0; + return; + } + + jsize len = (*env)->GetArrayLength(env, v); + MEM_WRITE(int64_t) = len; + if (len == 0) { + return; + } + + jboolean isCopy; + jbyte* b = (*env)->GetByteArrayElements(env, v, &isCopy); + if (isCopy) { + // TODO: It's not clear how to handle if b is pointing to + // a copy that may become invalid with ReleaseByteArrayElements. + // Should we fall back to copy the byte array into the buffer? + LOG_FATAL("got a copied byte array (len=%d)", len); + } + // gross pointer-to-int64 conversion. + MEM_WRITE(int64_t) = (int64_t)((intptr_t)b); + (*env)->ReleaseByteArrayElements(env, v, (jbyte*)b, 0); +} + JNIEXPORT void JNICALL Java_go_Seq_resetOffset(JNIEnv *env, jobject obj) { mem *m = mem_get(env, obj); diff --git a/bind/java/testpkg/Testpkg.java b/bind/java/testpkg/Testpkg.java index 5fb518b..1cdc75b 100644 --- a/bind/java/testpkg/Testpkg.java +++ b/bind/java/testpkg/Testpkg.java @@ -20,6 +20,17 @@ public abstract class Testpkg { return _result; } + public static byte[] BytesAppend(byte[] a, byte[] b) { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + byte[] _result; + _in.writeByteArray(a); + _in.writeByteArray(b); + Seq.send(DESCRIPTOR, CALL_BytesAppend, _in, _out); + _result = _out.readByteArray(); + return _result; + } + public static void Call(I i) { go.Seq _in = new go.Seq(); go.Seq _out = new go.Seq(); @@ -171,12 +182,13 @@ public abstract class Testpkg { } private static final int CALL_Add = 1; - private static final int CALL_Call = 2; - private static final int CALL_Err = 3; - private static final int CALL_GC = 4; - private static final int CALL_Keep = 5; - private static final int CALL_New = 6; - private static final int CALL_NumSCollected = 7; - private static final int CALL_StrDup = 8; + private static final int CALL_BytesAppend = 2; + private static final int CALL_Call = 3; + private static final int CALL_Err = 4; + private static final int CALL_GC = 5; + private static final int CALL_Keep = 6; + private static final int CALL_New = 7; + private static final int CALL_NumSCollected = 8; + private static final int CALL_StrDup = 9; private static final String DESCRIPTOR = "testpkg"; } diff --git a/bind/java/testpkg/go_testpkg/go_testpkg.go b/bind/java/testpkg/go_testpkg/go_testpkg.go index 2d3f3b4..618780c 100644 --- a/bind/java/testpkg/go_testpkg/go_testpkg.go +++ b/bind/java/testpkg/go_testpkg/go_testpkg.go @@ -16,6 +16,13 @@ func proxy_Add(out, in *seq.Buffer) { out.WriteInt(res) } +func proxy_BytesAppend(out, in *seq.Buffer) { + param_a := in.ReadByteArray() + param_b := in.ReadByteArray() + res := testpkg.BytesAppend(param_a, param_b) + out.WriteByteArray(res) +} + func proxy_Call(out, in *seq.Buffer) { var param_i testpkg.I param_i_ref := in.ReadRef() @@ -99,11 +106,12 @@ func proxy_StrDup(out, in *seq.Buffer) { func init() { seq.Register("testpkg", 1, proxy_Add) - seq.Register("testpkg", 2, proxy_Call) - seq.Register("testpkg", 3, proxy_Err) - seq.Register("testpkg", 4, proxy_GC) - seq.Register("testpkg", 5, proxy_Keep) - seq.Register("testpkg", 6, proxy_New) - seq.Register("testpkg", 7, proxy_NumSCollected) - seq.Register("testpkg", 8, proxy_StrDup) + seq.Register("testpkg", 2, proxy_BytesAppend) + seq.Register("testpkg", 3, proxy_Call) + seq.Register("testpkg", 4, proxy_Err) + seq.Register("testpkg", 5, proxy_GC) + seq.Register("testpkg", 6, proxy_Keep) + seq.Register("testpkg", 7, proxy_New) + seq.Register("testpkg", 8, proxy_NumSCollected) + seq.Register("testpkg", 9, proxy_StrDup) } diff --git a/bind/java/testpkg/testpkg.go b/bind/java/testpkg/testpkg.go index 6a6d7ec..0281972 100644 --- a/bind/java/testpkg/testpkg.go +++ b/bind/java/testpkg/testpkg.go @@ -68,3 +68,7 @@ func Err(s string) error { } return nil } + +func BytesAppend(a []byte, b []byte) []byte { + return append(a, b...) +} diff --git a/bind/seq.go b/bind/seq.go index 9324d51..a2fa9f4 100644 --- a/bind/seq.go +++ b/bind/seq.go @@ -25,7 +25,7 @@ func seqType(t types.Type) string { return "Int32" case types.Int64: return "Int64" - case types.Uint8: + case types.Uint8: // Byte. // TODO(crawshaw): questionable, but vital? return "Byte" // TODO(crawshaw): case types.Uint, types.Uint16, types.Uint32, types.Uint64: @@ -37,7 +37,7 @@ func seqType(t types.Type) string { return "UTF16" default: // Should be caught earlier in processing. - panic(fmt.Sprintf("unsupported return type: %s", t)) + panic(fmt.Sprintf("unsupported basic seqType: %s", t)) } case *types.Named: switch u := t.Underlying().(type) { @@ -46,6 +46,19 @@ func seqType(t types.Type) string { default: panic(fmt.Sprintf("unsupported named seqType: %s / %T", u, u)) } + case *types.Slice: + switch e := t.Elem().(type) { + case *types.Basic: + switch e.Kind() { + case types.Uint8: // Byte. + return "ByteArray" + default: + panic(fmt.Sprintf("unsupported seqType: %s(%s) / %T(%T)", t, e, t, e)) + } + default: + panic(fmt.Sprintf("unsupported seqType: %s(%s) / %T(%T)", t, e, t, e)) + } + // TODO: let the types.Array case handled like types.Slice? default: panic(fmt.Sprintf("unsupported seqType: %s / %T", t, t)) } diff --git a/bind/seq/buffer.go b/bind/seq/buffer.go index eb53f12..6fb9441 100644 --- a/bind/seq/buffer.go +++ b/bind/seq/buffer.go @@ -91,6 +91,22 @@ func (b *Buffer) ReadFloat64() float64 { return v } +func (b *Buffer) ReadByteArray() []byte { + sz := b.ReadInt64() + if sz == 0 { + return nil + } + + ptr := b.ReadInt64() + org := (*[1 << 30]byte)(unsafe.Pointer(uintptr(ptr)))[:sz] + + // Make a copy managed by Go, so the returned byte array can be + // used safely in Go. + slice := make([]byte, sz) + copy(slice, org) + return slice +} + func (b *Buffer) ReadRef() *Ref { ref := &Ref{b.ReadInt32()} if ref.Num > 0 { @@ -137,6 +153,19 @@ func (b *Buffer) WriteFloat64(v float64) { b.Offset += 8 } +func (b *Buffer) WriteByteArray(byt []byte) { + sz := len(byt) + if sz == 0 { + b.WriteInt64(int64(sz)) + return + } + + ptr := uintptr(unsafe.Pointer(&byt[0])) + b.WriteInt64(int64(sz)) + b.WriteInt64(int64(ptr)) + return +} + func (b *Buffer) WriteGoRef(obj interface{}) { refs.Lock() num := refs.refs[obj] @@ -154,6 +183,8 @@ func (b *Buffer) WriteGoRef(obj interface{}) { b.WriteInt32(int32(num)) } +/* TODO: Will we need it? func (b *Buffer) WriteRef(ref *Ref) { b.WriteInt32(ref.Num) } +*/ diff --git a/bind/testdata/basictypes.go b/bind/testdata/basictypes.go index 802dda7..bc0bffb 100644 --- a/bind/testdata/basictypes.go +++ b/bind/testdata/basictypes.go @@ -9,3 +9,5 @@ func Ints(x int8, y int16, z int32, t int64, u int) {} func Error() error { return nil } func ErrorPair() (int, error) { return 0, nil } + +func ByteArrays(x []byte) []byte { return nil } diff --git a/bind/testdata/basictypes.go.golden b/bind/testdata/basictypes.go.golden index b24898b..ed35dda 100644 --- a/bind/testdata/basictypes.go.golden +++ b/bind/testdata/basictypes.go.golden @@ -9,6 +9,12 @@ import ( "golang.org/x/mobile/bind/seq" ) +func proxy_ByteArrays(out, in *seq.Buffer) { + param_x := in.ReadByteArray() + res := basictypes.ByteArrays(param_x) + out.WriteByteArray(res) +} + func proxy_Error(out, in *seq.Buffer) { err := basictypes.Error() if err == nil { @@ -38,7 +44,8 @@ func proxy_Ints(out, in *seq.Buffer) { } func init() { - seq.Register("basictypes", 1, proxy_Error) - seq.Register("basictypes", 2, proxy_ErrorPair) - seq.Register("basictypes", 3, proxy_Ints) + seq.Register("basictypes", 1, proxy_ByteArrays) + seq.Register("basictypes", 2, proxy_Error) + seq.Register("basictypes", 3, proxy_ErrorPair) + seq.Register("basictypes", 4, proxy_Ints) } diff --git a/bind/testdata/basictypes.java.golden b/bind/testdata/basictypes.java.golden index 56ede5d..f44ad6e 100644 --- a/bind/testdata/basictypes.java.golden +++ b/bind/testdata/basictypes.java.golden @@ -9,6 +9,16 @@ import go.Seq; public abstract class Basictypes { private Basictypes() {} // uninstantiable + public static byte[] ByteArrays(byte[] x) { + go.Seq _in = new go.Seq(); + go.Seq _out = new go.Seq(); + byte[] _result; + _in.writeByteArray(x); + Seq.send(DESCRIPTOR, CALL_ByteArrays, _in, _out); + _result = _out.readByteArray(); + return _result; + } + public static void Error() throws Exception { go.Seq _in = new go.Seq(); go.Seq _out = new go.Seq(); @@ -43,8 +53,9 @@ public abstract class Basictypes { Seq.send(DESCRIPTOR, CALL_Ints, _in, _out); } - private static final int CALL_Error = 1; - private static final int CALL_ErrorPair = 2; - private static final int CALL_Ints = 3; + private static final int CALL_ByteArrays = 1; + private static final int CALL_Error = 2; + private static final int CALL_ErrorPair = 3; + private static final int CALL_Ints = 4; private static final String DESCRIPTOR = "basictypes"; }