| // 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; |
| } |
| } |
| } |