/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * 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.facebook.buck.jvm.java.abi.kotlin

import kotlinx.metadata.Flag
import kotlinx.metadata.KmDeclarationContainer
import kotlinx.metadata.jvm.KotlinClassHeader
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.getterSignature
import kotlinx.metadata.jvm.setterSignature
import kotlinx.metadata.jvm.signature
import org.objectweb.asm.tree.AnnotationNode

/**
 * Method to find the inline functions of a Kotlin class by finding the Kotlin metadata annotation
 * and reading it.
 */
fun getInlineFunctions(annotationNode: AnnotationNode): List<String> {
    val metadata = KotlinClassMetadata.read(createHeader(annotationNode))

    val container: KmDeclarationContainer
    if (metadata is KotlinClassMetadata.Class) {
        container = metadata.toKmClass()
    } else if (metadata is KotlinClassMetadata.FileFacade) {
        container = metadata.toKmPackage()
    } else if (metadata is KotlinClassMetadata.MultiFileClassPart) {
        container = metadata.toKmPackage()
    } else {
        return emptyList()
    }

    val inlineFunctions = container.functions.filter { Flag.Function.IS_INLINE(it.flags) }
        .mapNotNull { it.signature?.name }

    val inlineProperties =
        container.properties.filter { Flag.PropertyAccessor.IS_INLINE(it.flags) }.map { it.name }

    val inlineGetters =
        container.properties.filter { Flag.PropertyAccessor.IS_INLINE(it.getterFlags) }
            .mapNotNull { it.getterSignature?.name }

    val inlineSetters =
        container.properties.filter { Flag.PropertyAccessor.IS_INLINE(it.setterFlags) }
            .mapNotNull { it.setterSignature?.name }

    return inlineFunctions.union(inlineProperties).union(inlineGetters).union(inlineSetters)
        .toList()
}

/**
 * Converts the given AnnotationNode representing the @kotlin.Metadata annotation into
 * KotlinClassHeader, to be able to use it in KotlinClassMetadata.read.
 */
private fun createHeader(node: AnnotationNode): KotlinClassHeader {
    var kind: Int? = null
    var metadataVersion: IntArray? = null
    var bytecodeVersion: IntArray? = null
    var data1: Array<String>? = null
    var data2: Array<String>? = null
    var extraString: String? = null
    var packageName: String? = null
    var extraInt: Int? = null

    val it = node.values.iterator()
    while (it.hasNext()) {
        val name = it.next() as String
        val value = it.next()

        when (name) {
            "k" -> kind = value as Int
            "mv" -> metadataVersion = listToIntArray(value)
            "bv" -> bytecodeVersion = listToIntArray(value)
            "d1" -> data1 = listToStringArray(value)
            "d2" -> data2 = listToStringArray(value)
            "xs" -> extraString = value as String
            "pn" -> packageName = value as String
            "xi" -> extraInt = value as Int
        }
    }

    return KotlinClassHeader(kind, metadataVersion, bytecodeVersion, data1, data2, extraString,
        packageName, extraInt)
}

@SuppressWarnings("unchecked") private fun listToIntArray(list: Any): IntArray {
    return (list as List<Int>).toIntArray()
}

@SuppressWarnings("unchecked") private fun listToStringArray(list: Any): Array<String> {
    return (list as List<String>).toTypedArray()
}


