github.com/danbrough/mobile@v0.0.3-beta03/bind/java/Seq.java (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package go;
     6  
     7  //import android.content.Context;
     8  
     9  import java.lang.ref.PhantomReference;
    10  import java.lang.ref.ReferenceQueue;
    11  import java.util.Arrays;
    12  import java.util.Collection;
    13  import java.util.Collections;
    14  import java.util.HashSet;
    15  import java.util.IdentityHashMap;
    16  import java.util.logging.Logger;
    17  
    18  // Seq is a sequence of machine-dependent encoded values.
    19  // Used by automatically generated language bindings to talk to Go.
    20  public class Seq {
    21  	private static final Logger log = Logger.getLogger("GoSeq");
    22  
    23  	// also known to bind/seq/ref.go and bind/objc/seq_darwin.m
    24  	private static final int NULL_REFNUM = 41;
    25  
    26  	// use single Ref for null Object
    27  	public static final Ref nullRef = new Ref(NULL_REFNUM, null);
    28  
    29  	// The singleton GoRefQueue
    30  	private static final GoRefQueue goRefQueue = new GoRefQueue();
    31  
    32  	static {
    33  		//nativeLoader.loadLibrary("gojni");
    34  		init();
    35  		Universe.touch();
    36  	}
    37  
    38  	// setContext sets the context in the go-library to be used in RunOnJvm.
    39  	/*public static void setContext(Context context) {
    40  		setContext((java.lang.Object)context);
    41  	}*/
    42  
    43  	private static native void init();
    44  
    45  	// Empty method to run class initializer
    46  	public static void touch() {
    47  	}
    48  
    49  	private Seq() {
    50  	}
    51  
    52  	// ctx is an android.context.Context.
    53  	public static native void setContext(java.lang.Object ctx);
    54  
    55  	public static void incRefnum(int refnum) {
    56  		tracker.incRefnum(refnum);
    57  	}
    58  
    59  	// incRef increments the reference count of Java objects.
    60  	// For proxies for Go objects, it calls into the Proxy method
    61  	// incRefnum() to make sure the Go reference count is positive
    62  	// even if the Proxy is garbage collected and its Ref is finalized.
    63  	public static int incRef(Object o) {
    64  		return tracker.inc(o);
    65  	}
    66  
    67  	public static int incGoObjectRef(GoObject o) {
    68  		return o.incRefnum();
    69  	}
    70  
    71  	// trackGoRef tracks a Go reference and decrements its refcount
    72  	// when the given GoObject wrapper is garbage collected.
    73  	//
    74  	// TODO(crawshaw): We could cut down allocations for frequently
    75  	// sent Go objects by maintaining a map to weak references. This
    76  	// however, would require allocating two objects per reference
    77  	// instead of one. It also introduces weak references, the bane
    78  	// of any Java debugging session.
    79  	//
    80  	// When we have real code, examine the tradeoffs.
    81  	public static void trackGoRef(int refnum, GoObject obj) {
    82  		if (refnum > 0) {
    83  			throw new RuntimeException("trackGoRef called with Java refnum " + refnum);
    84  		}
    85  		goRefQueue.track(refnum, obj);
    86  	}
    87  
    88  	public static Ref getRef(int refnum) {
    89  		return tracker.get(refnum);
    90  	}
    91  
    92  	// Increment the Go reference count before sending over a refnum.
    93  	// The ref parameter is only used to make sure the referenced
    94  	// object is not garbage collected before Go increments the
    95  	// count. It's the equivalent of Go's runtime.KeepAlive.
    96  	public static native void incGoRef(int refnum, GoObject ref);
    97  
    98  	// Informs the Go ref tracker that Java is done with this refnum.
    99  	static native void destroyRef(int refnum);
   100  
   101  	// decRef is called from seq.FinalizeRef
   102  	static void decRef(int refnum) {
   103  		tracker.dec(refnum);
   104  	}
   105  
   106  	// A GoObject is a Java class implemented in Go. When a GoObject
   107  	// is passed to Go, it is wrapped in a Go proxy, to make it behave
   108  	// the same as passing a regular Java class.
   109  	public interface GoObject {
   110  		// Increment refcount and return the refnum of the proxy.
   111  		//
   112  		// The Go reference count need to be bumped while the
   113  		// refnum is passed to Go, to avoid finalizing and
   114  		// invalidating it before being translated on the Go side.
   115  		int incRefnum();
   116  	}
   117  
   118  	// A Proxy is a Java object that proxies a Go object. Proxies, unlike
   119  	// GoObjects, are unwrapped to their Go counterpart when deserialized
   120  	// in Go.
   121  	public interface Proxy extends GoObject {
   122  	}
   123  
   124  	// A Ref represents an instance of a Java object passed back and forth
   125  	// across the language boundary.
   126  	public static final class Ref {
   127  		public final int refnum;
   128  
   129  		private int refcnt;  // Track how many times sent to Go.
   130  
   131  		public final Object obj;  // The referenced Java obj.
   132  
   133  		Ref(int refnum, Object o) {
   134  			if (refnum < 0) {
   135  				throw new RuntimeException("Ref instantiated with a Go refnum " + refnum);
   136  			}
   137  			this.refnum = refnum;
   138  			this.refcnt = 0;
   139  			this.obj = o;
   140  		}
   141  
   142  		void inc() {
   143  			// Count how many times this ref's Java object is passed to Go.
   144  			if (refcnt == Integer.MAX_VALUE) {
   145  				throw new RuntimeException("refnum " + refnum + " overflow");
   146  			}
   147  			refcnt++;
   148  		}
   149  	}
   150  
   151  	static final RefTracker tracker = new RefTracker();
   152  
   153  	static final class RefTracker {
   154  		private static final int REF_OFFSET = 42;
   155  
   156  		// Next Java object reference number.
   157  		//
   158  		// Reference numbers are positive for Java objects,
   159  		// and start, arbitrarily at a different offset to Go
   160  		// to make debugging by reading Seq hex a little easier.
   161  		private int next = REF_OFFSET; // next Java object ref
   162  
   163  		// Java objects that have been passed to Go. refnum -> Ref
   164  		// The Ref obj field is non-null.
   165  		// This map pins Java objects so they don't get GCed while the
   166  		// only reference to them is held by Go code.
   167  		private final RefMap javaObjs = new RefMap();
   168  
   169  		// Java objects to refnum
   170  		private final IdentityHashMap<Object, Integer> javaRefs = new IdentityHashMap<>();
   171  
   172  		// inc increments the reference count of a Java object when it
   173  		// is sent to Go. inc returns the refnum for the object.
   174  		synchronized int inc(Object o) {
   175  			if (o == null) {
   176  				return NULL_REFNUM;
   177  			}
   178  			if (o instanceof Proxy) {
   179  				return ((Proxy) o).incRefnum();
   180  			}
   181  			Integer refnumObj = javaRefs.get(o);
   182  			if (refnumObj == null) {
   183  				if (next == Integer.MAX_VALUE) {
   184  					throw new RuntimeException("createRef overflow for " + o);
   185  				}
   186  				refnumObj = next++;
   187  				javaRefs.put(o, refnumObj);
   188  			}
   189  			int refnum = refnumObj;
   190  			Ref ref = javaObjs.get(refnum);
   191  			if (ref == null) {
   192  				ref = new Ref(refnum, o);
   193  				javaObjs.put(refnum, ref);
   194  			}
   195  			ref.inc();
   196  			return refnum;
   197  		}
   198  
   199  		synchronized void incRefnum(int refnum) {
   200  			Ref ref = javaObjs.get(refnum);
   201  			if (ref == null) {
   202  				throw new RuntimeException("referenced Java object is not found: refnum=" + refnum);
   203  			}
   204  			ref.inc();
   205  		}
   206  
   207  		// dec decrements the reference count of a Java object when
   208  		// Go signals a corresponding proxy object is finalized.
   209  		// If the count reaches zero, the Java object is removed
   210  		// from the javaObjs map.
   211  		synchronized void dec(int refnum) {
   212  			if (refnum <= 0) {
   213  				// We don't keep track of the Go object.
   214  				// This must not happen.
   215  				log.severe("dec request for Go object " + refnum);
   216  				return;
   217  			}
   218  			if (refnum == Seq.nullRef.refnum) {
   219  				return;
   220  			}
   221  			// Java objects are removed on request of Go.
   222  			Ref obj = javaObjs.get(refnum);
   223  			if (obj == null) {
   224  				throw new RuntimeException("referenced Java object is not found: refnum=" + refnum);
   225  			}
   226  			obj.refcnt--;
   227  			if (obj.refcnt <= 0) {
   228  				javaObjs.remove(refnum);
   229  				javaRefs.remove(obj.obj);
   230  			}
   231  		}
   232  
   233  		// get returns an existing Ref to a Java object.
   234  		synchronized Ref get(int refnum) {
   235  			if (refnum < 0) {
   236  				throw new RuntimeException("ref called with Go refnum " + refnum);
   237  			}
   238  			if (refnum == NULL_REFNUM) {
   239  				return nullRef;
   240  			}
   241  			Ref ref = javaObjs.get(refnum);
   242  			if (ref == null) {
   243  				throw new RuntimeException("unknown java Ref: " + refnum);
   244  			}
   245  			return ref;
   246  		}
   247  	}
   248  
   249  	// GoRefQueue is a queue of GoRefs that are no longer live. An internal thread
   250  	// processes the queue and decrement the reference count on the Go side.
   251  	static class GoRefQueue extends ReferenceQueue<GoObject> {
   252  		// The set of tracked GoRefs. If we don't hold on to the GoRef instances, the Java GC
   253  		// will not add them to the queue when their referents are reclaimed.
   254  		private final Collection<GoRef> refs = Collections.synchronizedCollection(new HashSet<GoRef>());
   255  
   256  		void track(int refnum, GoObject obj) {
   257  			refs.add(new GoRef(refnum, obj, this));
   258  		}
   259  
   260  		GoRefQueue() {
   261  			Thread daemon = new Thread(new Runnable() {
   262  				@Override
   263  				public void run() {
   264  					while (true) {
   265  						try {
   266  							GoRef ref = (GoRef) remove();
   267  							refs.remove(ref);
   268  							destroyRef(ref.refnum);
   269  							ref.clear();
   270  						} catch (InterruptedException e) {
   271  							// Ignore
   272  						}
   273  					}
   274  				}
   275  			});
   276  			daemon.setDaemon(true);
   277  			daemon.setName("GoRefQueue Finalizer Thread");
   278  			daemon.start();
   279  		}
   280  	}
   281  
   282  	// A GoRef is a PhantomReference to a Java proxy for a Go object.
   283  	// GoRefs are enqueued to the singleton GoRefQueue when no longer live,
   284  	// so the corresponding reference count can be decremented.
   285  	static class GoRef extends PhantomReference<GoObject> {
   286  		final int refnum;
   287  
   288  		GoRef(int refnum, GoObject obj, GoRefQueue q) {
   289  			super(obj, q);
   290  			if (refnum > 0) {
   291  				throw new RuntimeException("GoRef instantiated with a Java refnum " + refnum);
   292  			}
   293  			this.refnum = refnum;
   294  		}
   295  	}
   296  
   297  	// RefMap is a mapping of integers to Ref objects.
   298  	//
   299  	// The integers can be sparse. In Go this would be a map[int]*Ref.
   300  	static final class RefMap {
   301  		private int next = 0;
   302  		private int live = 0;
   303  		private int[] keys = new int[16];
   304  		private Ref[] objs = new Ref[16];
   305  
   306  		RefMap() {
   307  		}
   308  
   309  		Ref get(int key) {
   310  			int i = Arrays.binarySearch(keys, 0, next, key);
   311  			if (i >= 0) {
   312  				return objs[i];
   313  			}
   314  			return null;
   315  		}
   316  
   317  		void remove(int key) {
   318  			int i = Arrays.binarySearch(keys, 0, next, key);
   319  			if (i >= 0) {
   320  				if (objs[i] != null) {
   321  					objs[i] = null;
   322  					live--;
   323  				}
   324  			}
   325  		}
   326  
   327  		void put(int key, Ref obj) {
   328  			if (obj == null) {
   329  				throw new RuntimeException("put a null ref (with key " + key + ")");
   330  			}
   331  			int i = Arrays.binarySearch(keys, 0, next, key);
   332  			if (i >= 0) {
   333  				if (objs[i] == null) {
   334  					objs[i] = obj;
   335  					live++;
   336  				}
   337  				if (objs[i] != obj) {
   338  					throw new RuntimeException("replacing an existing ref (with key " + key + ")");
   339  				}
   340  				return;
   341  			}
   342  			if (next >= keys.length) {
   343  				grow();
   344  				i = Arrays.binarySearch(keys, 0, next, key);
   345  			}
   346  			i = ~i;
   347  			if (i < next) {
   348  				// Insert, shift everything afterwards down.
   349  				System.arraycopy(keys, i, keys, i + 1, next - i);
   350  				System.arraycopy(objs, i, objs, i + 1, next - i);
   351  			}
   352  			keys[i] = key;
   353  			objs[i] = obj;
   354  			live++;
   355  			next++;
   356  		}
   357  
   358  		private void grow() {
   359  			// Compact and (if necessary) grow backing store.
   360  			int[] newKeys;
   361  			Ref[] newObjs;
   362  			int len = 2 * roundPow2(live);
   363  			if (len > keys.length) {
   364  				newKeys = new int[keys.length * 2];
   365  				newObjs = new Ref[objs.length * 2];
   366  			} else {
   367  				newKeys = keys;
   368  				newObjs = objs;
   369  			}
   370  
   371  			int j = 0;
   372  			for (int i = 0; i < keys.length; i++) {
   373  				if (objs[i] != null) {
   374  					newKeys[j] = keys[i];
   375  					newObjs[j] = objs[i];
   376  					j++;
   377  				}
   378  			}
   379  			for (int i = j; i < newKeys.length; i++) {
   380  				newKeys[i] = 0;
   381  				newObjs[i] = null;
   382  			}
   383  
   384  			keys = newKeys;
   385  			objs = newObjs;
   386  			next = j;
   387  
   388  			if (live != next) {
   389  				throw new RuntimeException("bad state: live=" + live + ", next=" + next);
   390  			}
   391  		}
   392  
   393  		private static int roundPow2(int x) {
   394  			int p = 1;
   395  			while (p < x) {
   396  				p *= 2;
   397  			}
   398  			return p;
   399  		}
   400  	}
   401  }