blob: 3957d7c54583c82974f0a11bdf597c797af150b9 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chromoting;
import android.os.Handler;
import android.os.Looper;
import java.util.HashSet;
/**
* A thread-safe event queue which provides both {@link #add} and {@link #remove} functions with
* O(log(n)) time complexity, and a {@link #raise} function in the derived class
* {@link Event.Raisable} to execute all queued callbacks.
*
* @param <ParamT> The parameter used in {@link ParameterRunnable} callback.
*/
public class Event<ParamT> {
/** A runnable with parameter. */
public static interface ParameterRunnable<ParamT> {
void run(ParamT p);
}
/** A callback with parameter. */
public static interface ParameterCallback<ReturnT, ParamT> {
ReturnT run(ParamT p);
}
/**
* An event provider version of {@link Event} implementation, provides {@link #raise} function
* to execute appended {@link ParameterRunnable}, and {@link #clear} function to clear all
* appended callbacks.
*/
public static class Raisable<ParamT> extends Event<ParamT> {
/** Clears all appended callbacks */
public void clear() {
synchronized (mSet) {
mSet.clear();
}
}
/**
* Executes all queued {@link ParameterRunnable} with |parameter|, returns an integer of
* total callbacks executed. Note, if an 'add' function call is executing concurrently
* with the 'raise' function call, the newly added object may not be executed.
*/
public int raise(ParamT parameter) {
Object[] array;
synchronized (mSet) {
array = mSet.toArray();
}
int count = 0;
for (Object obj : array) {
execute(obj, parameter);
count++;
}
return count;
}
/** Executes |obj| as ParameterRunnable<ParamT> with |parameter| as ParamT. */
@SuppressWarnings("unchecked")
private void execute(Object obj, ParamT parameter) {
ParameterRunnable<ParamT> runnable = (ParameterRunnable<ParamT>) obj;
runnable.run(parameter);
}
}
/**
* A {@link Raisable} which always executes the newly added {@link ParameterRunnable} with the
* parameter sent to the last {@link #raise} function call. If the event has not been raised,
* this class has consistent behavior as {@link Raisable}. <br>
* This class is useful for some one-time events, such as RenderStub.onClientSizeChanged(). A
* later attached runnable will never be able to get the event. <br>
* The {@link ParameterRunnable} will be executed in the thread in which {@link #add}
* function is called if the event has been raised before. If there is not a {@link Looper} on
* current thread, the runnable will be executed immediately. Otherwise, a task will be posted
* to current looper. Note that it may be executed twice on different threads with the exactly
* same parameter.
*/
public static final class PromisedRaisable<ParamT> extends Raisable<ParamT> {
private boolean mRaised;
private ParamT mLastParameter;
@Override
public Object add(final ParameterRunnable<ParamT> runnable) {
Object result = super.add(runnable);
if (result != null) {
synchronized (mSet) {
if (mRaised) {
if (Looper.myLooper() == null) {
runnable.run(mLastParameter);
} else {
// We should always use the latest parameter, otherwise the order of
// parameters current runnable gets may not be correct.
final PromisedRaisable<ParamT> me = this;
Handler h = new Handler(Looper.myLooper());
h.post(new Runnable() {
@Override
public void run() {
runnable.run(me.mLastParameter);
}
});
}
}
}
}
return result;
}
@Override
public int raise(ParamT parameter) {
synchronized (mSet) {
mRaised = true;
mLastParameter = parameter;
}
return super.raise(parameter);
}
}
/**
* A self removable {@link ParameterRunner}, uses a boolean {@link ParameterCallback} to decide
* whether removes self from {@link Event} or not.
*/
private static final class SelfRemovableParameterRunnable<ParamT>
implements ParameterRunnable<ParamT> {
private final ParameterCallback<Boolean, ParamT> mCallback;
private final Event<ParamT> mOwner;
// This lock is used to make sure mEvent is correctly set before remove in run function.
// i.e. mOwner.add and assigment of mEvent need to be atomic.
private final Object mLock;
private final Object mEvent;
private SelfRemovableParameterRunnable(Event<ParamT> owner,
ParameterCallback<Boolean, ParamT> callback) {
Preconditions.notNull(callback);
mCallback = callback;
mOwner = owner;
mLock = new Object();
synchronized (mLock) {
mEvent = mOwner.add(this);
}
Preconditions.notNull(mEvent);
}
public final void run(ParamT p) {
synchronized (mLock) {
if (mOwner.contains(mEvent)) {
if (!mCallback.run(p)) {
// Event.Raisable.clear may be called in a different thread.
mOwner.remove(mEvent);
}
}
}
}
}
protected final HashSet<ParameterRunnable<ParamT>> mSet;
public Event() {
mSet = new HashSet<>();
}
/**
* Adds a {@link ParameterRunnable} object into current instance, returns an object which can
* be used to {@link remove} the runnable from this instance. If |runnable| is null or it has
* been added to this instance already, this function returns null.
*/
public Object add(ParameterRunnable<ParamT> runnable) {
if (runnable == null) {
return null;
}
synchronized (mSet) {
if (mSet.add(runnable)) {
return runnable;
}
}
return null;
}
/**
* Adds a self removable {@link ParameterRunnable} object into current instance; the runnable
* will remove itself from {@link Event} instance when callback returns false.
*/
public void addSelfRemovable(ParameterCallback<Boolean, ParamT> callback) {
Preconditions.notNull(callback);
// SelfRemovableParameterRunner is self-contained, i.e. consumers do not need to have a
// reference of this instance, but all the logic is in new function.
new SelfRemovableParameterRunnable<ParamT>(this, callback);
}
/**
* Removes an object that was previously returned by {@link add}. Returns false if the object
* is not in the event queue, or not returned by {@link add} function.
*/
public boolean remove(Object obj) {
synchronized (mSet) {
return mSet.remove(obj);
}
}
/**
* Returns whether current instance contains the object that was previously returned by
* {@link add}.
*/
public boolean contains(Object obj) {
synchronized (mSet) {
return mSet.contains(obj);
}
}
/**
* Returns the total count of runnables attached to current instance.
*/
public int size() {
synchronized (mSet) {
return mSet.size();
}
}
/**
* Returns true if there is no runnable attached to current instance.
*/
public boolean isEmpty() {
synchronized (mSet) {
return mSet.isEmpty();
}
}
}