blob: a1986830e68bb4a7d11ef6da5fded49860f61c2a [file] [log] [blame]
/*
* Copyright (C) 2022 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.quicksearchbox.util
import android.util.Log
import kotlin.collections.MutableList
/**
* Abstract base class for a one-place cache that holds a value that is produced asynchronously.
*
* @param <A> The type of the data held in the cache.
*/
abstract class CachedLater<A> : NowOrLater<A> {
private val mLock: Any = Any()
private var mValue: A? = null
private var mCreating = false
private var mValid = false
private var mWaitingConsumers: MutableList<Consumer<in A>>? = null
/**
* Creates the object to store in the cache. This method must call [.store] when it's done. This
* method must not block.
*/
protected abstract fun create()
/** Saves a new value to the cache. */
protected fun store(value: A) {
if (DBG) Log.d(TAG, "store()")
var waitingConsumers: MutableList<Consumer<in A>>?
synchronized(mLock) {
mValue = value
mValid = true
mCreating = false
waitingConsumers = mWaitingConsumers
mWaitingConsumers = null
}
if (waitingConsumers != null) {
for (consumer in waitingConsumers!!) {
if (DBG) Log.d(TAG, "Calling consumer: $consumer")
consumer.consume(value)
}
}
}
/**
* Gets the value.
*
* @param consumer A consumer that will be given the cached value. The consumer may be called
* synchronously, or asynchronously on an unspecified thread.
*/
override fun getLater(consumer: Consumer<in A>?) {
if (DBG) Log.d(TAG, "getLater()")
var valid: Boolean
var value: A?
synchronized(mLock) {
valid = mValid
value = mValue
if (!valid) {
if (mWaitingConsumers == null) {
mWaitingConsumers = mutableListOf()
}
mWaitingConsumers?.add(consumer!!)
}
}
if (valid) {
if (DBG) Log.d(TAG, "valid, calling consumer synchronously")
consumer!!.consume(value!!)
} else {
var create = false
synchronized(mLock) {
if (!mCreating) {
mCreating = true
create = true
}
}
if (create) {
if (DBG) Log.d(TAG, "not valid, calling create()")
create()
} else {
if (DBG) Log.d(TAG, "not valid, already creating")
}
}
}
/** Clears the cache. */
fun clear() {
if (DBG) Log.d(TAG, "clear()")
synchronized(mLock) {
mValue = null
mValid = false
}
}
override fun haveNow(): Boolean {
synchronized(mLock) {
return mValid
}
}
@get:Synchronized
override val now: A
get() {
synchronized(mLock) {
if (!haveNow()) {
throw IllegalStateException("getNow() called when haveNow() is false")
}
return mValue!!
}
}
companion object {
private const val TAG = "QSB.AsyncCache"
private const val DBG = false
}
}