diff --git a/libsession/src/main/java/org/session/libsession/utilities/Address.kt b/libsession/src/main/java/org/session/libsession/utilities/Address.kt new file mode 100644 index 000000000..3278907bd --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/utilities/Address.kt @@ -0,0 +1,185 @@ +package org.session.libsession.utilities + +import android.content.Context +import android.os.Parcel +import android.os.Parcelable +import android.util.Pair +import androidx.annotation.VisibleForTesting +import org.session.libsession.utilities.DelimiterUtil +import org.session.libsession.utilities.GroupUtil +import org.session.libsignal.utilities.guava.Optional +import org.session.libsignal.utilities.Util +import java.util.* +import java.util.concurrent.atomic.AtomicReference +import java.util.regex.Matcher +import java.util.regex.Pattern + +class Address private constructor(address: String) : Parcelable, Comparable { + private val address: String = address.toLowerCase() + + constructor(`in`: Parcel) : this(`in`.readString()!!) {} + + val isGroup: Boolean + get() = GroupUtil.isEncodedGroup(address) + val isClosedGroup: Boolean + get() = GroupUtil.isClosedGroup(address) + val isOpenGroup: Boolean + get() = GroupUtil.isOpenGroup(address) + val isContact: Boolean + get() = !isGroup + + fun contactIdentifier(): String { + if (!isContact && !isOpenGroup) { + if (isGroup) throw AssertionError("Not e164, is group") + throw AssertionError("Not e164, unknown") + } + return address + } + + fun toGroupString(): String { + if (!isGroup) throw AssertionError("Not group") + return address + } + + override fun toString(): String { + return address + } + + fun serialize(): String { + return address + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return if (other == null || other !is Address) false else address == other.address + } + + override fun hashCode(): Int { + return address.hashCode() + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(address) + } + + override fun compareTo(other: Address?): Int { + return address.compareTo(other?.address!!) + } + + @VisibleForTesting + class ExternalAddressFormatter internal constructor(localCountryCode: String, countryCode: Boolean) { + private val localNumber: Optional + private val localCountryCode: String + private val ALPHA_PATTERN = Pattern.compile("[a-zA-Z]") + fun format(number: String?): String { + return number ?: "Unknown" + } + + private fun parseAreaCode(e164Number: String, countryCode: Int): String? { + when (countryCode) { + 1 -> return e164Number.substring(2, 5) + 55 -> return e164Number.substring(3, 5) + } + return null + } + + private fun applyAreaCodeRules(localNumber: Optional, testNumber: String): String { + if (!localNumber.isPresent || !localNumber.get().areaCode.isPresent) { + return testNumber + } + val matcher: Matcher + when (localNumber.get().countryCode) { + 1 -> { + matcher = US_NO_AREACODE.matcher(testNumber) + if (matcher.matches()) { + return localNumber.get().areaCode.toString() + matcher.group() + } + } + 55 -> { + matcher = BR_NO_AREACODE.matcher(testNumber) + if (matcher.matches()) { + return localNumber.get().areaCode.toString() + matcher.group() + } + } + } + return testNumber + } + + private class PhoneNumber internal constructor(val e164Number: String, val countryCode: Int, areaCode: String?) { + val areaCode: Optional + + init { + this.areaCode = Optional.fromNullable(areaCode) + } + } + + companion object { + private val TAG = ExternalAddressFormatter::class.java.simpleName + private val SHORT_COUNTRIES: HashSet = object : HashSet() { + init { + add("NU") + add("TK") + add("NC") + add("AC") + } + } + private val US_NO_AREACODE = Pattern.compile("^(\\d{7})$") + private val BR_NO_AREACODE = Pattern.compile("^(9?\\d{8})$") + } + + init { + localNumber = Optional.absent() + this.localCountryCode = localCountryCode + } + } + + companion object { + @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(`in`: Parcel): Address { + return Address(`in`) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + val UNKNOWN = Address("Unknown") + private val TAG = Address::class.java.simpleName + private val cachedFormatter = AtomicReference>() + + @JvmStatic + fun fromSerialized(serialized: String): Address { + return Address(serialized) + } + + @JvmStatic + fun fromExternal(context: Context, external: String?): Address { + return fromSerialized(external!!) + } + + @JvmStatic + fun fromSerializedList(serialized: String, delimiter: Char): List
{ + val escapedAddresses = DelimiterUtil.split(serialized, delimiter) + val addresses: MutableList
= LinkedList() + for (escapedAddress in escapedAddresses) { + addresses.add(fromSerialized(DelimiterUtil.unescape(escapedAddress, delimiter))) + } + return addresses + } + + @JvmStatic + fun toSerializedList(addresses: List
, delimiter: Char): String { + Collections.sort(addresses) + val escapedAddresses: MutableList = LinkedList() + for (address in addresses) { + escapedAddresses.add(DelimiterUtil.escape(address.serialize(), delimiter)) + } + return Util.join(escapedAddresses, delimiter.toString() + "") + } + } + +} \ No newline at end of file