/*
 * Copyright 2014-present Facebook, Inc.
 *
 * 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.sample.dynamic_features.on_demand;

import android.app.Activity
import android.os.Bundle
import android.widget.TextView
import com.sample.kotlin.R

public class OnDemandKotlinFeatureActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_on_demand_kotlin)
        val tv: TextView = findViewById(R.id.on_demand_kotlin_text_view) as TextView
        tv.text = getString(R.string.on_demand_kotlin_text)
    }
}


package com.sample.lib

object Activity {
    var myString = "__FIND_ME_HERE__content=1__GOODBYE__"
}


package com.sample.depending

import android.app.Activity
import com.sample.lib.getActivity

fun test(): Activity {
    return getActivity()
}


package com.sample.lib

fun main(args: Array<String>) {
    println("Hello, world")
    JavaClass()
}

fun out(num1: Int): Float {
    return num1 / .66f
}


package com.sample.lib

import com.sample.R

import android.app.Activity
import android.os.Bundle

// Inline function, which is called by a depending library, makes sure that our ABI jars are at
// least somewhat Kotlin-aware (or else disabled for Kotlin rules)
inline fun getActivity(): Activity {
    return Sample()
}

class Sample : Activity() {
    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.top_layout)
        getActivity()
    }
}


package com.sample.lib

import com.sample2.R

import android.app.Activity
import android.os.Bundle

class Sample2 : Activity() {

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)

        setTitle(R.string.title)
    }
}


object Config {
  const val minSdk = 15
  const val compileSdk = 31
  const val targetSdk = 31
}

object Versions {
  const val acra = "5.5.0"
  const val analytics = "4.+"
  const val android_billingclient = "3.0.0"
  const val android_installreferrer =
      "1.0" // there are issues with 1.1 regarding permissions, asks for unnecessary permissions
  const val androidx = "1.1.0"
  const val androidxActivity = "1.2.0"
  const val androidxAnnotation = "1.1.0"
  const val androidxBrowser = "1.0.0"
  const val androidxCardview = "1.0.0"
  const val androidxConstraintLayout = "1.1.3"
  const val androidxCore = "1.0.0"
  const val androidxCoreKtx = "1.3.2"
  const val androidxEspressoCore = "3.1.0"
  const val androidxFragment = "1.3.0"
  const val androidxLegacy = "1.0.0"
  const val androidxTest = "1.4.0"
  const val assertj = "3.15.0"
  const val dexmaker = "1.2"
  const val dokka = "1.4.30"
  const val firebaseBom = "29.3.1"
  const val firebaseMessaging = "23.0.3"
  const val fresco = "2.6.0"
  const val glide = "4.13.0"
  const val googleServices = "4.3.5"
  const val gradle = "4.2.0" // Android gradle plugin version
  const val gson = "2.8.8"
  const val guava = "18.0"
  const val junit = "4.13"
  const val jacoco = "0.8.7"
  const val json = "20180130"
  const val kotlin = "1.5.10"
  const val leakcanaryAndroid = "2.9.1"
  const val liquidcore = "0.6.2"
  const val material = "1.0.0"
  const val mockitoInline = "2.26.0"
  const val mockitoKotlin = "2.2.11"
  const val mockwebserver = "4.9.0"
  const val playServicesAuth = "16.0.0"
  const val playServicesGcm = "17.0.0"
  const val powerMock = "2.0.2"
  const val robolectric = "4.4"
  const val stetho = "1.5.0"
  const val testng = "6.9.6"
  const val zxing = "3.3.3"
}

object Plugins {
  const val android_gradle = "com.android.tools.build:gradle:${Versions.gradle}"
  const val kotlin_gradle = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
  const val dokka = "org.jetbrains.dokka:dokka-gradle-plugin:${Versions.dokka}"
  const val google_services = "com.google.gms:google-services:${Versions.googleServices}"
  const val jacoco = "org.jacoco:org.jacoco.core:${Versions.jacoco}"
}

object Libs {

  // android
  const val android_billingclient =
      "com.android.billingclient:billing:${Versions.android_billingclient}"
  const val android_installreferrer =
      "com.android.installreferrer:installreferrer:${Versions.android_installreferrer}" // https://developer.android.com/google/play/installreferrer/library.html#java
  const val androidx_activity = "androidx.activity:activity:${Versions.androidxActivity}"
  const val androidx_annotation = "androidx.annotation:annotation:${Versions.androidxAnnotation}"
  const val androidx_appcompat = "androidx.appcompat:appcompat:${Versions.androidx}"
  const val androidx_browser = "androidx.browser:browser:${Versions.androidxBrowser}"
  const val androidx_cardview = "androidx.cardview:cardview:${Versions.androidxCardview}"
  const val androidx_constraintlayout =
      "androidx.constraintlayout:constraintlayout:${Versions.androidxConstraintLayout}"
  const val androidx_core = "androidx.core:core:${Versions.androidxCore}"
  const val androidx_core_ktx = "androidx.core:core-ktx:${Versions.androidxCoreKtx}"
  const val androidx_espresso_core =
      "androidx.test.espresso:espresso-core:${Versions.androidxEspressoCore}"
  const val androidx_fragment = "androidx.fragment:fragment:${Versions.androidxFragment}"
  const val androidx_legacy_support_v4 =
      "androidx.legacy:legacy-support-v4:${Versions.androidxLegacy}"
  const val androidx_legacy_support_core_utils =
      "androidx.legacy:legacy-support-core-utils:${Versions.androidxLegacy}"
  const val androidx_recyclerview = "androidx.recyclerview:recyclerview:${Versions.androidx}"
  const val androidx_test_core = "androidx.test:core:${Versions.androidxTest}"

  // google
  const val dexmaker = "com.google.dexmaker:dexmaker:${Versions.dexmaker}"
  const val dexmaker_mockito = "com.google.dexmaker:dexmaker-mockito:${Versions.dexmaker}"
  const val firebase_bom = "com.google.firebase:firebase-bom:${Versions.firebaseBom}"
  const val firebase_analytics = "com.google.firebase:firebase-analytics"
  const val firebase_messaging =
      "com.google.firebase:firebase-messaging:${Versions.firebaseMessaging}"
  const val gson = "com.google.code.gson:gson:${Versions.gson}"
  const val guava = "com.google.guava:guava:${Versions.guava}"
  const val material = "com.google.android.material:material:${Versions.material}"
  const val play_services_auth =
      "com.google.android.gms:play-services-auth:${Versions.playServicesAuth}"
  const val play_services_gcm =
      "com.google.android.gms:play-services-gcm:${Versions.playServicesGcm}"
  const val zxing = "com.google.zxing:core:${Versions.zxing}"

  // 1st-party
  const val fresco = "com.facebook.fresco:fresco:${Versions.fresco}"
  const val stetho = "com.facebook.stetho:stetho:${Versions.stetho}"

  const val acra = "ch.acra:acra-mail:${Versions.acra}"
  const val analytics = "com.segment.analytics.android:analytics:${Versions.analytics}"
  const val assertj_core = "org.assertj:assertj-core:${Versions.assertj}"
  const val glide = "com.github.bumptech.glide:glide:${Versions.glide}"
  const val glide_compiler = "com.github.bumptech.glide:compiler:${Versions.glide}"
  const val json = "org.json:json:${Versions.json}"
  const val junit = "junit:junit:${Versions.junit}"
  const val kotlin_stdlib = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}"
  const val kotlin_stdlib_jdk7 = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}"
  const val kotlin_test_junit = "org.jetbrains.kotlin:kotlin-test-junit:${Versions.kotlin}"
  const val leakcanary_android =
      "com.squareup.leakcanary:leakcanary-android:${Versions.leakcanaryAndroid}"
  const val leakcanary_object_watcher =
      "com.squareup.leakcanary:leakcanary-object-watcher-android:${Versions.leakcanaryAndroid}"
  const val liquidcore = "com.github.LiquidPlayer:LiquidCore:${Versions.liquidcore}"
  const val mockito_inline = "org.mockito:mockito-inline:${Versions.mockitoInline}"
  const val mockito_kotlin = "org.mockito.kotlin:mockito-kotlin:${Versions.mockitoKotlin}"
  const val mockwebserver = "com.squareup.okhttp3:mockwebserver:${Versions.mockwebserver}"
  const val powermock_core = "org.powermock:powermock-core:${Versions.powerMock}"
  const val powermock_api_mockito2 = "org.powermock:powermock-api-mockito2:${Versions.powerMock}"
  const val powermock_junit4 = "org.powermock:powermock-module-junit4:${Versions.powerMock}"
  const val powermock_junit4_rule =
      "org.powermock:powermock-module-junit4-rule:${Versions.powerMock}"
  const val powermock_classloading_xstream =
      "org.powermock:powermock-classloading-xstream:${Versions.powerMock}"
  const val robolectric = "org.robolectric:robolectric:${Versions.robolectric}"
  const val testng = "org.testng:testng:${Versions.testng}"
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.all

class All


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook

import com.facebook.internal.FacebookRequestErrorClassification
import junit.framework.Assert.assertEquals
import org.junit.Test

class ErrorClassificationTest : FacebookTestCase() {

  @Test
  fun testDefaultErrorClassification() {
    val errorClassification = FacebookRequestErrorClassification.defaultErrorClassification
    // Test transient takes precedence
    assertEquals(
        FacebookRequestError.Category.TRANSIENT,
        errorClassification.classify(FacebookRequestErrorClassification.EC_INVALID_TOKEN, 0, true))
    assertEquals(
        FacebookRequestError.Category.TRANSIENT,
        errorClassification.classify(
            FacebookRequestErrorClassification.EC_APP_NOT_INSTALLED, 0, true))
    assertEquals(
        FacebookRequestError.Category.LOGIN_RECOVERABLE,
        errorClassification.classify(
            FacebookRequestErrorClassification.EC_INVALID_SESSION, 0, false))
    assertEquals(
        FacebookRequestError.Category.LOGIN_RECOVERABLE,
        errorClassification.classify(FacebookRequestErrorClassification.EC_INVALID_TOKEN, 0, false))
    assertEquals(
        FacebookRequestError.Category.LOGIN_RECOVERABLE,
        errorClassification.classify(
            FacebookRequestErrorClassification.EC_APP_NOT_INSTALLED, 0, false))
    assertEquals(
        FacebookRequestError.Category.TRANSIENT,
        errorClassification.classify(
            FacebookRequestErrorClassification.EC_SERVICE_UNAVAILABLE, 0, false))
    assertEquals(
        FacebookRequestError.Category.TRANSIENT,
        errorClassification.classify(
            FacebookRequestErrorClassification.EC_APP_TOO_MANY_CALLS, 0, false))
    assertEquals(
        FacebookRequestError.Category.TRANSIENT,
        errorClassification.classify(FacebookRequestErrorClassification.EC_RATE, 0, false))
    assertEquals(
        FacebookRequestError.Category.TRANSIENT,
        errorClassification.classify(
            FacebookRequestErrorClassification.EC_USER_TOO_MANY_CALLS, 0, false))
    assertEquals(
        FacebookRequestError.Category.TRANSIENT,
        errorClassification.classify(
            FacebookRequestErrorClassification.EC_TOO_MANY_USER_ACTION_CALLS, 0, false))
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook

import com.facebook.FacebookSdk.getApplicationId
import com.facebook.FacebookSdk.getClientToken
import org.junit.Assert.assertEquals
import org.junit.Test
import org.mockito.kotlin.whenever
import org.powermock.api.mockito.PowerMockito.mockStatic
import org.powermock.core.classloader.annotations.PrepareForTest

/** This test makes sure PowerMock integration works. */
@PrepareForTest(FacebookSdk::class)
class PowerMockIntegrationTest : FacebookPowerMockTestCase() {
  @Test
  fun testStaticMethodOverrides() {
    mockStatic(FacebookSdk::class.java)
    val applicationId = "1234"

    whenever(getApplicationId()).thenReturn(applicationId)
    assertEquals(applicationId, getApplicationId())

    val clientToken = "clienttoken"
    whenever(getClientToken()).thenReturn(clientToken)
    assertEquals(clientToken, getClientToken())
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook

import android.annotation.TargetApi
import junit.framework.Assert
import org.json.JSONArray
import org.json.JSONObject

object TestUtils {
  @JvmStatic
  @TargetApi(16)
  fun assertEquals(expected: JSONObject?, actual: JSONObject?) {
    // JSONObject.equals does not do an order-independent comparison, so let's roll our own  :(
    if (areEqual(expected, actual)) {
      return
    }
    Assert.failNotEquals("", expected, actual)
  }

  private fun areEqual(expected: JSONObject?, actual: JSONObject?): Boolean {
    // JSONObject.equals does not do an order-independent comparison, so let's roll our own  :(
    if (expected === actual) {
      return true
    }
    if (expected == null || actual == null) {
      return false
    }
    val expectedKeysIterator = expected.keys()
    val expectedKeys = mutableSetOf<String>()
    while (expectedKeysIterator.hasNext()) {
      expectedKeys.add(expectedKeysIterator.next())
    }
    val actualKeysIterator = actual.keys()
    while (actualKeysIterator.hasNext()) {
      val key = actualKeysIterator.next()
      if (!areEqual(expected.opt(key), actual.opt(key))) {
        return false
      }
      expectedKeys.remove(key)
    }
    return expectedKeys.size == 0
  }

  private fun areEqual(expected: JSONArray?, actual: JSONArray?): Boolean {
    // JSONObject.equals does not do an order-independent comparison, so we need to check values
    // that are JSONObject
    // manually
    if (expected === actual) {
      return true
    }
    if (expected == null || actual == null) {
      return false
    }
    if (expected.length() != actual.length()) {
      return false
    }
    val length = expected.length()
    for (i in 0 until length) {
      if (!areEqual(expected.opt(i), actual.opt(i))) {
        return false
      }
    }
    return true
  }

  private fun areEqual(expected: Any?, actual: Any?): Boolean {
    if (expected === actual) {
      return true
    }
    if (expected == null || actual == null) {
      return false
    }
    if (expected is JSONObject && actual is JSONObject) {
      return areEqual(expected, actual)
    }
    return if (expected is JSONArray && actual is JSONArray) {
      areEqual(expected, actual)
    } else expected == actual
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.internal

import java.lang.IllegalArgumentException
import java.lang.UnsupportedOperationException
import java.util.HashSet
import org.json.JSONException
import org.json.JSONObject

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for
 * Android. Use of any of the classes in this package is unsupported, and they may be modified or
 * removed without warning at any time.
 */
internal object JsonUtil {
  fun jsonObjectClear(jsonObject: JSONObject) {
    val keys = jsonObject.keys()
    while (keys.hasNext()) {
      keys.next()
      keys.remove()
    }
  }

  fun jsonObjectContainsValue(jsonObject: JSONObject, value: Any): Boolean {
    val keys = jsonObject.keys()
    while (keys.hasNext()) {
      val thisValue = jsonObject.opt(keys.next())
      if (thisValue != null && thisValue == value) {
        return true
      }
    }
    return false
  }

  fun jsonObjectEntrySet(jsonObject: JSONObject): Set<Map.Entry<String, Any>> {
    val result = HashSet<Map.Entry<String, Any>>()
    val keys = jsonObject.keys()
    while (keys.hasNext()) {
      val key = keys.next()
      val value = jsonObject.get(key)
      result.add(JSONObjectEntry(key, value))
    }
    return result
  }

  fun jsonObjectKeySet(jsonObject: JSONObject): Set<String> {
    val result = HashSet<String>()
    val keys = jsonObject.keys()
    while (keys.hasNext()) {
      result.add(keys.next())
    }
    return result
  }

  fun jsonObjectPutAll(jsonObject: JSONObject, map: Map<String, Any>) {
    val entrySet = map.entries
    for ((key, value) in entrySet) {
      try {
        jsonObject.putOpt(key, value)
      } catch (e: JSONException) {
        throw IllegalArgumentException(e)
      }
    }
  }

  fun jsonObjectValues(jsonObject: JSONObject): Collection<Any> {
    val result = mutableListOf<Any>()
    val keys = jsonObject.keys()
    while (keys.hasNext()) {
      result.add(jsonObject.get(keys.next()))
    }
    return result
  }

  private class JSONObjectEntry constructor(override val key: String, override val value: Any) :
      MutableMap.MutableEntry<String, Any> {
    override fun setValue(newValue: Any): Any {
      throw UnsupportedOperationException("JSONObjectEntry is immutable")
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.internal

import com.facebook.FacebookTestCase
import java.util.HashMap
import junit.framework.Assert
import org.json.JSONObject
import org.junit.Test

class JsonUtilTest : FacebookTestCase() {

  @Test
  fun testJsonObjectClear() {
    val jsonObject = JSONObject()
    jsonObject.put("hello", "world")
    jsonObject.put("hocus", "pocus")
    JsonUtil.jsonObjectClear(jsonObject)
    Assert.assertEquals(0, jsonObject.length())
  }

  @Test
  fun testJsonObjectContainsValue() {
    val jsonObject = JSONObject()
    jsonObject.put("hello", "world")
    jsonObject.put("hocus", "pocus")
    Assert.assertTrue(JsonUtil.jsonObjectContainsValue(jsonObject, "pocus"))
    Assert.assertFalse(JsonUtil.jsonObjectContainsValue(jsonObject, "Fred"))
  }

  @Test
  fun testJsonObjectEntrySet() {
    val jsonObject = JSONObject()
    jsonObject.put("hello", "world")
    jsonObject.put("hocus", "pocus")
    val entrySet = JsonUtil.jsonObjectEntrySet(jsonObject)
    Assert.assertEquals(2, entrySet.size)
  }

  @Test
  fun testJsonObjectKeySet() {
    val jsonObject = JSONObject()
    jsonObject.put("hello", "world")
    jsonObject.put("hocus", "pocus")
    val keySet = JsonUtil.jsonObjectKeySet(jsonObject)
    Assert.assertEquals(2, keySet.size)
    Assert.assertTrue(keySet.contains("hello"))
    Assert.assertFalse(keySet.contains("world"))
  }

  @Test
  fun testJsonObjectPutAll() {
    val map = HashMap<String, Any>()
    map["hello"] = "world"
    map["hocus"] = "pocus"
    val jsonObject = JSONObject()
    JsonUtil.jsonObjectPutAll(jsonObject, map)
    Assert.assertEquals("pocus", jsonObject["hocus"])
    Assert.assertEquals(2, jsonObject.length())
  }

  @Test
  fun testJsonObjectValues() {
    val jsonObject = JSONObject()
    jsonObject.put("hello", "world")
    jsonObject.put("hocus", "pocus")
    val values = JsonUtil.jsonObjectValues(jsonObject)
    Assert.assertEquals(2, values.size)
    Assert.assertTrue(values.contains("world"))
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.messenger

import android.app.Activity
import android.content.ContentResolver
import android.content.Intent
import android.database.MatrixCursor
import android.net.Uri
import android.os.Bundle
import com.facebook.FacebookPowerMockTestCase
import com.facebook.FacebookSdk
import com.facebook.internal.FacebookSignatureValidator
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config

/** Tests for [com.facebook.messenger.MessengerUtils] */
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [18], manifest = Config.NONE)
@PrepareForTest(FacebookSignatureValidator::class)
class MessengerUtilsTest : FacebookPowerMockTestCase() {

  private lateinit var mMockActivity: Activity
  private lateinit var mMockContentResolver: ContentResolver

  @Before
  override fun setup() {
    mMockActivity = Mockito.mock(Activity::class.java)
    mMockContentResolver = Mockito.mock(ContentResolver::class.java)
    Mockito.`when`(mMockActivity.contentResolver).thenReturn(mMockContentResolver)
    FacebookSdk.setApplicationId("200")
    FacebookSdk.setClientToken("abcdefg")
    FacebookSdk.setAutoLogAppEventsEnabled(false)
    FacebookSdk.sdkInitialize(RuntimeEnvironment.application)
    PowerMockito.mockStatic(FacebookSignatureValidator::class.java)
  }

  @Test
  @Throws(Exception::class)
  fun testMessengerIsInstalled() {
    setupPackageManagerForMessenger(true)
    assertTrue(MessengerUtils.hasMessengerInstalled(mMockActivity))
  }

  @Test
  @Throws(Exception::class)
  fun testMessengerNotInstalled() {
    setupPackageManagerForMessenger(false)
    assertFalse(MessengerUtils.hasMessengerInstalled(mMockActivity))
  }

  @Test
  @Throws(Exception::class)
  fun testShareToMessengerWith20150314Protocol() {
    setupPackageManagerForMessenger(true)
    setupContentResolverForProtocolVersions(20150314)
    val uri = Uri.parse("file:///foo.jpeg")
    val externalUri = Uri.parse("http://example.com/foo.jpeg")
    val params =
        ShareToMessengerParams.newBuilder(uri, "image/jpeg")
            .setMetaData("{}")
            .setExternalUri(externalUri)
            .build()
    MessengerUtils.shareToMessenger(mMockActivity, 1, params)

    // Expect it to have launched messenger with the right intent.
    val intentArgumentCaptor = ArgumentCaptor.forClass(Intent::class.java)
    verify(mMockActivity)
        .startActivityForResult(intentArgumentCaptor.capture(), ArgumentMatchers.eq(1))
    val intent = intentArgumentCaptor.value
    assertEquals(Intent.ACTION_SEND, intent.action)
    assertEquals(Intent.FLAG_GRANT_READ_URI_PERMISSION.toLong(), intent.flags.toLong())
    assertEquals("com.facebook.orca", intent.getPackage())
    assertEquals(uri, intent.getParcelableExtra(Intent.EXTRA_STREAM))
    assertEquals("image/jpeg", intent.type)
    assertEquals("200", intent.getStringExtra("com.facebook.orca.extra.APPLICATION_ID"))
    assertEquals(
        20150314, intent.getIntExtra("com.facebook.orca.extra.PROTOCOL_VERSION", -1).toLong())
    assertEquals("{}", intent.getStringExtra("com.facebook.orca.extra.METADATA"))
    assertEquals(externalUri, intent.getParcelableExtra("com.facebook.orca.extra.EXTERNAL_URI"))
  }

  @Test
  @Throws(Exception::class)
  fun testShareToMessengerWithNoProtocol() {
    setupPackageManagerForMessenger(true)
    setupContentResolverForProtocolVersions()
    val uri = Uri.parse("file:///foo.jpeg")
    val externalUri = Uri.parse("http://example.com/foo.jpeg")
    val params =
        ShareToMessengerParams.newBuilder(uri, "image/jpeg")
            .setMetaData("{}")
            .setExternalUri(externalUri)
            .build()
    MessengerUtils.shareToMessenger(mMockActivity, 1, params)

    // Expect it to have gone to the play store.
    val intentArgumentCaptor = ArgumentCaptor.forClass(Intent::class.java)
    verify(mMockActivity).startActivity(intentArgumentCaptor.capture())
    val intent = intentArgumentCaptor.value
    assertEquals(Intent.ACTION_VIEW, intent.action)
    assertEquals(Uri.parse("market://details?id=com.facebook.orca"), intent.data)
  }

  @Test
  fun testGetMessengerThreadParamsForIntentWith20150314Protocol() {
    // Simulate an intent that Messenger would send.
    val intent = Intent()
    intent.addCategory("com.facebook.orca.category.PLATFORM_THREAD_20150314")
    val extrasBundle = setupIntentWithAppLinkExtrasBundle(intent)
    extrasBundle.putString("com.facebook.orca.extra.THREAD_TOKEN", "thread_token")
    extrasBundle.putString("com.facebook.orca.extra.METADATA", "{}")
    extrasBundle.putString("com.facebook.orca.extra.PARTICIPANTS", "100,400,500")
    extrasBundle.putBoolean("com.facebook.orca.extra.IS_REPLY", true)

    // Check the parsing logic.
    val params = MessengerUtils.getMessengerThreadParamsForIntent(intent)
    assertEquals(MessengerThreadParams.Origin.REPLY_FLOW, params?.origin)
    assertEquals("thread_token", params?.threadToken)
    assertEquals("{}", params?.metadata)
    assertEquals(listOf("100", "400", "500"), params?.participants)
  }

  @Test
  @Throws(Exception::class)
  fun testGetMessengerThreadParamsForIntentWithUnrecognizedIntent() {
    // Simulate an intent that Messenger would send.
    val intent = Intent()
    assertNull(MessengerUtils.getMessengerThreadParamsForIntent(intent))
  }

  @Test
  @Throws(Exception::class)
  fun testFinishShareToMessengerWith20150314Protocol() {
    // Simulate an intent that Messenger would send.
    val originalIntent = Intent()
    originalIntent.addCategory("com.facebook.orca.category.PLATFORM_THREAD_20150314")
    val extrasBundle = setupIntentWithAppLinkExtrasBundle(originalIntent)
    extrasBundle.putString("com.facebook.orca.extra.THREAD_TOKEN", "thread_token")
    extrasBundle.putString("com.facebook.orca.extra.METADATA", "{}")
    extrasBundle.putString("com.facebook.orca.extra.PARTICIPANTS", "100,400,500")
    Mockito.`when`(mMockActivity.intent).thenReturn(originalIntent)

    // Setup the data the app will send back to messenger.
    val uri = Uri.parse("file:///foo.jpeg")
    val externalUri = Uri.parse("http://example.com/foo.jpeg")
    val params =
        ShareToMessengerParams.newBuilder(uri, "image/jpeg")
            .setMetaData("{}")
            .setExternalUri(externalUri)
            .build()

    // Call finishShareToMessenger and verify the results.
    MessengerUtils.finishShareToMessenger(mMockActivity, params)
    val intentArgumentCaptor = ArgumentCaptor.forClass(Intent::class.java)
    verify(mMockActivity)
        .setResult(ArgumentMatchers.eq(Activity.RESULT_OK), intentArgumentCaptor.capture())
    verify(mMockActivity).finish()
    val intent = intentArgumentCaptor.value
    assertNotNull(intent)
    assertEquals(Intent.FLAG_GRANT_READ_URI_PERMISSION.toLong(), intent.flags.toLong())
    assertEquals(
        20150314, intent.getIntExtra("com.facebook.orca.extra.PROTOCOL_VERSION", -1).toLong())
    assertEquals("thread_token", intent.getStringExtra("com.facebook.orca.extra.THREAD_TOKEN"))
    assertEquals(uri, intent.data)
    assertEquals("image/jpeg", intent.type)
    assertEquals("200", intent.getStringExtra("com.facebook.orca.extra.APPLICATION_ID"))
    assertEquals("{}", intent.getStringExtra("com.facebook.orca.extra.METADATA"))
    assertEquals(externalUri, intent.getParcelableExtra("com.facebook.orca.extra.EXTERNAL_URI"))
  }

  @Test
  @Throws(Exception::class)
  fun testFinishShareToMessengerWithUnexpectedIntent() {
    // Simulate an intent that Messenger would send.
    val originalIntent = Intent()
    Mockito.`when`(mMockActivity.intent).thenReturn(originalIntent)

    // Setup the data the app will send back to messenger.
    val uri = Uri.parse("file:///foo.jpeg")
    val externalUri = Uri.parse("http://example.com/foo.jpeg")
    val params =
        ShareToMessengerParams.newBuilder(uri, "image/jpeg")
            .setMetaData("{}")
            .setExternalUri(externalUri)
            .build()

    // Call finishShareToMessenger and verify the results.
    MessengerUtils.finishShareToMessenger(mMockActivity, params)
    verify(mMockActivity).setResult(Activity.RESULT_CANCELED, null)
    verify(mMockActivity).finish()
  }

  /**
   * Sets up the PackageManager to return what we expect depending on whether messenger is
   * installed.
   *
   * @param isInstalled true to simulate that messenger is installed
   */
  private fun setupPackageManagerForMessenger(isInstalled: Boolean) {
    Mockito.`when`(FacebookSignatureValidator.validateSignature(mMockActivity, "com.facebook.orca"))
        .thenReturn(isInstalled)
  }

  /**
   * Sets up the Messenger content resolver to reply that it supports the specified versions.
   *
   * @param versions the versions that it should support
   */
  private fun setupContentResolverForProtocolVersions(vararg versions: Int) {
    val matrixCursor = MatrixCursor(arrayOf("version"))
    versions.forEach { matrixCursor.addRow(arrayOf<Any>(it)) }

    val versionsUri =
        Uri.parse("content://com.facebook.orca.provider.MessengerPlatformProvider/versions")
    Mockito.`when`(mMockContentResolver.query(versionsUri, arrayOf("version"), null, null, null))
        .thenReturn(matrixCursor)
  }

  /**
   * Adds the structure to the Intent to look like an app link and returns the Extras section which
   * is where the messenger parameters go.
   *
   * @param intent the intent to add to
   * @return the extras Bundle
   */
  private fun setupIntentWithAppLinkExtrasBundle(intent: Intent): Bundle {
    val appLinksDataBundle = Bundle()
    intent.putExtra("al_applink_data", appLinksDataBundle)
    val extrasBundle = Bundle()
    appLinksDataBundle.putBundle("extras", extrasBundle)
    return extrasBundle
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import java.io.PrintStream
import java.io.PrintWriter
import java.util.Collections

/**
 * Aggregates multiple `Throwable`s that may be thrown in the process of a task's execution.
 *
 * @param detailMessage The detail message for this exception.
 * @param innerThrowables The exceptions that are the cause of the current exception.
 *
 * @see Task.whenAll
 */
class AggregateException(detailMessage: String?, innerThrowables: List<Throwable?>?) :
    Exception(
        detailMessage,
        if (innerThrowables != null && innerThrowables.isNotEmpty()) innerThrowables[0] else null) {
  private val innerThrowables: List<Throwable?> =
      Collections.unmodifiableList(innerThrowables ?: emptyList())
  override fun printStackTrace(err: PrintStream) {
    super.printStackTrace(err)
    var currentIndex = -1
    for (throwable in innerThrowables) {
      err.append("\n")
      err.append("  Inner throwable #")
      err.append((++currentIndex).toString())
      err.append(": ")
      throwable?.printStackTrace(err)
      err.append("\n")
    }
  }

  override fun printStackTrace(err: PrintWriter) {
    super.printStackTrace(err)
    var currentIndex = -1
    for (throwable in innerThrowables) {
      err.append("\n")
      err.append("  Inner throwable #")
      err.append((++currentIndex).toString())
      err.append(": ")
      throwable?.printStackTrace(err)
      err.append("\n")
    }
  }

  companion object {
    private const val serialVersionUID = 1L
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import android.os.Handler
import android.os.Looper
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit

/**
 * This was created because the helper methods in [java.util.concurrent.Executors] do not work as
 * people would normally expect.
 *
 * Normally, you would think that a cached thread pool would create new threads when necessary,
 * queue them when the pool is full, and kill threads when they've been inactive for a certain
 * period of time. This is not how [java.util.concurrent.Executors.newCachedThreadPool] works.
 *
 * Instead, [java.util.concurrent.Executors.newCachedThreadPool] executes all tasks on a new or
 * cached thread immediately because corePoolSize is 0, SynchronousQueue is a queue with size 0 and
 * maxPoolSize is Integer.MAX_VALUE. This is dangerous because it can create an unchecked amount of
 * threads.
 */
internal class AndroidExecutors private constructor() {
  private val uiThread: Executor = UIThreadExecutor()

  /** An [java.util.concurrent.Executor] that runs tasks on the UI thread. */
  private class UIThreadExecutor : Executor {
    override fun execute(command: Runnable) {
      Handler(Looper.getMainLooper()).post(command)
    }
  }

  companion object {
    private val INSTANCE = AndroidExecutors()

    /**
     * Nexus 5: Quad-Core Moto X: Dual-Core
     *
     * AsyncTask: CORE_POOL_SIZE = CPU_COUNT + 1 MAX_POOL_SIZE = CPU_COUNT * 2 + 1
     *
     * https://github.com/android/platform_frameworks_base/commit/719c44e03b97e850a46136ba336d729f5fbd1f47
     */
    private val CPU_COUNT = Runtime.getRuntime().availableProcessors()

    private val CORE_POOL_SIZE = CPU_COUNT + 1

    private val MAX_POOL_SIZE = CPU_COUNT * 2 + 1

    private const val KEEP_ALIVE_TIME = 1L

    /**
     * Creates a proper Cached Thread Pool. Tasks will reuse cached threads if available or create
     * new threads until the core pool is full. tasks will then be queued. If an task cannot be
     * queued, a new thread will be created unless this would exceed max pool size, then the task
     * will be rejected. Threads will time out after 1 second.
     *
     * Core thread timeout is only available on android-9+.
     *
     * @return the newly created thread pool
     */
    @JvmStatic
    fun newCachedThreadPool(): ExecutorService {
      val executor =
          ThreadPoolExecutor(
              CORE_POOL_SIZE,
              MAX_POOL_SIZE,
              KEEP_ALIVE_TIME,
              TimeUnit.SECONDS,
              LinkedBlockingQueue())
      executor.allowCoreThreadTimeOut(true)
      return executor
    }

    /** An [java.util.concurrent.Executor] that executes tasks on the UI thread. */
    @JvmStatic fun uiThread(): Executor = INSTANCE.uiThread
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import android.net.Uri
import java.util.Collections

/**
 * Contains App Link metadata relevant for navigation on this device derived from the HTML at a
 * given URL.
 *
 * @param sourceUrl the URL from which this App Link was derived.
 * @param webUrl the fallback web URL, if any was specified, for this App Link.
 * @param targets the ordered list of Targets for this platform.
 */
class AppLink(val sourceUrl: Uri, targets: List<Target>?, val webUrl: Uri) {

  /**
   * Represents a target defined in App Link metadata, consisting of at least a package name, and
   * optionally a URL, class name (for explicit intent handling), and an app name.
   */
  class Target
  /**
   * Creates a Target with the given metadata.
   *
   * @param packageName the package name for the target.
   * @param className the class name to be used when creating an explicit intent.
   * @param url the URL to be used as the data in the constructed intent.
   * @param appName the name of the app.
   */
  (
      /** the package name of the app. */
      val packageName: String,
      /** the class name to be used when creating an explicit intent from this target. */
      val className: String,
      /**
       * the URL that will be used as the data in an intent constructed from this target. If no url
       * is specified, the intent will use the URL that was the source of this metadata.
       */
      val url: Uri,
      /** the app name. */
      val appName: String
  )

  /** the ordered list of Targets for this platform. */
  val targets: List<Target> = targets ?: emptyList()
    get() = Collections.unmodifiableList(field)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import android.net.Uri

/**
 * Implement this interface to provide an alternate strategy for resolving App Links that may
 * include pre-fetching, caching, or querying for App Link data from an index provided by a service
 * provider.
 */
fun interface AppLinkResolver {
  /**
   * Asynchronously resolves App Link data for a given URL.
   *
   * @param url the URL to resolve into an App Link.
   * @return the [AppLink] for the given URL.
   */
  fun getAppLinkFromUrlInBackground(url: Uri): Task<AppLink>
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import android.content.Intent
import android.os.Bundle

/**
 * Provides a set of utility methods for working with incoming Intents that may contain App Link
 * data.
 */
object AppLinks {
  const val KEY_NAME_APPLINK_DATA = "al_applink_data"
  const val KEY_NAME_EXTRAS = "extras"

  /**
   * Gets the App Link data for an intent, if there is any. This is the authorized function to check
   * if an intent is AppLink. If null is returned it is not.
   *
   * @param intent the incoming intent.
   * @return a bundle containing the App Link data for the intent, or `null` if none is specified.
   */
  @JvmStatic
  fun getAppLinkData(intent: Intent): Bundle? {
    return intent.getBundleExtra(KEY_NAME_APPLINK_DATA)
  }

  /**
   * Gets the App Link extras for an intent, if there is any.
   *
   * @param intent the incoming intent.
   * @return a bundle containing the App Link extras for the intent, or `null` if none is specified.
   */
  @JvmStatic
  fun getAppLinkExtras(intent: Intent): Bundle? {
    val appLinkData = getAppLinkData(intent) ?: return null
    return appLinkData.getBundle(KEY_NAME_EXTRAS)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import java.util.Locale
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService

/** Collection of [Executor]s to use in conjunction with [Task]. */
internal class BoltsExecutors private constructor() {
  private val background: ExecutorService =
      if (!isAndroidRuntime) Executors.newCachedThreadPool()
      else AndroidExecutors.newCachedThreadPool()
  private val scheduled: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
  private val immediate: Executor = ImmediateExecutor()

  /**
   * An [java.util.concurrent.Executor] that runs a runnable inline (rather than scheduling it on a
   * thread pool) as long as the recursion depth is less than MAX_DEPTH. If the executor has
   * recursed too deeply, it will instead delegate to the [Task.BACKGROUND_EXECUTOR] in order to
   * trim the stack.
   */
  private class ImmediateExecutor : Executor {
    private val executionDepth = ThreadLocal<Int>()

    /**
     * Increments the depth.
     *
     * @return the new depth value.
     */
    private fun incrementDepth(): Int {
      var oldDepth = executionDepth.get()
      if (oldDepth == null) {
        oldDepth = 0
      }
      val newDepth = oldDepth + 1
      executionDepth.set(newDepth)
      return newDepth
    }

    /**
     * Decrements the depth.
     *
     * @return the new depth value.
     */
    private fun decrementDepth(): Int {
      var oldDepth = executionDepth.get()
      if (oldDepth == null) {
        oldDepth = 0
      }
      val newDepth = oldDepth - 1
      if (newDepth == 0) {
        executionDepth.remove()
      } else {
        executionDepth.set(newDepth)
      }
      return newDepth
    }

    override fun execute(command: Runnable) {
      val depth = incrementDepth()
      try {
        if (depth <= MAX_DEPTH) {
          command.run()
        } else {
          background().execute(command)
        }
      } finally {
        decrementDepth()
      }
    }

    companion object {
      private const val MAX_DEPTH = 15
    }
  }

  companion object {
    private val INSTANCE = BoltsExecutors()
    private val isAndroidRuntime: Boolean
      private get() {
        val javaRuntimeName = System.getProperty("java.runtime.name") ?: return false
        return javaRuntimeName.toLowerCase(Locale.US).contains("android")
      }

    /** An [java.util.concurrent.Executor] that executes tasks in parallel. */
    @JvmStatic fun background(): ExecutorService = INSTANCE.background

    @JvmStatic internal fun scheduled(): ScheduledExecutorService = INSTANCE.scheduled

    /**
     * An [java.util.concurrent.Executor] that executes tasks in the current thread unless the stack
     * runs too deep, at which point it will delegate to [BoltsExecutors.background] in order to
     * trim the stack.
     */
    @JvmStatic internal fun immediate(): Executor = INSTANCE.immediate
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import java.util.Locale
import java.util.concurrent.CancellationException

/**
 * Propagates notification that operations should be canceled.
 *
 * Create an instance of `CancellationTokenSource` and pass the token returned from
 * `CancellationTokenSource.token` to the asynchronous operation(s). Call
 * `CancellationTokenSource#cancel()` to cancel the operations.
 *
 * A `CancellationToken` can only be cancelled once - it should not be passed to future operations
 * once cancelled.
 *
 * @see CancellationTokenSource
 *
 * @see CancellationTokenSource.token
 * @see CancellationTokenSource.cancel
 * @see CancellationToken.register
 */
class CancellationToken internal constructor(private val tokenSource: CancellationTokenSource) {
  /** @return `true` if the cancellation was requested from the source, `false` otherwise. */
  val isCancellationRequested: Boolean
    get() = tokenSource.isCancellationRequested

  /**
   * Registers a runnable that will be called when this CancellationToken is canceled. If this token
   * is already in the canceled state, the runnable will be run immediately and synchronously.
   *
   * @param action the runnable to be run when the token is cancelled.
   * @return a [CancellationTokenRegistration] instance that can be used to unregister the action.
   */
  fun register(action: Runnable?): CancellationTokenRegistration {
    return tokenSource.register(action)
  }

  /**
   * @throws CancellationException if this token has had cancellation requested. May be used to stop
   * execution of a thread or runnable.
   */
  @Throws(CancellationException::class)
  fun throwIfCancellationRequested() {
    tokenSource.throwIfCancellationRequested()
  }

  override fun toString(): String {
    return String.format(
        Locale.US,
        "%s@%s[cancellationRequested=%s]",
        javaClass.name,
        Integer.toHexString(hashCode()),
        java.lang.Boolean.toString(tokenSource.isCancellationRequested))
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import java.io.Closeable

/**
 * Represents a callback delegate that has been registered with a [CancellationToken].
 *
 * @see CancellationToken.register
 */
class CancellationTokenRegistration
internal constructor(tokenSource: CancellationTokenSource, private var action: Runnable?) :
    Closeable {
  private var closed = false
  private var tokenSource: CancellationTokenSource? = tokenSource

  /** Unregisters the callback runnable from the cancellation token. */
  override fun close() {
    synchronized(this) {
      if (closed) {
        return
      }
      closed = true
      tokenSource?.unregister(this)
      tokenSource = null
      action = null
    }
  }

  internal fun runAction() {
    synchronized(this) {
      throwIfClosed()
      action?.run()
      close()
    }
  }

  private fun throwIfClosed() {
    check(!closed) { "Object already closed" }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import java.io.Closeable
import java.util.Locale
import java.util.concurrent.CancellationException
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit

/**
 * Signals to a [CancellationToken] that it should be canceled. To create a `CancellationToken`
 * first create a `CancellationTokenSource` then use [token] to retrieve the token for the source.
 *
 * @see CancellationToken
 * @see CancellationTokenSource.token
 */
class CancellationTokenSource : Closeable {
  private val lock = Any()
  private val registrations = mutableListOf<CancellationTokenRegistration>()
  private val executor = BoltsExecutors.scheduled()
  private var scheduledCancellation: ScheduledFuture<*>? = null
  private var cancellationRequested = false
  private var closed = false

  /** @return `true` if cancellation has been requested for this `CancellationTokenSource`. */
  val isCancellationRequested: Boolean
    get() {
      synchronized(lock) {
        throwIfClosed()
        return cancellationRequested
      }
    }

  /** The token that can be passed to asynchronous method to control cancellation. */
  val token: CancellationToken
    get() {
      synchronized(lock) {
        throwIfClosed()
        return CancellationToken(this)
      }
    }

  /** Cancels the token if it has not already been cancelled. */
  fun cancel() {
    var registrations: List<CancellationTokenRegistration>
    synchronized(lock) {
      throwIfClosed()
      if (cancellationRequested) {
        return
      }
      cancelScheduledCancellation()
      cancellationRequested = true
      registrations = ArrayList(this.registrations)
    }
    notifyListeners(registrations)
  }

  /**
   * Schedules a cancel operation on this `CancellationTokenSource` after the specified number of
   * milliseconds.
   *
   * @param delay The number of milliseconds to wait before completing the returned task. If delay
   * is `0` the cancel is executed immediately. If delay is `-1` any scheduled cancellation is
   * stopped.
   */
  fun cancelAfter(delay: Long) {
    cancelAfter(delay, TimeUnit.MILLISECONDS)
  }

  private fun cancelAfter(delay: Long, timeUnit: TimeUnit) {
    require(delay >= -1) { "Delay must be >= -1" }
    if (delay == 0L) {
      cancel()
      return
    }
    synchronized(lock) {
      if (cancellationRequested) {
        return
      }
      cancelScheduledCancellation()
      if (delay != -1L) {
        scheduledCancellation =
            executor.schedule(
                Runnable {
                  synchronized(lock) { scheduledCancellation = null }
                  cancel()
                },
                delay,
                timeUnit)
      }
    }
  }

  override fun close() {
    synchronized(lock) {
      if (closed) {
        return
      }
      cancelScheduledCancellation()
      for (registration: CancellationTokenRegistration in registrations) {
        registration.close()
      }
      registrations.clear()
      closed = true
    }
  }

  internal fun register(action: Runnable?): CancellationTokenRegistration {
    var ctr: CancellationTokenRegistration
    synchronized(lock) {
      throwIfClosed()
      ctr = CancellationTokenRegistration(this, action)
      if (cancellationRequested) {
        ctr.runAction()
      } else {
        registrations.add(ctr)
      }
    }
    return ctr
  }

  /**
   * @throws CancellationException if this token has had cancellation requested. May be used to stop
   * execution of a thread or runnable.
   */
  @Throws(CancellationException::class)
  internal fun throwIfCancellationRequested() {
    synchronized(lock) {
      throwIfClosed()
      if (cancellationRequested) {
        throw CancellationException()
      }
    }
  }

  internal fun unregister(registration: CancellationTokenRegistration) {
    synchronized(lock) {
      throwIfClosed()
      registrations.remove(registration)
    }
  }

  // This method makes no attempt to perform any synchronization or state checks itself and once
  // invoked will notify all runnables unconditionally. As such if you require the notification
  // event
  // to be synchronized with state changes you should provide external synchronization.
  // If this is invoked without external synchronization there is a probability the token becomes
  // cancelled concurrently.
  private fun notifyListeners(registrations: List<CancellationTokenRegistration>) {
    for (registration in registrations) {
      registration.runAction()
    }
  }

  override fun toString(): String {
    return String.format(
        Locale.US,
        "%s@%s[cancellationRequested=%s]",
        javaClass.name,
        Integer.toHexString(hashCode()),
        java.lang.Boolean.toString(isCancellationRequested))
  }

  // This method makes no attempt to perform any synchronization itself - you should ensure
  // accesses to this method are synchronized if you want to ensure correct behaviour in the
  // face of a concurrent invocation of the close method.
  private fun throwIfClosed() {
    check(!closed) { "Object already closed" }
  }

  // Performs no synchronization.
  private fun cancelScheduledCancellation() {
    val scheduledCancellation = this.scheduledCancellation ?: return
    scheduledCancellation.cancel(true)
    this.scheduledCancellation = null
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

/**
 * A function to be called after a task completes.
 *
 * If you wish to have the Task from a Continuation that does not return a Task be cancelled then
 * throw a [java.util.concurrent.CancellationException] from the Continuation.
 *
 * @see Task
 */
fun interface Continuation<TTaskResult, TContinuationResult> {
  /**
   * The function to be called when the task in the parameter is competed.
   *
   * @param task the completed task. See [Task.continueWith] for more details.
   * @return a nullable result of this function.
   */
  @Throws(Exception::class) fun then(task: Task<TTaskResult>): TContinuationResult?
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

/**
 * This is a wrapper class for emphasizing that task failed due to bad `Executor`, rather than the
 * continuation block it self.
 */
class ExecutorException(e: Exception) :
    RuntimeException("An exception was thrown by an Executor", e)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import java.util.concurrent.Callable
import java.util.concurrent.CancellationException
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock

/**
 * Represents the result of an asynchronous operation.
 *
 * @param <TResult> The type of the result of the task. </TResult>
 */
class Task<TResult> {
  /**
   * Interface for handlers invoked when a failed `Task` is about to be finalized, but the exception
   * has not been consumed.
   *
   * The handler will execute in the GC thread, so if the handler needs to do anything time
   * consuming or complex it is a good idea to fire off a `Task` to handle the exception.
   *
   * @see .unobservedExceptionHandler
   */
  fun interface UnobservedExceptionHandler {
    /**
     * Method invoked when the given task has an unobserved exception.
     *
     * Any exception thrown by this method will be ignored.
     *
     * @param t the task
     * @param e the exception
     */
    fun unobservedException(t: Task<*>, e: UnobservedTaskException)
  }

  private val lock = ReentrantLock()
  private val condition = lock.newCondition()
  private var completeField = false
  private var cancelledField = false
  private var resultField: TResult? = null
  private var errorField: Exception? = null
  private var errorHasBeenObserved = false
  private var unobservedErrorNotifier: UnobservedErrorNotifier? = null
  private var continuations: MutableList<Continuation<TResult, Void>>? = ArrayList()

  internal constructor()
  private constructor(result: TResult?) {
    trySetResult(result)
  }

  private constructor(cancelled: Boolean) {
    if (cancelled) {
      trySetCancelled()
    } else {
      trySetResult(null)
    }
  }

  /** `true` if the task completed (has a result, an error, or was cancelled. `false` otherwise. */
  val isCompleted: Boolean
    get() {
      return lock.withLock { completeField }
    }

  /** `true` if the task was cancelled, `false` otherwise. */
  val isCancelled: Boolean
    get() {
      return lock.withLock { cancelledField }
    }

  /** @return `true` if the task has an error, `false` otherwise. */
  val isFaulted: Boolean
    get() {
      return lock.withLock { errorField != null }
    }

  /** @return The result of the task, if set. `null` otherwise. */
  val result: TResult?
    get() {
      return lock.withLock { resultField }
    }

  /** @return The error for the task, if set. `null` otherwise. */
  val error: Exception?
    get() {
      return lock.withLock {
        if (errorField != null) {
          errorHasBeenObserved = true
          unobservedErrorNotifier?.apply {
            this.setObserved()
            unobservedErrorNotifier = null
          }
        }
        errorField
      }
    }

  /** Blocks until the task is complete. */
  @Throws(InterruptedException::class)
  fun waitForCompletion() {
    lock.withLock {
      if (!isCompleted) {
        condition.await()
      }
    }
  }

  /**
   * Blocks until the task is complete or times out.
   *
   * @return `true` if the task completed (has a result, an error, or was cancelled). `false`
   * otherwise.
   */
  @Throws(InterruptedException::class)
  fun waitForCompletion(duration: Long, timeUnit: TimeUnit): Boolean {
    return lock.withLock {
      if (!isCompleted) {
        condition.await(duration, timeUnit)
      }
      isCompleted
    }
  }

  /**
   * Makes a fluent cast of a Task's result possible, avoiding an extra continuation just to cast
   * the type of the result.
   */
  fun <TOut> cast(): Task<TOut> {
    @Suppress("unchecked") return this as Task<TOut>
  }

  /** Turns a Task<T> into a Task<Void>, dropping any result. </Void></T> */
  fun makeVoid(): Task<Void> {
    return this.continueWithTask<Void>(
        Continuation { task ->
          if (task.isCancelled) {
            return@Continuation cancelled<Void>()
          }
          if (task.isFaulted) {
            forError(task.error)
          } else forResult(null)
        })
  }

  /**
   * Continues a task with the equivalent of a Task-based while loop, where the body of the loop is
   * a task continuation.
   */
  fun continueWhile(
      predicate: Callable<Boolean>,
      continuation: Continuation<Void, Task<Void>>
  ): Task<Void> {
    return continueWhile(predicate, continuation, IMMEDIATE_EXECUTOR, null)
  }

  /**
   * Continues a task with the equivalent of a Task-based while loop, where the body of the loop is
   * a task continuation.
   */
  fun continueWhile(
      predicate: Callable<Boolean>,
      continuation: Continuation<Void, Task<Void>>,
      ct: CancellationToken?
  ): Task<Void> {
    return continueWhile(predicate, continuation, IMMEDIATE_EXECUTOR, ct)
  }
  /**
   * Continues a task with the equivalent of a Task-based while loop, where the body of the loop is
   * a task continuation.
   */
  /**
   * Continues a task with the equivalent of a Task-based while loop, where the body of the loop is
   * a task continuation.
   */
  /**
   * Continues a task with the equivalent of a Task-based while loop, where the body of the loop is
   * a task continuation.
   */
  fun continueWhile(
      predicate: Callable<Boolean>,
      continuation: Continuation<Void, Task<Void>>,
      executor: Executor = IMMEDIATE_EXECUTOR,
      ct: CancellationToken? = null
  ): Task<Void> {
    val predicateContinuation: Continuation<Void, Task<Void>> =
        object : Continuation<Void, Task<Void>> {
          @Throws(Exception::class)
          override fun then(task: Task<Void>): Task<Void> {
            if (ct != null && ct.isCancellationRequested) {
              return cancelled()
            }
            return if (predicate.call()) {
              forResult<Void>(null)
                  .onSuccessTask(continuation, executor)
                  .onSuccessTask(this, executor)
            } else forResult(null)
          }
        }
    return makeVoid().continueWithTask(predicateContinuation, executor)
  }

  /**
   * Adds a continuation that will be scheduled using the executor, returning a new task that
   * completes after the continuation has finished running. This allows the continuation to be
   * scheduled on different thread.
   */
  fun <TContinuationResult> continueWith(
      continuation: Continuation<TResult, TContinuationResult>,
      executor: Executor
  ): Task<TContinuationResult> {
    return continueWith(continuation, executor, null)
  }

  /**
   * Adds a continuation that will be scheduled using the executor, returning a new task that
   * completes after the continuation has finished running. This allows the continuation to be
   * scheduled on different thread.
   */
  fun <TContinuationResult> continueWith(
      continuation: Continuation<TResult, TContinuationResult>,
      executor: Executor,
      ct: CancellationToken?
  ): Task<TContinuationResult> {
    var completed: Boolean
    val tcs = TaskCompletionSource<TContinuationResult>()
    lock.withLock {
      completed = isCompleted
      if (!completed) {
        continuations?.add(
            Continuation { task ->
              completeImmediately(tcs, continuation, task, executor, ct)
              null
            })
      }
    }
    if (completed) {
      completeImmediately(tcs, continuation, this, executor, ct)
    }
    return tcs.task
  }

  /**
   * Adds a synchronous continuation to this task, returning a new task that completes after the
   * continuation has finished running.
   */
  fun <TContinuationResult> continueWith(
      continuation: Continuation<TResult, TContinuationResult>
  ): Task<TContinuationResult> {
    return continueWith(continuation, IMMEDIATE_EXECUTOR, null)
  }

  /**
   * Adds a synchronous continuation to this task, returning a new task that completes after the
   * continuation has finished running.
   */
  fun <TContinuationResult> continueWith(
      continuation: Continuation<TResult, TContinuationResult>,
      ct: CancellationToken?
  ): Task<TContinuationResult> {
    return continueWith(continuation, IMMEDIATE_EXECUTOR, ct)
  }

  /**
   * Adds an Task-based continuation to this task that will be scheduled using the executor,
   * returning a new task that completes after the task returned by the continuation has completed.
   */
  fun <TContinuationResult> continueWithTask(
      continuation: Continuation<TResult, Task<TContinuationResult>>,
      executor: Executor
  ): Task<TContinuationResult> {
    return continueWithTask(continuation, executor, null)
  }

  /**
   * Adds an Task-based continuation to this task that will be scheduled using the executor,
   * returning a new task that completes after the task returned by the continuation has completed.
   */
  fun <TContinuationResult> continueWithTask(
      continuation: Continuation<TResult, Task<TContinuationResult>>,
      executor: Executor,
      ct: CancellationToken?
  ): Task<TContinuationResult> {
    var completed: Boolean
    val tcs = TaskCompletionSource<TContinuationResult>()
    lock.withLock {
      completed = isCompleted
      if (!completed) {
        continuations?.add(
            Continuation { task ->
              completeAfterTask(tcs, continuation, task, executor, ct)
              null
            })
      }
    }
    if (completed) {
      completeAfterTask(tcs, continuation, this, executor, ct)
    }
    return tcs.task
  }

  /**
   * Adds an asynchronous continuation to this task, returning a new task that completes after the
   * task returned by the continuation has completed.
   */
  fun <TContinuationResult> continueWithTask(
      continuation: Continuation<TResult, Task<TContinuationResult>>
  ): Task<TContinuationResult> {
    return continueWithTask(continuation, IMMEDIATE_EXECUTOR, null)
  }

  /**
   * Adds an asynchronous continuation to this task, returning a new task that completes after the
   * task returned by the continuation has completed.
   */
  fun <TContinuationResult> continueWithTask(
      continuation: Continuation<TResult, Task<TContinuationResult>>,
      ct: CancellationToken?
  ): Task<TContinuationResult> {
    return continueWithTask(continuation, IMMEDIATE_EXECUTOR, ct)
  }

  /**
   * Runs a continuation when a task completes successfully, forwarding along [Exception] or
   * cancellation.
   */
  fun <TContinuationResult> onSuccess(
      continuation: Continuation<TResult, TContinuationResult>,
      executor: Executor
  ): Task<TContinuationResult> {
    return onSuccess(continuation, executor, null)
  }

  /**
   * Runs a continuation when a task completes successfully, forwarding along [Exception] or
   * cancellation.
   */
  fun <TContinuationResult> onSuccess(
      continuation: Continuation<TResult, TContinuationResult>,
      executor: Executor,
      ct: CancellationToken?
  ): Task<TContinuationResult> {
    return continueWithTask(
        Continuation { task ->
          if (ct != null && ct.isCancellationRequested) {
            return@Continuation cancelled()
          }
          if (task.isFaulted) {
            forError(task.error)
          } else if (task.isCancelled) {
            cancelled()
          } else {
            task.continueWith(continuation)
          }
        },
        executor)
  }

  /**
   * Runs a continuation when a task completes successfully, forwarding along [Exception]s or
   * cancellation.
   */
  fun <TContinuationResult> onSuccess(
      continuation: Continuation<TResult, TContinuationResult>
  ): Task<TContinuationResult> {
    return onSuccess(continuation, IMMEDIATE_EXECUTOR, null)
  }

  /**
   * Runs a continuation when a task completes successfully, forwarding along [Exception]s or
   * cancellation.
   */
  fun <TContinuationResult> onSuccess(
      continuation: Continuation<TResult, TContinuationResult>,
      ct: CancellationToken?
  ): Task<TContinuationResult> {
    return onSuccess(continuation, IMMEDIATE_EXECUTOR, ct)
  }

  /**
   * Runs a continuation when a task completes successfully, forwarding along [Exception]s or
   * cancellation.
   */
  fun <TContinuationResult> onSuccessTask(
      continuation: Continuation<TResult, Task<TContinuationResult>>,
      executor: Executor
  ): Task<TContinuationResult> {
    return onSuccessTask(continuation, executor, null)
  }

  /**
   * Runs a continuation when a task completes successfully, forwarding along [Exception]s or
   * cancellation.
   */
  fun <TContinuationResult> onSuccessTask(
      continuation: Continuation<TResult, Task<TContinuationResult>>,
      executor: Executor,
      ct: CancellationToken?
  ): Task<TContinuationResult> {
    return continueWithTask(
        Continuation { task ->
          if (ct != null && ct.isCancellationRequested) {
            return@Continuation cancelled()
          }
          if (task.isFaulted) {
            forError(task.error)
          } else if (task.isCancelled) {
            cancelled()
          } else {
            task.continueWithTask(continuation)
          }
        },
        executor)
  }

  /**
   * Runs a continuation when a task completes successfully, forwarding along [ ]s or cancellation.
   */
  fun <TContinuationResult> onSuccessTask(
      continuation: Continuation<TResult, Task<TContinuationResult>>
  ): Task<TContinuationResult> {
    return onSuccessTask(continuation, IMMEDIATE_EXECUTOR)
  }

  /**
   * Runs a continuation when a task completes successfully, forwarding along [ ]s or cancellation.
   */
  fun <TContinuationResult> onSuccessTask(
      continuation: Continuation<TResult, Task<TContinuationResult>>,
      ct: CancellationToken?
  ): Task<TContinuationResult> {
    return onSuccessTask(continuation, IMMEDIATE_EXECUTOR, ct)
  }

  private fun runContinuations() {
    lock.withLock {
      continuations?.forEach {
        try {
          it.then(this@Task)
        } catch (e: RuntimeException) {
          throw e
        } catch (e: Throwable) {
          throw RuntimeException(e)
        }
      }
      continuations = null
    }
  }

  /** Sets the cancelled flag on the Task if the Task hasn't already been completed. */
  /* package */
  fun trySetCancelled(): Boolean {
    lock.withLock {
      if (completeField) {
        return false
      }
      completeField = true
      cancelledField = true
      condition.signalAll()
      runContinuations()
      return true
    }
  }

  /** Sets the result on the Task if the Task hasn't already been completed. */
  /* package */
  fun trySetResult(result: TResult?): Boolean {
    lock.withLock {
      if (completeField) {
        return false
      }
      completeField = true
      this@Task.resultField = result
      condition.signalAll()
      runContinuations()
      return true
    }
  }

  /** Sets the error on the Task if the Task hasn't already been completed. */
  /* package */
  fun trySetError(error: Exception?): Boolean {
    lock.withLock {
      if (completeField) {
        return false
      }
      completeField = true
      this@Task.errorField = error
      errorHasBeenObserved = false
      condition.signalAll()
      runContinuations()
      if (!errorHasBeenObserved && unobservedExceptionHandler != null) {
        unobservedErrorNotifier = UnobservedErrorNotifier(this)
      }
      return true
    }
  }

  @Deprecated("Please use [TaskCompletionSource] instead. ")
  inner class TaskCompletionSource internal constructor() :
      com.facebook.bolts.TaskCompletionSource<TResult>()

  companion object {
    /** An [java.util.concurrent.Executor] that executes tasks in parallel. */
    @JvmField val BACKGROUND_EXECUTOR: ExecutorService = BoltsExecutors.background()

    /**
     * An [java.util.concurrent.Executor] that executes tasks in the current thread unless the stack
     * runs too deep, at which point it will delegate to [Task.BACKGROUND_EXECUTOR] in order to trim
     * the stack.
     */
    private val IMMEDIATE_EXECUTOR = BoltsExecutors.immediate()

    /** An [java.util.concurrent.Executor] that executes tasks on the UI thread. */
    @JvmField val UI_THREAD_EXECUTOR: Executor = AndroidExecutors.uiThread()

    // null unless explicitly set
    @Volatile private var unobservedExceptionHandler: UnobservedExceptionHandler? = null

    /** Returns the handler invoked when a task has an unobserved exception or `null`. */
    @JvmStatic
    fun getUnobservedExceptionHandler(): UnobservedExceptionHandler? = unobservedExceptionHandler

    /**
     * Set the handler invoked when a task has an unobserved exception.
     *
     * @param eh the object to use as an unobserved exception handler. If <tt>null</tt> then
     * unobserved exceptions will be ignored.
     */
    @JvmStatic
    fun setUnobservedExceptionHandler(eh: UnobservedExceptionHandler?) {
      unobservedExceptionHandler = eh
    }

    /** Creates a completed task with the given value. */
    @Suppress("unchecked")
    @JvmStatic
    fun <TResult> forResult(value: TResult?): Task<TResult> {
      if (value == null) {
        return TASK_NULL as Task<TResult>
      }
      if (value is Boolean) {
        return (if (value) TASK_TRUE else TASK_FALSE) as Task<TResult>
      }
      val tcs = TaskCompletionSource<TResult>()
      tcs.setResult(value)
      return tcs.task
    }

    /** Creates a faulted task with the given error. */
    @JvmStatic
    fun <TResult> forError(error: Exception?): Task<TResult> {
      val tcs = TaskCompletionSource<TResult>()
      tcs.setError(error)
      return tcs.task
    }

    /** Creates a cancelled task. */
    @Suppress("unchecked")
    @JvmStatic
    fun <TResult> cancelled(): Task<TResult> {
      return TASK_CANCELLED as Task<TResult>
    }

    /**
     * Creates a task that completes after a time delay.
     *
     * @param delay The number of milliseconds to wait before completing the returned task. Zero and
     * negative values are treated as requests for immediate execution.
     */
    @JvmStatic
    fun delay(delay: Long): Task<Void?> {
      return delay(delay, BoltsExecutors.scheduled(), null)
    }

    /**
     * Creates a task that completes after a time delay.
     *
     * @param delay The number of milliseconds to wait before completing the returned task. Zero and
     * negative values are treated as requests for immediate execution.
     * @param cancellationToken The optional cancellation token that will be checked prior to
     * completing the returned task.
     */
    @JvmStatic
    fun delay(delay: Long, cancellationToken: CancellationToken?): Task<Void?> {
      return delay(delay, BoltsExecutors.scheduled(), cancellationToken)
    }

    @JvmStatic
    internal fun delay(
        delay: Long,
        executor: ScheduledExecutorService,
        cancellationToken: CancellationToken?
    ): Task<Void?> {
      if (cancellationToken != null && cancellationToken.isCancellationRequested) {
        return cancelled()
      }
      if (delay <= 0) {
        return forResult(null)
      }
      val tcs = TaskCompletionSource<Void?>()
      val scheduled = executor.schedule({ tcs.trySetResult(null) }, delay, TimeUnit.MILLISECONDS)
      cancellationToken?.register {
        scheduled.cancel(true)
        tcs.trySetCancelled()
      }
      return tcs.task
    }

    /**
     * Invokes the callable on a background thread, returning a Task to represent the operation.
     *
     * If you want to cancel the resulting Task throw a [Exception] from the callable.
     */
    @JvmStatic
    fun <TResult> callInBackground(callable: Callable<TResult?>): Task<TResult> {
      return call(callable, BACKGROUND_EXECUTOR, null)
    }

    /** Invokes the callable on a background thread, returning a Task to represent the operation. */
    @JvmStatic
    fun <TResult> callInBackground(
        callable: Callable<TResult?>,
        ct: CancellationToken?
    ): Task<TResult> {
      return call(callable, BACKGROUND_EXECUTOR, ct)
    }

    /**
     * Invokes the callable using the given executor, returning a Task to represent the operation.
     *
     * If you want to cancel the resulting Task throw a [Exception] from the callable.
     */
    @JvmStatic
    fun <TResult> call(callable: Callable<TResult?>, executor: Executor): Task<TResult> {
      return call(callable, executor, null)
    }

    /**
     * Invokes the callable using the given executor, returning a Task to represent the operation.
     */
    @JvmStatic
    fun <TResult> call(
        callable: Callable<TResult?>,
        executor: Executor,
        ct: CancellationToken?
    ): Task<TResult> {
      val tcs = TaskCompletionSource<TResult>()
      try {
        executor.execute(
            Runnable {
              if (ct != null && ct.isCancellationRequested) {
                tcs.setCancelled()
                return@Runnable
              }
              try {
                tcs.setResult(callable.call())
              } catch (e: CancellationException) {
                tcs.setCancelled()
              } catch (e: Exception) {
                tcs.setError(e)
              }
            })
      } catch (e: Exception) {
        tcs.setError(ExecutorException(e))
      }
      return tcs.task
    }

    /**
     * Invokes the callable on the current thread, producing a Task.
     *
     * If you want to cancel the resulting Task throw a [Exception] from the callable.
     */
    @JvmStatic
    fun <TResult> call(callable: Callable<TResult?>): Task<TResult> {
      return call(callable, IMMEDIATE_EXECUTOR, null)
    }

    /** Invokes the callable on the current thread, producing a Task. */
    @JvmStatic
    fun <TResult> call(callable: Callable<TResult?>, ct: CancellationToken?): Task<TResult> {
      return call(callable, IMMEDIATE_EXECUTOR, ct)
    }

    /**
     * Creates a task that will complete when any of the supplied tasks have completed.
     *
     * The returned task will complete when any of the supplied tasks has completed. The returned
     * task will always end in the completed state with its result set to the first task to
     * complete. This is true even if the first task to complete ended in the canceled or faulted
     * state.
     *
     * @param tasks The tasks to wait on for completion.
     * @return A task that represents the completion of one of the supplied tasks. The return task's
     * result is the task that completed.
     */
    @JvmStatic
    fun <TResult> whenAnyResult(tasks: Collection<Task<TResult>>): Task<Task<TResult>> {
      if (tasks.isEmpty()) {
        return forResult(null)
      }
      val firstCompleted = TaskCompletionSource<Task<TResult>>()
      val isAnyTaskComplete = AtomicBoolean(false)
      for (task in tasks) {
        task.continueWith<Void>(
            Continuation {
              if (isAnyTaskComplete.compareAndSet(false, true)) {
                firstCompleted.setResult(it)
              } else {
                // ensure observed
                it.error
              }
              null
            })
      }
      return firstCompleted.task
    }

    /**
     * Creates a task that will complete when any of the supplied tasks have completed.
     *
     * The returned task will complete when any of the supplied tasks has completed. The returned
     * task will always end in the completed state with its result set to the first task to
     * complete. This is true even if the first task to complete ended in the canceled or faulted
     * state.
     *
     * @param tasks The tasks to wait on for completion.
     * @return A task that represents the completion of one of the supplied tasks. The return task's
     * Result is the task that completed.
     */
    @Suppress("unchecked")
    @JvmStatic
    fun whenAny(tasks: Collection<Task<*>>): Task<Task<*>> {
      if (tasks.isEmpty()) {
        return forResult(null)
      }
      val firstCompleted = TaskCompletionSource<Task<*>>()
      val isAnyTaskComplete = AtomicBoolean(false)
      for (task in tasks) {
        (task as Task<Any>).continueWith<Void>(
            Continuation<Any, Void> {
              if (isAnyTaskComplete.compareAndSet(false, true)) {
                firstCompleted.setResult(it)
              } else {
                // ensure observed
                it.error
              }
              null
            })
      }
      return firstCompleted.task
    }

    /**
     * Creates a task that completes when all of the provided tasks are complete.
     *
     * If any of the supplied tasks completes in a faulted state, the returned task will also
     * complete in a faulted state, where its exception will resolve to that [Exception] if a single
     * task fails or an [AggregateException] of all the [Exception]s if multiple tasks fail.
     *
     * If none of the supplied tasks faulted but at least one of them was cancelled, the returned
     * task will end as cancelled.
     *
     * If none of the tasks faulted and none of the tasks were cancelled, the resulting task will
     * end completed. The result of the returned task will be set to a list containing all of the
     * results of the supplied tasks in the same order as they were provided (e.g. if the input
     * tasks collection contained t1, t2, t3, the output task's result will return an
     * `List<TResult>` where `list.get(0) == t1.getResult(), list.get(1) == t2.getResult(),
     * and list.get(2) == t3.getResult()`).
     *
     * If the supplied collection contains no tasks, the returned task will immediately transition
     * to a completed state before it's returned to the caller. The returned `List<TResult>`
     * will contain 0 elements.
     *
     * @param tasks The tasks that the return value will wait for before completing.
     * @return A Task that will resolve to `List<TResult>` when all the tasks are resolved.
     */
    @JvmStatic
    fun <TResult> whenAllResult(tasks: Collection<Task<TResult>>): Task<List<TResult?>> {
      return whenAll(tasks)
          .onSuccess(
              object : Continuation<Void, List<TResult?>> {
                override fun then(task: Task<Void>): List<TResult?> {
                  if (tasks.isEmpty()) {
                    return emptyList()
                  }
                  val results: MutableList<TResult?> = ArrayList<TResult?>()
                  for (individualTask in tasks) {
                    results.add(individualTask.result)
                  }
                  return results
                }
              })
    }

    /**
     * Creates a task that completes when all of the provided tasks are complete.
     *
     * If any of the supplied tasks completes in a faulted state, the returned task will also
     * complete in a faulted state, where its exception will resolve to that [ ] if a single task
     * fails or an [AggregateException] of all the [ ]s if multiple tasks fail.
     *
     * If none of the supplied tasks faulted but at least one of them was cancelled, the returned
     * task will end as cancelled.
     *
     * If none of the tasks faulted and none of the tasks were canceled, the resulting task will end
     * in the completed state.
     *
     * If the supplied collection contains no tasks, the returned task will immediately transition
     * to a completed state before it's returned to the caller.
     *
     * @param tasks The tasks that the return value will wait for before completing.
     * @return A Task that will resolve to `Void` when all the tasks are resolved.
     */
    @Suppress("unchecked")
    @JvmStatic
    fun whenAll(tasks: Collection<Task<*>>): Task<Void> {
      if (tasks.isEmpty()) {
        return forResult(null)
      }
      val allFinished = TaskCompletionSource<Void>()
      val causes = ArrayList<Exception?>()
      val errorLock = ReentrantLock()
      val count = AtomicInteger(tasks.size)
      val isCancelled = AtomicBoolean(false)
      for (task in tasks) {
        val t = task as Task<Any>
        t.continueWith {
          if (it.isFaulted) {
            errorLock.withLock { causes.add(it.error) }
          }
          if (it.isCancelled) {
            isCancelled.set(true)
          }
          if (count.decrementAndGet() == 0) {
            if (causes.size != 0) {
              if (causes.size == 1) {
                allFinished.setError(causes[0])
              } else {
                val error: Exception =
                    AggregateException(
                        String.format("There were %d exceptions.", causes.size), causes)
                allFinished.setError(error)
              }
            } else if (isCancelled.get()) {
              allFinished.setCancelled()
            } else {
              allFinished.setResult(null)
            }
          }
          null
        }
      }
      return allFinished.task
    }

    /**
     * Handles the non-async (i.e. the continuation doesn't return a Task) continuation case,
     * passing the results of the given Task through to the given continuation and using the results
     * of that call to set the result of the TaskContinuationSource.
     *
     * @param tcs The TaskContinuationSource that will be orchestrated by this call.
     * @param continuation The non-async continuation.
     * @param task The task being completed.
     * @param executor The executor to use when running the continuation (allowing the continuation
     * to be scheduled on a different thread).
     */
    private fun <TContinuationResult, TResult> completeImmediately(
        tcs: com.facebook.bolts.TaskCompletionSource<TContinuationResult>,
        continuation: Continuation<TResult, TContinuationResult>,
        task: Task<TResult>,
        executor: Executor,
        ct: CancellationToken?
    ) {
      try {
        executor.execute(
            Runnable {
              if (ct != null && ct.isCancellationRequested) {
                tcs.setCancelled()
                return@Runnable
              }
              try {
                val result = continuation.then(task)
                tcs.setResult(result)
              } catch (e: CancellationException) {
                tcs.setCancelled()
              } catch (e: Exception) {
                tcs.setError(e)
              }
            })
      } catch (e: Exception) {
        tcs.setError(ExecutorException(e))
      }
    }

    /**
     * Handles the async (i.e. the continuation does return a Task) continuation case, passing the
     * results of the given Task through to the given continuation to get a new Task. The
     * TaskCompletionSource's results are only set when the new Task has completed, unwrapping the
     * results of the task returned by the continuation.
     *
     * @param tcs The TaskContinuationSource that will be orchestrated by this call.
     * @param continuation The async continuation.
     * @param task The task being completed.
     * @param executor The executor to use when running the continuation (allowing the continuation
     * to be scheduled on a different thread).
     */
    private fun <TContinuationResult, TResult> completeAfterTask(
        tcs: com.facebook.bolts.TaskCompletionSource<TContinuationResult>,
        continuation: Continuation<TResult, Task<TContinuationResult>>,
        task: Task<TResult>,
        executor: Executor,
        ct: CancellationToken?
    ) {
      try {
        executor.execute(
            Runnable {
              if (ct != null && ct.isCancellationRequested) {
                tcs.setCancelled()
                return@Runnable
              }
              try {
                val result = continuation.then(task)
                result?.continueWith<Void>(
                    Continuation { task ->
                      if (ct != null && ct.isCancellationRequested) {
                        tcs.setCancelled()
                        return@Continuation null
                      }
                      if (task.isCancelled) {
                        tcs.setCancelled()
                      } else if (task.isFaulted) {
                        tcs.setError(task.error)
                      } else {
                        tcs.setResult(task.result)
                      }
                      null
                    })
                    ?: tcs.setResult(null)
              } catch (e: CancellationException) {
                tcs.setCancelled()
              } catch (e: Exception) {
                tcs.setError(e)
              }
            })
      } catch (e: Exception) {
        tcs.setError(ExecutorException(e))
      }
    }

    private val TASK_NULL: Task<*> = Task(null)
    private val TASK_TRUE = Task(result = true)
    private val TASK_FALSE = Task(result = false)
    private val TASK_CANCELLED: Task<*> = Task<Any>(true)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

/**
 * Allows safe orchestration of a task's completion, preventing the consumer from prematurely
 * completing the task. Essentially, it represents the producer side of a Task<TResult>, providing
 * access to the consumer side through the getTask() method while isolating the Task's completion
 * mechanisms from the consumer. </TResult>
 */
open class TaskCompletionSource<TResult> {
  /** @return the Task associated with this TaskCompletionSource. */
  val task: Task<TResult> = Task()

  /** Sets the cancelled flag on the Task if the Task hasn't already been completed. */
  fun trySetCancelled(): Boolean {
    return task.trySetCancelled()
  }

  /** Sets the result on the Task if the Task hasn't already been completed. */
  fun trySetResult(result: TResult?): Boolean {
    return task.trySetResult(result)
  }

  /** Sets the error on the Task if the Task hasn't already been completed. */
  fun trySetError(error: Exception?): Boolean {
    return task.trySetError(error)
  }

  /** Sets the cancelled flag on the task, throwing if the Task has already been completed. */
  fun setCancelled() {
    check(trySetCancelled()) { "Cannot cancel a completed task." }
  }

  /** Sets the result of the Task, throwing if the Task has already been completed. */
  fun setResult(result: TResult?) {
    check(trySetResult(result)) { "Cannot set the result of a completed task." }
  }

  /** Sets the error of the Task, throwing if the Task has already been completed. */
  fun setError(error: Exception?) {
    check(trySetError(error)) { "Cannot set the error on a completed task." }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import androidx.annotation.VisibleForTesting

/**
 * This class is used to retain a faulted task until either its error is observed or it is
 * finalized. If it is finalized with a task, then the uncaught exception handler is executed with
 * an UnobservedTaskException.
 */
internal class UnobservedErrorNotifier(private var task: Task<*>?) {
  @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
  fun finalize() {
    val faultedTask = task
    if (faultedTask != null) {
      val ueh = Task.getUnobservedExceptionHandler()
      ueh?.unobservedException(faultedTask, UnobservedTaskException(faultedTask.error))
    }
  }

  fun setObserved() {
    task = null
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import java.lang.RuntimeException

/** Used to signify that a Task's error went unobserved. */
class UnobservedTaskException(cause: Throwable?) : RuntimeException(cause)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import com.facebook.FacebookTestCase
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

class AggregateExceptionTest : FacebookTestCase() {

  @Test
  fun `test AggregateException pass the first throwable as cause`() {
    val throwables = listOf<Throwable>(RuntimeException("first"), RuntimeException("second"))
    val exception = AggregateException("unknown", throwables)
    assertThat(exception.cause?.message).isEqualTo("first")
  }

  @Test
  fun `test null as throwables`() {
    val exception = AggregateException("unknown", null)
    assertThat(exception.cause).isNull()
  }

  @Test
  fun `test null as throwables elements`() {
    val exception = AggregateException("unknown", listOf(null))
    assertThat(exception.cause).isNull()
    val testBuffer = ByteArrayOutputStream()
    val testPrintStream = PrintStream(testBuffer)
    exception.printStackTrace(testPrintStream)
  }

  @Test
  fun `test all throwables are printed`() {
    val throwables = listOf<Throwable>(RuntimeException("first%"), RuntimeException("second^"))
    val exception = AggregateException("unknown", throwables)
    val testBuffer = ByteArrayOutputStream()
    val testPrintStream = PrintStream(testBuffer)
    exception.printStackTrace(testPrintStream)
    testPrintStream.close()
    val printedData = testBuffer.toString()
    assertThat(printedData).contains("first%")
    assertThat(printedData).contains("second^")
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import android.os.Handler
import android.os.Looper
import com.facebook.FacebookPowerMockTestCase
import java.util.concurrent.ThreadPoolExecutor
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest

@PrepareForTest(AndroidExecutors::class, Handler::class, Looper::class)
class AndroidExecutorsTest : FacebookPowerMockTestCase() {
  private lateinit var mockHandler: Handler
  private lateinit var mockLooper: Looper

  override fun setup() {
    super.setup()
    mockHandler = mock()
    mockLooper = mock()
    PowerMockito.whenNew(Handler::class.java).withArguments(mockLooper).thenReturn(mockHandler)
    PowerMockito.mockStatic(Looper::class.java)
    whenever(Looper.getMainLooper()).thenReturn(mockLooper)
  }

  @Test
  fun `test ui thread executor`() {
    val mockCommand = mock<Runnable>()
    AndroidExecutors.uiThread().execute(mockCommand)
    PowerMockito.verifyNew(Handler::class.java).withArguments(mockLooper)
    verify(mockHandler).post(mockCommand)
  }

  @Test
  fun `test cached thread pool allows core thread timeout`() {
    val executor = AndroidExecutors.newCachedThreadPool() as ThreadPoolExecutor
    assertThat(executor.allowsCoreThreadTimeOut()).isTrue
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import android.content.Intent
import android.os.Bundle
import com.facebook.FacebookTestCase
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

class AppLinksTest : FacebookTestCase() {
  companion object {
    private const val TEST_KEY = "test key"
    private const val TEST_DATA = "test value"
  }

  @Test
  fun `test get app link data`() {
    val intent = Intent()
    val bundle = Bundle()
    val extraBundle = Bundle()
    extraBundle.putString(TEST_KEY, TEST_DATA)
    bundle.putBundle(AppLinks.KEY_NAME_EXTRAS, extraBundle)
    intent.putExtra(AppLinks.KEY_NAME_APPLINK_DATA, bundle)

    assertThat(AppLinks.getAppLinkData(intent)).isEqualTo(bundle)
    assertThat(AppLinks.getAppLinkExtras(intent)?.getString(TEST_KEY)).isEqualTo(TEST_DATA)
  }

  @Test
  fun `test empty intent won't crash`() {
    val intent = Intent()
    assertThat(AppLinks.getAppLinkExtras(intent)).isNull()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.bolts

import com.facebook.FacebookPowerMockTestCase
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.mockito.kotlin.mock

class AppLinkTest : FacebookPowerMockTestCase() {
  @Test
  fun `test constructor with null targets`() {
    val appLink = AppLink(mock(), null, mock())
    assertThat(appLink.targets).isEmpty()
  }

  @Test(expected = UnsupportedOperationException::class)
  fun `test targets getter return an unmodifiable copy`() {
    val targets = arrayListOf<AppLink.Target>(mock(), mock())
    val appLink = AppLink(mock(), targets, mock())
    val gotTargets = appLink.targets as MutableList<AppLink.Target>
    gotTargets.removeAt(0)
  }
}