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