• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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