diff options
Diffstat (limited to 'core/src')
6 files changed, 237 insertions, 29 deletions
diff --git a/core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt b/core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt new file mode 100644 index 0000000..cff520f --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/kotlin/Zip.kt @@ -0,0 +1,90 @@ +package ca.allanwang.kau.kotlin + +import org.jetbrains.anko.doAsync +import java.util.concurrent.atomic.AtomicInteger + +/** + * Created by Allan Wang on 2017-08-06. + * + * Collection of zip methods that aim to replicate + * <a href="http://reactivex.io/documentation/operators/zip.html">Reactive Zips</a> + * For unit returning functions + * + * Typically, the functions will execute asynchronously and call their given callbacks when finished. + * Once all callbacks are called, the final onFinish callback will be executed. + * + * There is also a helper zipper to wrap synchronous functions with Anko's doAsync to achieve the same results + * + * Note that not wrapping synchronous functions will render these methods useless, + * as you can simply define an inline callback after all functions are finished + */ + +/** + * Callback which will only execute the first time + */ +open class ZipCallbackBase { + var completed: Boolean = false + + inline operator fun invoke(callback: () -> Unit) { + if (completed) return + completed = true + callback() + } +} + +class ZipCallback<T>(val onReceived: (T) -> Unit) : ZipCallbackBase() { + operator fun invoke(result: T) = invoke { onReceived(result) } +} + +class ZipEmptyCallback(val onReceived: () -> Unit) : ZipCallbackBase() { + operator fun invoke() = invoke(onReceived) +} + +/** + * Given a default result, a series of tasks, and a finished callback, + * this method will run all tasks and wait until all tasks emit a response + * The response will then be sent back to the callback + * + * ALl tasks must invoke the task callback for [onFinished] to execute + */ +inline fun <reified T> Collection<(ZipCallback<T>) -> Unit>.zip( + defaultResult: T, crossinline onFinished: (results: Array<T>) -> Unit +) { + val result = Array(size) { defaultResult } + val countDown = AtomicInteger(size) + forEachIndexed { index, asyncFun -> + asyncFun(ZipCallback<T> { + result[index] = it + if (countDown.decrementAndGet() <= 0) + onFinished(result) + }) + } +} + +/** + * Simplified zip method with no finished callback arguments + */ +inline fun Collection<(ZipEmptyCallback) -> Unit>.zip(crossinline onFinished: () -> Unit) { + val countDown = AtomicInteger(size) + forEach { asyncFun -> + asyncFun(ZipEmptyCallback { + if (countDown.decrementAndGet() <= 0) + onFinished() + }) + } +} + +/** + * Converts a collection of synchronous tasks to asynchronous tasks with a common callback + */ +inline fun Collection<() -> Unit>.zipAsync(crossinline onFinished: () -> Unit) { + map { synchronousFun -> + { + callback: ZipEmptyCallback -> + doAsync { + synchronousFun() + callback() + }; Unit + } + }.zip(onFinished) +} diff --git a/core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt b/core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt index 4fa3360..24146b0 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/logging/KL.kt @@ -3,4 +3,4 @@ package ca.allanwang.kau.logging /** * Created by Allan Wang on 2017-06-19. */ -object KL : TimberLogger("KAU")
\ No newline at end of file +object KL : KauLogger("KAU")
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt b/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt index 4cf566a..2fbecf5 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt @@ -2,23 +2,71 @@ package ca.allanwang.kau.logging -import timber.log.Timber +import android.os.Looper +import android.util.Log /** * Created by Allan Wang on 2017-05-28. * - * Timber extension that will embed the tag as part of the message for each log item + * Base logger class with a predefined tag + * This may be extended by an object to effectively replace [Log] */ -open class TimberLogger(tag: String) { - val TAG = "$tag: %s" - inline fun e(s: String) = Timber.e(TAG, s) - inline fun e(t: Throwable?, s: String = "error") = if (t == null) e(s) else Timber.e(t, TAG, s) - inline fun d(s: String) = Timber.d(TAG, s) - inline fun i(s: String) = Timber.i(TAG, s) - inline fun v(s: String) = Timber.v(TAG, s) - inline fun eThrow(s: String) = e(Throwable(s)) -// fun plant() { -// Timber.plant(Timber.Tree()) -// } +open class KauLogger(val tag: String) { + + open var enabled = true + open var showPrivateText = false + + /** + * Filter pass-through to decide what we wish to log + * By default, we will ignore verbose and debug logs + * @returns {@code true} to log the message, {@code false} to ignore + */ + open var filter: (Int) -> Boolean = { it != Log.VERBOSE && it != Log.DEBUG } + + open fun disable(disable: Boolean = true): KauLogger { + enabled = !disable + return this + } + + open fun debug(enable: Boolean) { + filter = if (enable) { _ -> true } else { i -> i != Log.VERBOSE && i != Log.DEBUG } + showPrivateText = enable + } + + open fun log(priority: Int, message: String?, privateMessage: String?, t: Throwable? = null) { + if (!shouldLog(priority, message, privateMessage, t)) return + logImpl(priority, message, privateMessage, t) + } + + protected open fun shouldLog(priority: Int, message: String?, privateMessage: String?, t: Throwable?): Boolean + = enabled && filter(priority) + + protected open fun logImpl(priority: Int, message: String?, privateMessage: String?, t: Throwable?) { + var text = message ?: "" + if (showPrivateText && privateMessage != null) + text += "\n-\t$privateMessage" + if (t != null) Log.e(tag, text, t) + else if (text.isNotBlank()) Log.println(priority, tag, text) + } + + open fun v(text: String?, privateText: String? = null) = log(Log.VERBOSE, text, privateText) + open fun d(text: String?, privateText: String? = null) = log(Log.DEBUG, text, privateText) + open fun i(text: String?, privateText: String? = null) = log(Log.INFO, text, privateText) + open fun e(text: String?, privateText: String? = null) = log(Log.ERROR, text, privateText) + open fun a(text: String?, privateText: String? = null) = log(Log.ASSERT, text, privateText) + open fun e(t: Throwable?, text: String?, privateText: String? = null) = log(Log.ERROR, text, privateText, t) + open fun eThrow(text: String?) { + if (text != null) + e(Throwable(text), text) + } + + /** + * Log the looper + */ + open fun checkThread(id: Int) { + val name = Thread.currentThread().name + val status = if (Looper.myLooper() == Looper.getMainLooper()) "is" else "is not" + d("$id $status in the main thread - thread name: $name") + } }
\ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt index e8f385a..2f3e9a5 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt @@ -39,14 +39,6 @@ annotation class KauUtils get() = (this / Resources.getSystem().displayMetrics.density).toInt() /** - * Log whether current state is in the main thread - */ -@KauUtils fun checkThread(id: Int) { - val status = if (Looper.myLooper() == Looper.getMainLooper()) "is" else "is not" - KL.d("$id $status in the main thread") -} - -/** * Converts minute value to string * Whole hours and days will be converted as such, otherwise it will default to x minutes */ diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt index 53d711d..3620a4a 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt @@ -211,13 +211,19 @@ inline val TextInputEditText.value: String get() = text.toString().trim() /** * Generates a recycler view with match parent and a linearlayoutmanager, since it's so commonly used */ -fun Context.fullLinearRecycler(rvAdapter: RecyclerView.Adapter<*>? = null, configs: RecyclerView.() -> Unit = {}): RecyclerView { - return RecyclerView(this).apply { - layoutManager = LinearLayoutManager(this@fullLinearRecycler) - layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT) - if (rvAdapter != null) adapter = rvAdapter - configs() - } +fun Context.fullLinearRecycler(rvAdapter: RecyclerView.Adapter<*>? = null, configs: RecyclerView.() -> Unit = {}) = RecyclerView(this).apply { + layoutManager = LinearLayoutManager(this@fullLinearRecycler) + layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT) + if (rvAdapter != null) adapter = rvAdapter + configs() +} + +/** + * Sets a linear layout manager along with an adapter + */ +fun RecyclerView.withLinearAdapter(rvAdapter: RecyclerView.Adapter<*>) = apply { + layoutManager = LinearLayoutManager(context) + adapter = rvAdapter } /** diff --git a/core/src/test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt b/core/src/test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt new file mode 100644 index 0000000..4a04142 --- /dev/null +++ b/core/src/test/kotlin/ca/allanwang/kau/kotlin/ZipTest.kt @@ -0,0 +1,72 @@ +package ca.allanwang.kau.kotlin + +import org.jetbrains.anko.doAsync +import org.junit.Test +import java.util.* +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import kotlin.test.assertTrue + +/** + * Created by Allan Wang on 2017-08-06. + */ +class ZipTest { + + val debug = false + + fun p(text: String) { + if (debug) println(text) + } + + @Test + fun basic() { + val start = System.currentTimeMillis() + val latch = CountDownLatch(1) + val rnd = Random() + (0..10).map { + { + callback: ZipCallback<Int> -> + doAsync { + val sleepTime = rnd.nextInt(100) + 200L + p("Task $it will sleep for ${sleepTime}ms") + Thread.sleep(sleepTime) + val finish = System.currentTimeMillis() + p("Task $it finished in ${finish - start}ms at $finish") + callback(it) + }; Unit + } + }.zip(-1) { + results -> + val finish = System.currentTimeMillis() + println("Results ${results.contentToString()} received in ${finish - start}ms at $finish") + assertTrue((0..10).toList().toTypedArray().contentEquals(results), "Basic zip results do not match") + assertTrue(finish - start < 1000L, "Basic zip does not seem to be running asynchronously") + latch.countDown() + + } + latch.await(1100, TimeUnit.MILLISECONDS) + } + + @Test + fun basicAsync() { + val start = System.currentTimeMillis() + val latch = CountDownLatch(1) + val rnd = Random() + (0..10).map { + { + val sleepTime = rnd.nextInt(100) + 200L + p("Task $it will sleep for ${sleepTime}ms") + Thread.sleep(sleepTime) + val finish = System.currentTimeMillis() + p("Task $it finished in ${finish - start}ms at $finish") + } + }.zipAsync { + val finish = System.currentTimeMillis() + println("Results received in ${finish - start}ms at $finish") + assertTrue(finish - start < 1000L, "BasicAsync does not seem to be wrapping the tasks asynchronously") + latch.countDown() + } + latch.await(1100, TimeUnit.MILLISECONDS) + } + +}
\ No newline at end of file |