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