tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }