1/*
2 * Copyright (C) 2013 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.inputmethod.latin.utils;
18
19import java.util.Queue;
20import java.util.concurrent.ArrayBlockingQueue;
21import java.util.concurrent.ConcurrentLinkedQueue;
22import java.util.concurrent.ThreadPoolExecutor;
23import java.util.concurrent.TimeUnit;
24
25/**
26 * An object that executes submitted tasks using a thread.
27 */
28public class PrioritizedSerialExecutor {
29    public static final String TAG = PrioritizedSerialExecutor.class.getSimpleName();
30
31    private final Object mLock = new Object();
32
33    private final Queue<Runnable> mTasks;
34    private final Queue<Runnable> mPrioritizedTasks;
35    private boolean mIsShutdown;
36    private final ThreadPoolExecutor mThreadPoolExecutor;
37
38    // The task which is running now.
39    private Runnable mActive;
40
41    public PrioritizedSerialExecutor() {
42        mTasks = new ConcurrentLinkedQueue<Runnable>();
43        mPrioritizedTasks = new ConcurrentLinkedQueue<Runnable>();
44        mIsShutdown = false;
45        mThreadPoolExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
46                0 /* keepAliveTime */, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1));
47    }
48
49    /**
50     * Clears all queued tasks.
51     */
52    public void clearAllTasks() {
53        synchronized(mLock) {
54            mTasks.clear();
55            mPrioritizedTasks.clear();
56        }
57    }
58
59    /**
60     * Enqueues the given task into the task queue.
61     * @param r the enqueued task
62     */
63    public void execute(final Runnable r) {
64        synchronized(mLock) {
65            if (!mIsShutdown) {
66                mTasks.offer(new Runnable() {
67                    @Override
68                    public void run() {
69                        try {
70                            r.run();
71                        } finally {
72                            scheduleNext();
73                        }
74                    }
75                });
76                if (mActive == null) {
77                    scheduleNext();
78                }
79            }
80        }
81    }
82
83    /**
84     * Enqueues the given task into the prioritized task queue.
85     * @param r the enqueued task
86     */
87    public void executePrioritized(final Runnable r) {
88        synchronized(mLock) {
89            if (!mIsShutdown) {
90                mPrioritizedTasks.offer(new Runnable() {
91                    @Override
92                    public void run() {
93                        try {
94                            r.run();
95                        } finally {
96                            scheduleNext();
97                        }
98                    }
99                });
100                if (mActive == null) {
101                    scheduleNext();
102                }
103            }
104        }
105    }
106
107    private boolean fetchNextTasksLocked() {
108        mActive = mPrioritizedTasks.poll();
109        if (mActive == null) {
110            mActive = mTasks.poll();
111        }
112        return mActive != null;
113    }
114
115    private void scheduleNext() {
116        synchronized(mLock) {
117            if (fetchNextTasksLocked()) {
118                mThreadPoolExecutor.execute(mActive);
119            }
120        }
121    }
122
123    public void remove(final Runnable r) {
124        synchronized(mLock) {
125            mTasks.remove(r);
126            mPrioritizedTasks.remove(r);
127        }
128    }
129
130    public void replaceAndExecute(final Runnable oldTask, final Runnable newTask) {
131        synchronized(mLock) {
132            if (oldTask != null) remove(oldTask);
133            execute(newTask);
134        }
135    }
136
137    public void shutdown() {
138        synchronized(mLock) {
139            mIsShutdown = true;
140        }
141    }
142
143    public boolean isTerminated() {
144        synchronized(mLock) {
145            if (!mIsShutdown) {
146                return false;
147            }
148            return mPrioritizedTasks.isEmpty() && mTasks.isEmpty() && mActive == null;
149        }
150    }
151}
152