summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/gallery/BaseCancelable.java
blob: d5566a839180efc3de98ea7686a87f05efd96b26 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.camera.gallery;

import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;

/**
 * An abstract class for the interface <code>Cancelable</code>. Subclass can
 * simply override the <code>execute()</code> function to provide an
 * implementation of <code>Cancelable</code>.
 */
public abstract class BaseCancelable<T> implements Cancelable<T> {

    /**
     * The state of the task, possible transitions are:
     * <pre>
     *     INITIAL -> CANCELED
     *     EXECUTING -> COMPLETE, CANCELING, ERROR, CANCELED
     *     CANCELING -> CANCELED
     * </pre>
     * When the task stop, it must be end with one of the following states:
     * COMPLETE, CANCELED, or ERROR;
     */
    private static final int STATE_INITIAL = (1 << 0);
    private static final int STATE_EXECUTING = (1 << 1);
    private static final int STATE_CANCELING = (1 << 2);
    private static final int STATE_CANCELED = (1 << 3);
    private static final int STATE_ERROR = (1 << 4);
    private static final int STATE_COMPLETE = (1 << 5);

    private int mState = STATE_INITIAL;

    private Throwable mError;
    private T mResult;
    private Cancelable<?> mCurrentTask;

    protected abstract T execute() throws Exception;

    /**
     * Frees the result (which is not null) when the task has been canceled.
     */
    protected void freeCanceledResult(T result) {
        // Do nothing by default;
    }

    private boolean isInStates(int states) {
        return (states & mState) != 0;
    }

    private T handleTerminalStates() throws ExecutionException {
        if (mState == STATE_CANCELED) {
            throw new CancellationException();
        }
        if (mState == STATE_ERROR) {
            throw new ExecutionException(mError);
        }
        if (mState == STATE_COMPLETE) return mResult;
        throw new IllegalStateException();
    }

    public synchronized void await() throws InterruptedException {
        while (!isInStates(STATE_COMPLETE | STATE_CANCELED | STATE_ERROR)) {
            wait();
        }
    }

    public final T get() throws InterruptedException, ExecutionException {
        synchronized (this) {
            if (mState != STATE_INITIAL) {
                await();
                return handleTerminalStates();
            }
            mState = STATE_EXECUTING;
        }
        try {
            mResult = execute();
        } catch (CancellationException e) {
            mState = STATE_CANCELED;
        } catch (InterruptedException e) {
            mState = STATE_CANCELED;
        } catch (Throwable error) {
            synchronized (this) {
                if (mState != STATE_CANCELING) {
                    mError = error;
                    mState = STATE_ERROR;
                }
            }
        }
        synchronized (this) {
            if (mState == STATE_CANCELING) mState = STATE_CANCELED;
            if (mState == STATE_EXECUTING) mState = STATE_COMPLETE;
            notifyAll();
            if (mState == STATE_CANCELED && mResult != null) {
                freeCanceledResult(mResult);
            }
            return handleTerminalStates();
        }
    }

    /**
     * Requests the task to be canceled.
     *
     * @return true if the task is running and has not been canceled; false
     *     otherwise
     */

    public synchronized boolean requestCancel() {
        if (mState == STATE_INITIAL) {
            mState = STATE_CANCELED;
            notifyAll();
            return false;
        }
        if (mState == STATE_EXECUTING) {
            if (mCurrentTask != null) mCurrentTask.requestCancel();
            mState = STATE_CANCELING;
            return true;
        }
        return false;
    }

    /**
     * Runs a <code>Cancelable</code> subtask. This method is helpful, if the
     * task can be composed of several cancelable tasks. By using this function,
     * it will pass <code>requestCancel</code> message to those subtasks.
     *
     * @param <T> the return type of the sub task
     * @param cancelable the sub task
     * @return the result of the subtask
     */
    protected <T> T runSubTask(Cancelable<T> cancelable)
            throws InterruptedException, ExecutionException {
        synchronized (this) {
            if (mCurrentTask != null) {
                throw new IllegalStateException(
                        "cannot two subtasks at the same time");
            }
            if (mState == STATE_CANCELING) throw new CancellationException();
            mCurrentTask = cancelable;
        }
        try {
            return cancelable.get();
        } finally {
            synchronized (this) {
                mCurrentTask = null;
            }
        }
    }

}