/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- * vim: ts=4 sw=4 expandtab: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; public class NativeQueue { private static final String LOGTAG = "GeckoNativeQueue"; public interface State { boolean is(final State other); boolean isAtLeast(final State other); } private volatile State mState; private final State mReadyState; public NativeQueue(final State initial, final State ready) { mState = initial; mReadyState = ready; } public boolean isReady() { return getState().isAtLeast(mReadyState); } public State getState() { return mState; } public boolean setState(final State newState) { return checkAndSetState(null, newState); } public synchronized boolean checkAndSetState(final State expectedState, final State newState) { if (expectedState != null && !mState.is(expectedState)) { return false; } flushQueuedLocked(newState); mState = newState; return true; } private static class QueuedCall { public Method method; public Object target; public Object[] args; public State state; public QueuedCall( final Method method, final Object target, final Object[] args, final State state) { this.method = method; this.target = target; this.args = args; this.state = state; } } private static final int QUEUED_CALLS_COUNT = 16; /* package */ final ArrayList mQueue = new ArrayList<>(QUEUED_CALLS_COUNT); // Invoke the given Method and handle checked Exceptions. private static void invokeMethod(final Method method, final Object obj, final Object[] args) { try { method.setAccessible(true); method.invoke(obj, args); } catch (final IllegalAccessException e) { throw new IllegalStateException("Unexpected exception", e); } catch (final InvocationTargetException e) { throw new UnsupportedOperationException("Cannot make call", e.getCause()); } } // Queue a call to the given method. private void queueNativeCallLocked( final Class cls, final String methodName, final Object obj, final Object[] args, final State state) { final ArrayList> argTypes = new ArrayList<>(args.length); final ArrayList argValues = new ArrayList<>(args.length); for (int i = 0; i < args.length; i++) { if (args[i] instanceof Class) { argTypes.add((Class) args[i]); argValues.add(args[++i]); continue; } Class argType = args[i].getClass(); if (argType == Boolean.class) argType = Boolean.TYPE; else if (argType == Byte.class) argType = Byte.TYPE; else if (argType == Character.class) argType = Character.TYPE; else if (argType == Double.class) argType = Double.TYPE; else if (argType == Float.class) argType = Float.TYPE; else if (argType == Integer.class) argType = Integer.TYPE; else if (argType == Long.class) argType = Long.TYPE; else if (argType == Short.class) argType = Short.TYPE; argTypes.add(argType); argValues.add(args[i]); } final Method method; try { method = cls.getDeclaredMethod(methodName, argTypes.toArray(new Class[argTypes.size()])); } catch (final NoSuchMethodException e) { throw new IllegalArgumentException("Cannot find method", e); } if (!Modifier.isNative(method.getModifiers())) { // As a precaution, we disallow queuing non-native methods. Queuing non-native // methods is dangerous because the method could end up being called on either // the original thread or the Gecko thread depending on timing. Native methods // usually handle this by posting an event to the Gecko thread automatically, // but there is no automatic mechanism for non-native methods. throw new UnsupportedOperationException("Not allowed to queue non-native methods"); } if (getState().isAtLeast(state)) { invokeMethod(method, obj, argValues.toArray()); return; } mQueue.add(new QueuedCall(method, obj, argValues.toArray(), state)); } /** * Queue a call to the given instance method if the given current state does not satisfy the * isReady condition. * * @param obj Object that declares the instance method. * @param methodName Name of the instance method. * @param args Args to call the instance method with; to specify a parameter type, pass in a Class * instance first, followed by the value. */ public synchronized void queueUntilReady( final Object obj, final String methodName, final Object... args) { queueNativeCallLocked(obj.getClass(), methodName, obj, args, mReadyState); } /** * Queue a call to the given static method if the given current state does not satisfy the isReady * condition. * * @param cls Class that declares the static method. * @param methodName Name of the instance method. * @param args Args to call the instance method with; to specify a parameter type, pass in a Class * instance first, followed by the value. */ public synchronized void queueUntilReady( final Class cls, final String methodName, final Object... args) { queueNativeCallLocked(cls, methodName, null, args, mReadyState); } /** * Queue a call to the given instance method if the given current state does not satisfy the given * state. * * @param state The state in which the native call could be executed. * @param obj Object that declares the instance method. * @param methodName Name of the instance method. * @param args Args to call the instance method with; to specify a parameter type, pass in a Class * instance first, followed by the value. */ public synchronized void queueUntil( final State state, final Object obj, final String methodName, final Object... args) { queueNativeCallLocked(obj.getClass(), methodName, obj, args, state); } /** * Queue a call to the given static method if the given current state does not satisfy the given * state. * * @param state The state in which the native call could be executed. * @param cls Class that declares the static method. * @param methodName Name of the instance method. * @param args Args to call the instance method with; to specify a parameter type, pass in a Class * instance first, followed by the value. */ public synchronized void queueUntil( final State state, final Class cls, final String methodName, final Object... args) { queueNativeCallLocked(cls, methodName, null, args, state); } // Run all queued methods private void flushQueuedLocked(final State state) { int lastSkipped = -1; for (int i = 0; i < mQueue.size(); i++) { final QueuedCall call = mQueue.get(i); if (call == null) { // We already handled the call. continue; } if (!state.isAtLeast(call.state)) { // The call is not ready yet; skip it. lastSkipped = i; continue; } // Mark as handled. mQueue.set(i, null); invokeMethod(call.method, call.target, call.args); } if (lastSkipped < 0) { // We're done here; release the memory mQueue.clear(); } else if (lastSkipped < mQueue.size() - 1) { // We skipped some; free up null entries at the end, // but keep all the previous entries for later. mQueue.subList(lastSkipped + 1, mQueue.size()).clear(); } } public synchronized void reset(final State initial) { mQueue.clear(); mState = initial; } }