1/*
2 * Copyright (C) 2010 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.quicksearchbox.util;
18
19import android.util.Log;
20
21import java.util.ArrayList;
22import java.util.List;
23
24/**
25 * Abstract base class for a one-place cache that holds a value that is produced
26 * asynchronously.
27 *
28 * @param <A> The type of the data held in the cache.
29 */
30public abstract class CachedLater<A> implements NowOrLater<A> {
31
32    private static final String TAG = "QSB.AsyncCache";
33    private static final boolean DBG = false;
34
35    private final Object mLock = new Object();
36
37    private A mValue;
38
39    private boolean mCreating;
40    private boolean mValid;
41
42    private List<Consumer<? super A>> mWaitingConsumers;
43
44    /**
45     * Creates the object to store in the cache. This method must call
46     * {@link #store} when it's done.
47     * This method must not block.
48     */
49    protected abstract void create();
50
51    /**
52     * Saves a new value to the cache.
53     */
54    protected void store(A value) {
55        if (DBG) Log.d(TAG, "store()");
56        List<Consumer<? super A>> waitingConsumers;
57        synchronized (mLock) {
58            mValue = value;
59            mValid = true;
60            mCreating = false;
61            waitingConsumers = mWaitingConsumers;
62            mWaitingConsumers = null;
63        }
64        if (waitingConsumers != null) {
65            for (Consumer<? super A> consumer : waitingConsumers) {
66                if (DBG) Log.d(TAG, "Calling consumer: " + consumer);
67                consumer.consume(value);
68            }
69        }
70    }
71
72    /**
73     * Gets the value.
74     *
75     * @param consumer A consumer that will be given the cached value.
76     *        The consumer may be called synchronously, or asynchronously on
77     *        an unspecified thread.
78     */
79    public void getLater(Consumer<? super A> consumer) {
80        if (DBG) Log.d(TAG, "getLater()");
81        boolean valid;
82        A value;
83        synchronized (mLock) {
84            valid = mValid;
85            value = mValue;
86            if (!valid) {
87                if (mWaitingConsumers == null) {
88                    mWaitingConsumers = new ArrayList<Consumer<? super A>>();
89                }
90                mWaitingConsumers.add(consumer);
91            }
92        }
93        if (valid) {
94            if (DBG) Log.d(TAG, "valid, calling consumer synchronously");
95            consumer.consume(value);
96        } else {
97            boolean create = false;
98            synchronized (mLock) {
99                if (!mCreating) {
100                    mCreating = true;
101                    create = true;
102                }
103            }
104            if (create) {
105                if (DBG) Log.d(TAG, "not valid, calling create()");
106                create();
107            } else {
108                if (DBG) Log.d(TAG, "not valid, already creating");
109            }
110        }
111    }
112
113    /**
114     * Clears the cache.
115     */
116    public void clear() {
117        if (DBG) Log.d(TAG, "clear()");
118        synchronized (mLock) {
119            mValue = null;
120            mValid = false;
121        }
122    }
123
124    public boolean haveNow() {
125        synchronized (mLock) {
126            return mValid;
127        }
128    }
129
130    public synchronized A getNow() {
131        synchronized (mLock) {
132            if (!haveNow()) {
133                throw new IllegalStateException("getNow() called when haveNow() is false");
134            }
135            return mValue;
136        }
137    }
138
139}
140