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 }