This removes the last dependency on an Android class in the running gobind code. (Tests still need some work before they will run on the desktop.) Change-Id: I49bfb545d4587c6f430c0938fa1ca4d513b56d77 Reviewed-on: https://go-review.googlesource.com/17252 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
419 lines
11 KiB
Java
419 lines
11 KiB
Java
// 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 go;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.logging.Logger;
|
|
|
|
// Seq is a sequence of machine-dependent encoded values.
|
|
// Used by automatically generated language bindings to talk to Go.
|
|
public class Seq {
|
|
private static Logger log = Logger.getLogger("GoSeq");
|
|
|
|
static {
|
|
// Look for the shim class auto-generated by gomobile bind.
|
|
// Its only purpose is to call System.loadLibrary.
|
|
try {
|
|
Class loadJNI = Class.forName("go.LoadJNI");
|
|
setContext(loadJNI.getDeclaredField("ctx").get(null));
|
|
} catch (ClassNotFoundException e) {
|
|
// Ignore, assume the user will load JNI for it.
|
|
log.warning("LoadJNI class not found");
|
|
} catch (NoSuchFieldException e) {
|
|
log.severe("LoadJNI class missing field: " + e);
|
|
} catch (IllegalAccessException e) {
|
|
log.severe("LoadJNI class bad field: " + e);
|
|
}
|
|
|
|
initSeq();
|
|
new Thread("GoSeq") {
|
|
public void run() { Seq.receive(); }
|
|
}.start();
|
|
}
|
|
|
|
@SuppressWarnings("UnusedDeclaration")
|
|
private long memptr; // holds C-allocated pointer
|
|
|
|
public Seq() {
|
|
ensure(64);
|
|
}
|
|
|
|
// ctx is an android.context.Context.
|
|
static native void setContext(java.lang.Object ctx);
|
|
|
|
// Ensure that at least size bytes can be written to the Seq.
|
|
// Any existing data in the buffer is preserved.
|
|
public native void ensure(int size);
|
|
|
|
// Moves the internal buffer offset back to zero.
|
|
// Length and contents are maintained. Data can be read after a reset.
|
|
public native void resetOffset();
|
|
|
|
public native void log(String label);
|
|
|
|
public native boolean readBool();
|
|
public native byte readInt8();
|
|
public native short readInt16();
|
|
public native int readInt32();
|
|
public native long readInt64();
|
|
public long readInt() { return readInt64(); }
|
|
|
|
public native float readFloat32();
|
|
public native double readFloat64();
|
|
public native String readUTF16();
|
|
public String readString() { return readUTF16(); }
|
|
public native byte[] readByteArray();
|
|
|
|
public native void writeBool(boolean v);
|
|
public native void writeInt8(byte v);
|
|
public native void writeInt16(short v);
|
|
public native void writeInt32(int v);
|
|
public native void writeInt64(long v);
|
|
public void writeInt(long v) { writeInt64(v); }
|
|
|
|
public native void writeFloat32(float v);
|
|
public native void writeFloat64(double v);
|
|
public native void writeUTF16(String v);
|
|
public void writeString(String v) { writeUTF16(v); }
|
|
public native void writeByteArray(byte[] v);
|
|
|
|
public void writeRef(Ref ref) {
|
|
tracker.inc(ref);
|
|
writeInt32(ref.refnum);
|
|
}
|
|
|
|
public Ref readRef() {
|
|
int refnum = readInt32();
|
|
return tracker.get(refnum);
|
|
}
|
|
|
|
static native void initSeq();
|
|
|
|
// Informs the Go ref tracker that Java is done with this ref.
|
|
static native void destroyRef(int refnum);
|
|
|
|
// createRef creates a Ref to a Java object.
|
|
public static Ref createRef(Seq.Object o) {
|
|
return tracker.createRef(o);
|
|
}
|
|
|
|
// sends a function invocation request to Go.
|
|
//
|
|
// Blocks until the function completes.
|
|
// If the request is for a method, the first element in src is
|
|
// a Ref to the receiver.
|
|
public static native void send(String descriptor, int code, Seq src, Seq dst);
|
|
|
|
// recv returns the next request from Go for a Java call.
|
|
static native void recv(Seq in, Receive params);
|
|
|
|
// recvRes sends the result of a Java call back to Go.
|
|
static native void recvRes(int handle, Seq out);
|
|
|
|
static final class Receive {
|
|
int refnum;
|
|
int code;
|
|
int handle;
|
|
}
|
|
|
|
protected void finalize() throws Throwable {
|
|
super.finalize();
|
|
free();
|
|
}
|
|
private native void free();
|
|
|
|
private static final ExecutorService receivePool = Executors.newCachedThreadPool();
|
|
|
|
// receive listens for callback requests from Go, invokes them on a thread
|
|
// pool and sends the responses.
|
|
public static void receive() {
|
|
Seq.Receive params = new Seq.Receive();
|
|
while (true) {
|
|
final Seq in = new Seq();
|
|
Seq.recv(in, params);
|
|
|
|
final int code = params.code;
|
|
final int handle = params.handle;
|
|
final int refnum = params.refnum;
|
|
|
|
if (code == -1) {
|
|
// Special signal from seq.FinalizeRef.
|
|
tracker.dec(refnum);
|
|
Seq out = new Seq();
|
|
Seq.recvRes(handle, out);
|
|
continue;
|
|
}
|
|
|
|
receivePool.execute(new Runnable() {
|
|
public void run() {
|
|
Ref r = tracker.get(refnum);
|
|
Seq out = new Seq();
|
|
r.obj.call(code, in, out);
|
|
Seq.recvRes(handle, out);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// An Object is a Java object that matches a Go object.
|
|
// The implementation of the object may be in either Java or Go,
|
|
// with a proxy instance in the other language passing calls
|
|
// through to the other language.
|
|
//
|
|
// Don't implement an Object directly. Instead, look for the
|
|
// generated abstract Stub.
|
|
public interface Object {
|
|
public Ref ref();
|
|
public void call(int code, Seq in, Seq out);
|
|
}
|
|
|
|
// A Ref is an object tagged with an integer for passing back and
|
|
// forth across the language boundary.
|
|
//
|
|
// A Ref may represent either an instance of a Java Object subclass,
|
|
// or an instance of a Go object. The explicit allocation of a Ref
|
|
// is used to pin Go object instances when they are passed to Java.
|
|
// The Go Seq library maintains a reference to the instance in a map
|
|
// keyed by the Ref number. When the JVM calls finalize, we ask Go
|
|
// to clear the entry in the map.
|
|
public static final class Ref {
|
|
// refnum < 0: Go object tracked by Java
|
|
// refnum > 0: Java object tracked by Go
|
|
int refnum;
|
|
|
|
int refcnt; // for Java obj: track how many times sent to Go.
|
|
|
|
public Seq.Object obj; // for Java obj: pointers to the Java obj.
|
|
|
|
private Ref(int refnum, Seq.Object o) {
|
|
this.refnum = refnum;
|
|
this.refcnt = 0;
|
|
this.obj = o;
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
if (refnum < 0) {
|
|
// Go object: signal Go to decrement the reference count.
|
|
Seq.destroyRef(refnum);
|
|
}
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
static final RefTracker tracker = new RefTracker();
|
|
|
|
static final class RefTracker {
|
|
private static final int REF_OFFSET = 42;
|
|
|
|
// use single Ref for null Seq.Object
|
|
private static final Ref nullRef = new Ref(REF_OFFSET - 1, null);
|
|
|
|
// Next Java object reference number.
|
|
//
|
|
// Reference numbers are positive for Java objects,
|
|
// and start, arbitrarily at a different offset to Go
|
|
// to make debugging by reading Seq hex a little easier.
|
|
private int next = REF_OFFSET; // next Java object ref
|
|
|
|
// Java objects that have been passed to Go. refnum -> Ref
|
|
// The Ref obj field is non-null.
|
|
// This map pins Java objects so they don't get GCed while the
|
|
// only reference to them is held by Go code.
|
|
private RefMap javaObjs = new RefMap();
|
|
|
|
// inc increments the reference count of a Java object when it
|
|
// is sent to Go.
|
|
synchronized void inc(Ref ref) {
|
|
int refnum = ref.refnum;
|
|
if (refnum <= 0) {
|
|
// We don't keep track of the Go object.
|
|
return;
|
|
}
|
|
if (refnum == nullRef.refnum) {
|
|
return;
|
|
}
|
|
// Count how many times this ref's Java object is passed to Go.
|
|
if (ref.refcnt == Integer.MAX_VALUE) {
|
|
throw new RuntimeException("refnum " + refnum + " overflow");
|
|
}
|
|
ref.refcnt++;
|
|
Ref obj = javaObjs.get(refnum);
|
|
if (obj == null) {
|
|
javaObjs.put(refnum, ref);
|
|
}
|
|
}
|
|
|
|
// dec decrements the reference count of a Java object when
|
|
// Go signals a corresponding proxy object is finalized.
|
|
// If the count reaches zero, the Java object is removed
|
|
// from the javaObjs map.
|
|
synchronized void dec(int refnum) {
|
|
if (refnum <= 0) {
|
|
// We don't keep track of the Go object.
|
|
// This must not happen.
|
|
log.severe("dec request for Go object "+ refnum);
|
|
return;
|
|
}
|
|
if (refnum == nullRef.refnum) {
|
|
return;
|
|
}
|
|
// Java objects are removed on request of Go.
|
|
Ref obj = javaObjs.get(refnum);
|
|
if (obj == null) {
|
|
throw new RuntimeException("referenced Java object is not found: refnum="+refnum);
|
|
}
|
|
obj.refcnt--;
|
|
if (obj.refcnt <= 0) {
|
|
javaObjs.remove(refnum);
|
|
}
|
|
}
|
|
|
|
synchronized Ref createRef(Seq.Object o) {
|
|
if (o == null) {
|
|
return nullRef;
|
|
}
|
|
if (next == Integer.MAX_VALUE) {
|
|
throw new RuntimeException("createRef overflow for " + o);
|
|
}
|
|
int refnum = next++;
|
|
Ref ref = new Ref(refnum, o);
|
|
javaObjs.put(refnum, ref);
|
|
return ref;
|
|
}
|
|
|
|
// get returns an existing Ref to either a Java or Go object.
|
|
// It may be the first time we have seen the Go object.
|
|
//
|
|
// TODO(crawshaw): We could cut down allocations for frequently
|
|
// sent Go objects by maintaining a map to weak references. This
|
|
// however, would require allocating two objects per reference
|
|
// instead of one. It also introduces weak references, the bane
|
|
// of any Java debugging session.
|
|
//
|
|
// When we have real code, examine the tradeoffs.
|
|
synchronized Ref get(int refnum) {
|
|
if (refnum > 0) {
|
|
if (refnum == nullRef.refnum) {
|
|
return nullRef;
|
|
}
|
|
Ref ref = javaObjs.get(refnum);
|
|
if (ref == null) {
|
|
throw new RuntimeException("unknown java Ref: "+refnum);
|
|
}
|
|
return ref;
|
|
} else {
|
|
// Go object.
|
|
return new Ref(refnum, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
// RefMap is a mapping of integers to Ref objects.
|
|
//
|
|
// The integers can be sparse. In Go this would be a map[int]*Ref.
|
|
private static final class RefMap {
|
|
private int next = 0;
|
|
private int live = 0;
|
|
private int[] keys = new int[16];
|
|
private Ref[] objs = new Ref[16];
|
|
|
|
RefMap() {}
|
|
|
|
Ref get(int key) {
|
|
int i = Arrays.binarySearch(keys, 0, next, key);
|
|
if (i >= 0) {
|
|
return objs[i];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void remove(int key) {
|
|
int i = Arrays.binarySearch(keys, 0, next, key);
|
|
if (i >= 0) {
|
|
if (objs[i] != null) {
|
|
objs[i] = null;
|
|
live--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void put(int key, Ref obj) {
|
|
if (obj == null) {
|
|
throw new RuntimeException("put a null ref (with key "+key+")");
|
|
}
|
|
int i = Arrays.binarySearch(keys, 0, next, key);
|
|
if (i >= 0) {
|
|
if (objs[i] == null) {
|
|
objs[i] = obj;
|
|
}
|
|
if (objs[i] != obj) {
|
|
throw new RuntimeException("replacing an existing ref (with key "+key+")");
|
|
}
|
|
return;
|
|
}
|
|
if (next >= keys.length) {
|
|
grow();
|
|
i = Arrays.binarySearch(keys, 0, next, key);
|
|
}
|
|
i = ~i;
|
|
if (i < next) {
|
|
// Insert, shift everything afterwards down.
|
|
System.arraycopy(keys, i, keys, i+1, next-i);
|
|
System.arraycopy(objs, i, objs, i+1, next-i);
|
|
}
|
|
keys[i] = key;
|
|
objs[i] = obj;
|
|
live++;
|
|
next++;
|
|
}
|
|
|
|
private void grow() {
|
|
// Compact and (if necessary) grow backing store.
|
|
int[] newKeys;
|
|
Ref[] newObjs;
|
|
int len = 2*roundPow2(live);
|
|
if (len > keys.length) {
|
|
newKeys = new int[keys.length*2];
|
|
newObjs = new Ref[objs.length*2];
|
|
} else {
|
|
newKeys = keys;
|
|
newObjs = objs;
|
|
}
|
|
|
|
int j = 0;
|
|
for (int i = 0; i < keys.length; i++) {
|
|
if (objs[i] != null) {
|
|
newKeys[j] = keys[i];
|
|
newObjs[j] = objs[i];
|
|
j++;
|
|
}
|
|
}
|
|
for (int i = j; i < newKeys.length; i++) {
|
|
newKeys[i] = 0;
|
|
newObjs[i] = null;
|
|
}
|
|
|
|
keys = newKeys;
|
|
objs = newObjs;
|
|
next = j;
|
|
|
|
if (live != next) {
|
|
throw new RuntimeException("bad state: live="+live+", next="+next);
|
|
}
|
|
}
|
|
|
|
private static int roundPow2(int x) {
|
|
int p = 1;
|
|
while (p < x) {
|
|
p *= 2;
|
|
}
|
|
return p;
|
|
}
|
|
}
|
|
}
|