Home | History | Annotate | Download | only in android
      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