NativeQueue.java (8007B)
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- 2 * vim: ts=4 sw=4 expandtab: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 package org.mozilla.gecko; 8 9 import java.lang.reflect.InvocationTargetException; 10 import java.lang.reflect.Method; 11 import java.lang.reflect.Modifier; 12 import java.util.ArrayList; 13 14 public class NativeQueue { 15 private static final String LOGTAG = "GeckoNativeQueue"; 16 17 public interface State { 18 boolean is(final State other); 19 20 boolean isAtLeast(final State other); 21 } 22 23 private volatile State mState; 24 private final State mReadyState; 25 26 public NativeQueue(final State initial, final State ready) { 27 mState = initial; 28 mReadyState = ready; 29 } 30 31 public boolean isReady() { 32 return getState().isAtLeast(mReadyState); 33 } 34 35 public State getState() { 36 return mState; 37 } 38 39 public boolean setState(final State newState) { 40 return checkAndSetState(null, newState); 41 } 42 43 public synchronized boolean checkAndSetState(final State expectedState, final State newState) { 44 if (expectedState != null && !mState.is(expectedState)) { 45 return false; 46 } 47 flushQueuedLocked(newState); 48 mState = newState; 49 return true; 50 } 51 52 private static class QueuedCall { 53 public Method method; 54 public Object target; 55 public Object[] args; 56 public State state; 57 58 public QueuedCall( 59 final Method method, final Object target, final Object[] args, final State state) { 60 this.method = method; 61 this.target = target; 62 this.args = args; 63 this.state = state; 64 } 65 } 66 67 private static final int QUEUED_CALLS_COUNT = 16; 68 /* package */ final ArrayList<QueuedCall> mQueue = new ArrayList<>(QUEUED_CALLS_COUNT); 69 70 // Invoke the given Method and handle checked Exceptions. 71 private static void invokeMethod(final Method method, final Object obj, final Object[] args) { 72 try { 73 method.setAccessible(true); 74 method.invoke(obj, args); 75 } catch (final IllegalAccessException e) { 76 throw new IllegalStateException("Unexpected exception", e); 77 } catch (final InvocationTargetException e) { 78 throw new UnsupportedOperationException("Cannot make call", e.getCause()); 79 } 80 } 81 82 // Queue a call to the given method. 83 private void queueNativeCallLocked( 84 final Class<?> cls, 85 final String methodName, 86 final Object obj, 87 final Object[] args, 88 final State state) { 89 final ArrayList<Class<?>> argTypes = new ArrayList<>(args.length); 90 final ArrayList<Object> argValues = new ArrayList<>(args.length); 91 92 for (int i = 0; i < args.length; i++) { 93 if (args[i] instanceof Class) { 94 argTypes.add((Class<?>) args[i]); 95 argValues.add(args[++i]); 96 continue; 97 } 98 Class<?> argType = args[i].getClass(); 99 if (argType == Boolean.class) argType = Boolean.TYPE; 100 else if (argType == Byte.class) argType = Byte.TYPE; 101 else if (argType == Character.class) argType = Character.TYPE; 102 else if (argType == Double.class) argType = Double.TYPE; 103 else if (argType == Float.class) argType = Float.TYPE; 104 else if (argType == Integer.class) argType = Integer.TYPE; 105 else if (argType == Long.class) argType = Long.TYPE; 106 else if (argType == Short.class) argType = Short.TYPE; 107 argTypes.add(argType); 108 argValues.add(args[i]); 109 } 110 final Method method; 111 try { 112 method = cls.getDeclaredMethod(methodName, argTypes.toArray(new Class<?>[argTypes.size()])); 113 } catch (final NoSuchMethodException e) { 114 throw new IllegalArgumentException("Cannot find method", e); 115 } 116 117 if (!Modifier.isNative(method.getModifiers())) { 118 // As a precaution, we disallow queuing non-native methods. Queuing non-native 119 // methods is dangerous because the method could end up being called on either 120 // the original thread or the Gecko thread depending on timing. Native methods 121 // usually handle this by posting an event to the Gecko thread automatically, 122 // but there is no automatic mechanism for non-native methods. 123 throw new UnsupportedOperationException("Not allowed to queue non-native methods"); 124 } 125 126 if (getState().isAtLeast(state)) { 127 invokeMethod(method, obj, argValues.toArray()); 128 return; 129 } 130 131 mQueue.add(new QueuedCall(method, obj, argValues.toArray(), state)); 132 } 133 134 /** 135 * Queue a call to the given instance method if the given current state does not satisfy the 136 * isReady condition. 137 * 138 * @param obj Object that declares the instance method. 139 * @param methodName Name of the instance method. 140 * @param args Args to call the instance method with; to specify a parameter type, pass in a Class 141 * instance first, followed by the value. 142 */ 143 public synchronized void queueUntilReady( 144 final Object obj, final String methodName, final Object... args) { 145 queueNativeCallLocked(obj.getClass(), methodName, obj, args, mReadyState); 146 } 147 148 /** 149 * Queue a call to the given static method if the given current state does not satisfy the isReady 150 * condition. 151 * 152 * @param cls Class that declares the static method. 153 * @param methodName Name of the instance method. 154 * @param args Args to call the instance method with; to specify a parameter type, pass in a Class 155 * instance first, followed by the value. 156 */ 157 public synchronized void queueUntilReady( 158 final Class<?> cls, final String methodName, final Object... args) { 159 queueNativeCallLocked(cls, methodName, null, args, mReadyState); 160 } 161 162 /** 163 * Queue a call to the given instance method if the given current state does not satisfy the given 164 * state. 165 * 166 * @param state The state in which the native call could be executed. 167 * @param obj Object that declares the instance method. 168 * @param methodName Name of the instance method. 169 * @param args Args to call the instance method with; to specify a parameter type, pass in a Class 170 * instance first, followed by the value. 171 */ 172 public synchronized void queueUntil( 173 final State state, final Object obj, final String methodName, final Object... args) { 174 queueNativeCallLocked(obj.getClass(), methodName, obj, args, state); 175 } 176 177 /** 178 * Queue a call to the given static method if the given current state does not satisfy the given 179 * state. 180 * 181 * @param state The state in which the native call could be executed. 182 * @param cls Class that declares the static method. 183 * @param methodName Name of the instance method. 184 * @param args Args to call the instance method with; to specify a parameter type, pass in a Class 185 * instance first, followed by the value. 186 */ 187 public synchronized void queueUntil( 188 final State state, final Class<?> cls, final String methodName, final Object... args) { 189 queueNativeCallLocked(cls, methodName, null, args, state); 190 } 191 192 // Run all queued methods 193 private void flushQueuedLocked(final State state) { 194 int lastSkipped = -1; 195 for (int i = 0; i < mQueue.size(); i++) { 196 final QueuedCall call = mQueue.get(i); 197 if (call == null) { 198 // We already handled the call. 199 continue; 200 } 201 if (!state.isAtLeast(call.state)) { 202 // The call is not ready yet; skip it. 203 lastSkipped = i; 204 continue; 205 } 206 // Mark as handled. 207 mQueue.set(i, null); 208 209 invokeMethod(call.method, call.target, call.args); 210 } 211 if (lastSkipped < 0) { 212 // We're done here; release the memory 213 mQueue.clear(); 214 } else if (lastSkipped < mQueue.size() - 1) { 215 // We skipped some; free up null entries at the end, 216 // but keep all the previous entries for later. 217 mQueue.subList(lastSkipped + 1, mQueue.size()).clear(); 218 } 219 } 220 221 public synchronized void reset(final State initial) { 222 mQueue.clear(); 223 mState = initial; 224 } 225 }