1 package org.robolectric.res.android; 2 3 import static com.google.common.base.Preconditions.checkNotNull; 4 5 import com.google.common.collect.BiMap; 6 import com.google.common.collect.HashBiMap; 7 import java.util.ArrayList; 8 import java.util.HashMap; 9 import java.util.List; 10 import java.util.Map; 11 12 /** 13 * A unique id per object registry. Used to emulate android platform behavior of storing a long 14 * which represents a pointer to an object. 15 */ 16 public class NativeObjRegistry<T> { 17 18 private static final int INITIAL_ID = 1; 19 20 private final String name; 21 private final boolean debug; 22 private final BiMap<Long, T> nativeObjToIdMap = HashBiMap.create(); 23 private final Map<Long, DebugInfo> idToDebugInfoMap; 24 25 private long nextId = INITIAL_ID; 26 NativeObjRegistry(Class<T> theClass)27 public NativeObjRegistry(Class<T> theClass) { 28 this(theClass, false); 29 } 30 NativeObjRegistry(Class<T> theClass, boolean debug)31 public NativeObjRegistry(Class<T> theClass, boolean debug) { 32 this(theClass.getSimpleName(), debug); 33 } 34 NativeObjRegistry(String name)35 public NativeObjRegistry(String name) { 36 this(name, false); 37 } 38 NativeObjRegistry(String name, boolean debug)39 public NativeObjRegistry(String name, boolean debug) { 40 this.name = name; 41 this.debug = debug; 42 this.idToDebugInfoMap = debug ? new HashMap<>() : null; 43 } 44 45 /** 46 * Retrieve the native id for given object. Assigns a new unique id to the object if not 47 * previously registered. 48 * 49 * @deprecated Use {@link #register(Object)} instead. 50 */ 51 @Deprecated getNativeObjectId(T o)52 public synchronized long getNativeObjectId(T o) { 53 checkNotNull(o); 54 Long nativeId = nativeObjToIdMap.inverse().get(o); 55 if (nativeId == null) { 56 nativeId = nextId; 57 if (debug) { 58 System.out.printf("NativeObjRegistry %s: register %d -> %s%n", name, nativeId, o); 59 } 60 nativeObjToIdMap.put(nativeId, o); 61 nextId++; 62 } 63 return nativeId; 64 } 65 66 /** 67 * Register and assign a new unique native id for given object (representing a C memory pointer). 68 * 69 * @throws IllegalStateException if the object was previously registered 70 */ register(T o)71 public synchronized long register(T o) { 72 checkNotNull(o); 73 Long nativeId = nativeObjToIdMap.inverse().get(o); 74 if (nativeId != null) { 75 if (debug) { 76 DebugInfo debugInfo = idToDebugInfoMap.get(nativeId); 77 if (debugInfo != null) { 78 System.out.printf( 79 "NativeObjRegistry %s: register %d -> %s already registered:%n", name, nativeId, o); 80 debugInfo.registrationTrace.printStackTrace(System.out); 81 } 82 } 83 throw new IllegalStateException("Object was previously registered with id " + nativeId); 84 } 85 86 nativeId = nextId; 87 if (debug) { 88 System.out.printf("NativeObjRegistry %s: register %d -> %s%n", name, nativeId, o); 89 idToDebugInfoMap.put(nativeId, new DebugInfo(new Trace())); 90 } 91 nativeObjToIdMap.put(nativeId, o); 92 nextId++; 93 return nativeId; 94 } 95 96 /** 97 * Unregister an object previously registered with {@link #register(Object)}. 98 * 99 * @param nativeId the unique id (representing a C memory pointer) of the object to unregister. 100 * @throws IllegalStateException if the object was never registered, or was previously 101 * unregistered. 102 */ unregister(long nativeId)103 public synchronized T unregister(long nativeId) { 104 T o = nativeObjToIdMap.remove(nativeId); 105 if (debug) { 106 System.out.printf("NativeObjRegistry %s: unregister %d -> %s%n", name, nativeId, o); 107 new RuntimeException("unregister debug").printStackTrace(System.out); 108 } 109 if (o == null) { 110 if (debug) { 111 DebugInfo debugInfo = idToDebugInfoMap.get(nativeId); 112 debugInfo.unregistrationTraces.add(new Trace()); 113 if (debugInfo.unregistrationTraces.size() > 1) { 114 System.out.format("NativeObjRegistry %s: Too many unregistrations:%n", name); 115 for (Trace unregistration : debugInfo.unregistrationTraces) { 116 unregistration.printStackTrace(System.out); 117 } 118 } 119 } 120 throw new IllegalStateException( 121 nativeId + " has already been removed (or was never registered)"); 122 } 123 return o; 124 } 125 126 /** 127 * @deprecated Use {@link #unregister(long)} instead. 128 */ 129 @Deprecated unregister(T removed)130 public synchronized void unregister(T removed) { 131 nativeObjToIdMap.inverse().remove(removed); 132 } 133 134 /** Retrieve the native object for given id. Throws if object with that id cannot be found */ getNativeObject(long nativeId)135 public synchronized T getNativeObject(long nativeId) { 136 T object = nativeObjToIdMap.get(nativeId); 137 if (object != null) { 138 return object; 139 } else { 140 throw new NullPointerException( 141 String.format( 142 "Could not find object with nativeId: %d. Currently registered ids: %s", 143 nativeId, nativeObjToIdMap.keySet())); 144 } 145 } 146 147 /** 148 * Updates the native object for the given id. 149 * 150 * @throws IllegalStateException if no object was registered with the given id before 151 */ update(long nativeId, T o)152 public synchronized void update(long nativeId, T o) { 153 T previous = nativeObjToIdMap.get(nativeId); 154 if (previous == null) { 155 throw new IllegalStateException("Native id " + nativeId + " was never registered"); 156 } 157 if (debug) { 158 System.out.printf("NativeObjRegistry %s: update %d -> %s%n", name, nativeId, o); 159 idToDebugInfoMap.put(nativeId, new DebugInfo(new Trace())); 160 } 161 nativeObjToIdMap.put(nativeId, o); 162 } 163 164 /** 165 * Similar to {@link #getNativeObject(long)} but returns null if object with given id cannot be 166 * found. 167 */ peekNativeObject(long nativeId)168 public synchronized T peekNativeObject(long nativeId) { 169 return nativeObjToIdMap.get(nativeId); 170 } 171 172 /** WARNING -- dangerous! Call {@link #unregister(long)} instead! */ clear()173 public synchronized void clear() { 174 nextId = INITIAL_ID; 175 nativeObjToIdMap.clear(); 176 } 177 178 private static class DebugInfo { 179 final Trace registrationTrace; 180 final List<Trace> unregistrationTraces = new ArrayList<>(); 181 DebugInfo(Trace trace)182 public DebugInfo(Trace trace) { 183 registrationTrace = trace; 184 } 185 } 186 187 private static class Trace extends Throwable { 188 Trace()189 private Trace() {} 190 } 191 } 192