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 27 public NativeObjRegistry(Class<T> theClass) { 28 this(theClass, false); 29 } 30 31 public NativeObjRegistry(Class<T> theClass, boolean debug) { 32 this(theClass.getSimpleName(), debug); 33 } 34 35 public NativeObjRegistry(String name) { 36 this(name, false); 37 } 38 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 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 */ 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(o))); 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 */ 103 public synchronized void 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(o)); 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 } 124 125 /** 126 * @deprecated Use {@link #unregister(long)} instead. 127 */ 128 @Deprecated 129 public synchronized void unregister(T removed) { 130 nativeObjToIdMap.inverse().remove(removed); 131 } 132 133 /** Retrieve the native object for given id. Throws if object with that id cannot be found */ 134 public synchronized T getNativeObject(long nativeId) { 135 T object = nativeObjToIdMap.get(nativeId); 136 if (object != null) { 137 return object; 138 } else { 139 throw new NullPointerException( 140 String.format( 141 "Could not find object with nativeId: %d. Currently registered ids: %s", 142 nativeId, nativeObjToIdMap.keySet())); 143 } 144 } 145 146 /** 147 * Similar to {@link #getNativeObject(long)} but returns null if object with given id cannot be 148 * found. 149 */ 150 public synchronized T peekNativeObject(long nativeId) { 151 return nativeObjToIdMap.get(nativeId); 152 } 153 154 /** WARNING -- dangerous! Call {@link #unregister(long)} instead! */ 155 public synchronized void clear() { 156 nextId = INITIAL_ID; 157 nativeObjToIdMap.clear(); 158 } 159 160 private static class DebugInfo { 161 final Trace registrationTrace; 162 final List<Trace> unregistrationTraces = new ArrayList<>(); 163 164 public DebugInfo(Trace trace) { 165 registrationTrace = trace; 166 } 167 } 168 169 private static class Trace extends Throwable { 170 private int apiLevel; 171 private boolean useLegacyResources; 172 173 private Trace(Object o) { 174 try { 175 Class<?> runtimeEnvClass = o.getClass().getClassLoader() 176 .loadClass("org.robolectric.RuntimeEnvironment"); 177 this.apiLevel = (int) runtimeEnvClass.getMethod("getApiLevel").invoke(null); 178 this.useLegacyResources = (boolean) runtimeEnvClass.getMethod("useLegacyResources") 179 .invoke(null); 180 } catch (Exception e) { 181 throw new RuntimeException(e); 182 } 183 } 184 185 private Trace(int apiLevel, boolean legacyResources) { 186 } 187 } 188 } 189