1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.layoutlib.bridge.impl;
18 
19 import com.android.layoutlib.bridge.util.Debug;
20 import com.android.layoutlib.bridge.util.SparseWeakArray;
21 
22 import android.annotation.Nullable;
23 import android.util.SparseArray;
24 
25 import java.io.PrintStream;
26 import java.lang.ref.WeakReference;
27 import java.util.HashSet;
28 import java.util.Set;
29 import java.util.concurrent.atomic.AtomicLong;
30 
31 
32 /**
33  * Manages native delegates.
34  *
35  * This is used in conjunction with layoublib_create: certain Android java classes are mere
36  * wrappers around a heavily native based implementation, and we need a way to run these classes
37  * in our Android Studio rendering framework without bringing all the native code from the Android
38  * platform.
39  *
40  * Thus we instruct layoutlib_create to modify the bytecode of these classes to replace their
41  * native methods by "delegate calls".
42  *
43  * For example, a native method android.graphics.Matrix.init(...) will actually become
44  * a call to android.graphics.Matrix_Delegate.init(...).
45  *
46  * The Android java classes that use native code uses an int (Java side) to reference native
47  * objects. This int is generally directly the pointer to the C structure counterpart.
48  * Typically a creation method will return such an int, and then this int will be passed later
49  * to a Java method to identify the C object to manipulate.
50  *
51  * Since we cannot use the Java object reference as the int directly, DelegateManager manages the
52  * int -> Delegate class link.
53  *
54  * Native methods usually always have the int as parameters. The first thing the delegate method
55  * will do is call {@link #getDelegate(long)} to get the Java object matching the int.
56  *
57  * Typical native init methods are returning a new int back to the Java class, so
58  * {@link #addNewDelegate(Object)} does the same.
59  *
60  * The JNI references are counted, so we do the same through a {@link WeakReference}. Because
61  * the Java object needs to count as a reference (even though it only holds an int), we use the
62  * following mechanism:
63  *
64  * - {@link #addNewDelegate(Object)} and {@link #removeJavaReferenceFor(long)} adds and removes
65  *   the delegate to/from a set. This set holds the reference and prevents the GC from reclaiming
66  *   the delegate.
67  *
68  * - {@link #addNewDelegate(Object)} also adds the delegate to a {@link SparseArray} that holds a
69  *   {@link WeakReference} to the delegate. This allows the delegate to be deleted automatically
70  *   when nothing references it. This means that any class that holds a delegate (except for the
71  *   Java main class) must not use the int but the Delegate class instead. The integers must
72  *   only be used in the API between the main Java class and the Delegate.
73  *
74  * @param <T> the delegate class to manage
75  */
76 public final class DelegateManager<T> {
77     private static final SparseWeakArray<Object> sDelegates = new SparseWeakArray<>();
78     /** Set used to store delegates when their main object holds a reference to them.
79      * This is to ensure that the WeakReference in the SparseWeakArray doesn't get GC'ed
80      * @see #addNewDelegate(Object)
81      * @see #removeJavaReferenceFor(long)
82      */
83     private static final Set<Object> sJavaReferences = new HashSet<>();
84     private static final AtomicLong sDelegateCounter = new AtomicLong(1);
85 
86     private final Class<T> mClass;
87 
DelegateManager(Class<T> theClass)88     public DelegateManager(Class<T> theClass) {
89         mClass = theClass;
90     }
91 
dump(PrintStream out)92     public synchronized static void dump(PrintStream out) {
93         for (Object reference : sJavaReferences) {
94             int idx = sDelegates.indexOfValue(reference);
95             out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName());
96         }
97         out.printf("\nTotal number of objects: %d\n", sJavaReferences.size());
98     }
99 
100     /**
101      * Returns the delegate from the given native int.
102      * <p>
103      * If the int is zero, then this will always return null.
104      * <p>
105      * If the int is non zero and the delegate is not found, this will throw an assert.
106      *
107      * @param native_object the native int.
108      * @return the delegate or null if not found.
109      */
110     @Nullable
getDelegate(long native_object)111     public T getDelegate(long native_object) {
112         if (native_object > 0) {
113             Object delegate;
114             synchronized (DelegateManager.class) {
115                 delegate = sDelegates.get(native_object);
116             }
117 
118             if (Debug.DEBUG) {
119                 if (delegate == null) {
120                     System.err.println("Unknown " + mClass.getSimpleName() + " with int " +
121                             native_object);
122                 }
123             }
124 
125             //noinspection unchecked
126             return (T)delegate;
127         }
128         return null;
129     }
130 
131     /**
132      * Adds a delegate to the manager and returns the native int used to identify it.
133      * @param newDelegate the delegate to add
134      * @return a unique native int to identify the delegate
135      */
addNewDelegate(T newDelegate)136     public long addNewDelegate(T newDelegate) {
137         long native_object = sDelegateCounter.getAndIncrement();
138         synchronized (DelegateManager.class) {
139             sDelegates.put(native_object, newDelegate);
140             // Only for development: assert !sJavaReferences.contains(newDelegate);
141             sJavaReferences.add(newDelegate);
142         }
143 
144         if (Debug.DEBUG) {
145             System.out.println(
146                     "New " + mClass.getSimpleName() + " " +
147                             "with int " +
148                             native_object);
149         }
150 
151         return native_object;
152     }
153 
154     /**
155      * Removes the main reference on the given delegate.
156      * @param native_object the native integer representing the delegate.
157      */
removeJavaReferenceFor(long native_object)158     public void removeJavaReferenceFor(long native_object) {
159         synchronized (DelegateManager.class) {
160             T delegate = getDelegate(native_object);
161 
162             if (Debug.DEBUG) {
163                 System.out.println("Removing main Java ref on " + mClass.getSimpleName() +
164                         " with int " + native_object);
165             }
166 
167             sJavaReferences.remove(delegate);
168         }
169     }
170 }
171