From 9ae6b20fd15ca1193dfd598202937cb22dd9e7a0 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sun, 12 Nov 2023 11:54:37 -0700 Subject: [PATCH] music: decouple settings somewhat Try to decouple the stateful music settings object from the stateless internals of the music loader. This should make unit testing far easier. --- .../oxycblt/auxio/music/MusicRepository.kt | 25 +++- .../auxio/music/device/DeviceLibrary.kt | 13 +- .../org/oxycblt/auxio/music/fs/FsModule.kt | 5 +- .../auxio/music/fs/MediaStoreExtractor.kt | 56 ++++---- .../java/org/oxycblt/auxio/music/info/Name.kt | 39 ++---- .../auxio/music/metadata/Separators.kt | 14 +- .../oxycblt/auxio/music/user/UserLibrary.kt | 22 ++- .../org/oxycblt/auxio/music/info/NameTest.kt | 126 ++++++++---------- 8 files changed, 133 insertions(+), 167 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt index d5263da7b..55cfeaf0a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt @@ -36,6 +36,8 @@ import org.oxycblt.auxio.music.cache.CacheRepository import org.oxycblt.auxio.music.device.DeviceLibrary import org.oxycblt.auxio.music.device.RawSong import org.oxycblt.auxio.music.fs.MediaStoreExtractor +import org.oxycblt.auxio.music.info.Name +import org.oxycblt.auxio.music.metadata.Separators import org.oxycblt.auxio.music.metadata.TagExtractor import org.oxycblt.auxio.music.user.MutableUserLibrary import org.oxycblt.auxio.music.user.UserLibrary @@ -223,7 +225,8 @@ constructor( private val mediaStoreExtractor: MediaStoreExtractor, private val tagExtractor: TagExtractor, private val deviceLibraryFactory: DeviceLibrary.Factory, - private val userLibraryFactory: UserLibrary.Factory + private val userLibraryFactory: UserLibrary.Factory, + private val musicSettings: MusicSettings ) : MusicRepository { private val updateListeners = mutableListOf() private val indexingListeners = mutableListOf() @@ -356,6 +359,8 @@ constructor( } private suspend fun indexImpl(worker: MusicRepository.IndexingWorker, withCache: Boolean) { + // TODO: Find a way to break up this monster of a method, preferably as another class. + val start = System.currentTimeMillis() // Make sure we have permissions before going forward. Theoretically this would be better // done at the UI level, but that intertwines logic and display too much. @@ -365,6 +370,17 @@ constructor( throw NoAudioPermissionException() } + // Obtain configuration information + val constraints = + MediaStoreExtractor.Constraints(musicSettings.excludeNonMusic, musicSettings.musicDirs) + val separators = Separators.from(musicSettings.separators) + val nameFactory = + if (musicSettings.intelligentSorting) { + Name.Known.IntelligentFactory + } else { + Name.Known.SimpleFactory + } + // Begin with querying MediaStore and the music cache. The former is needed for Auxio // to figure out what songs are (probably) on the device, and the latter will be needed // for discovery (described later). These have no shared state, so they are done in @@ -376,7 +392,7 @@ constructor( worker.scope.async { val query = try { - mediaStoreExtractor.query() + mediaStoreExtractor.query(constraints) } catch (e: Exception) { // Normally, errors in an async call immediately bubble up to the Looper // and crash the app. Thus, we have to wrap any error into a Result @@ -445,7 +461,8 @@ constructor( worker.scope.async(Dispatchers.Default) { val deviceLibrary = try { - deviceLibraryFactory.create(completeSongs, processedSongs) + deviceLibraryFactory.create( + completeSongs, processedSongs, separators, nameFactory) } catch (e: Exception) { processedSongs.close(e) return@async Result.failure(e) @@ -518,7 +535,7 @@ constructor( logD("Awaiting DeviceLibrary creation") val deviceLibrary = deviceLibraryJob.await().getOrThrow() logD("Starting UserLibrary creation") - val userLibrary = userLibraryFactory.create(rawPlaylists, deviceLibrary) + val userLibrary = userLibraryFactory.create(rawPlaylists, deviceLibrary, nameFactory) // Loading process is functionally done, indicate such logD( diff --git a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceLibrary.kt b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceLibrary.kt index 527dcd198..c694a65ea 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceLibrary.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceLibrary.kt @@ -28,7 +28,6 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicRepository -import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.fs.contentResolverSafe import org.oxycblt.auxio.music.fs.useQuery @@ -110,19 +109,19 @@ interface DeviceLibrary { suspend fun create( rawSongs: Channel, processedSongs: Channel, + separators: Separators, + nameFactory: Name.Known.Factory ): DeviceLibraryImpl } } -class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: MusicSettings) : - DeviceLibrary.Factory { +class DeviceLibraryFactoryImpl @Inject constructor() : DeviceLibrary.Factory { override suspend fun create( rawSongs: Channel, - processedSongs: Channel + processedSongs: Channel, + separators: Separators, + nameFactory: Name.Known.Factory ): DeviceLibraryImpl { - val nameFactory = Name.Known.Factory.from(musicSettings) - val separators = Separators.from(musicSettings) - val songGrouping = mutableMapOf() val albumGrouping = mutableMapOf>() val artistGrouping = mutableMapOf>() diff --git a/app/src/main/java/org/oxycblt/auxio/music/fs/FsModule.kt b/app/src/main/java/org/oxycblt/auxio/music/fs/FsModule.kt index 10c4192bc..828a468da 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/fs/FsModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/fs/FsModule.kt @@ -24,12 +24,11 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import org.oxycblt.auxio.music.MusicSettings @Module @InstallIn(SingletonComponent::class) class FsModule { @Provides - fun mediaStoreExtractor(@ApplicationContext context: Context, musicSettings: MusicSettings) = - MediaStoreExtractor.from(context, musicSettings) + fun mediaStoreExtractor(@ApplicationContext context: Context) = + MediaStoreExtractor.from(context) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt index 76e62e897..392103d80 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt @@ -29,7 +29,6 @@ import androidx.core.database.getStringOrNull import java.io.File import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.yield -import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.cache.Cache import org.oxycblt.auxio.music.device.RawSong import org.oxycblt.auxio.music.info.Date @@ -50,9 +49,11 @@ interface MediaStoreExtractor { /** * Query the media database. * + * @param constraints Configuration parameter to restrict what music should be ignored when + * querying. * @return A new [Query] returned from the media database. */ - suspend fun query(): Query + suspend fun query(constraints: Constraints): Query /** * Consume the [Cursor] loaded after [query]. @@ -84,46 +85,44 @@ interface MediaStoreExtractor { fun populateTags(rawSong: RawSong) } + data class Constraints(val excludeNonMusic: Boolean, val musicDirs: MusicDirectories) + companion object { /** * Create a framework-backed instance. * * @param context [Context] required. - * @param musicSettings [MusicSettings] required. * @return A new [MediaStoreExtractor] that will work best on the device's API level. */ - fun from(context: Context, musicSettings: MusicSettings): MediaStoreExtractor = + fun from(context: Context): MediaStoreExtractor = when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> - Api30MediaStoreExtractor(context, musicSettings) - Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> - Api29MediaStoreExtractor(context, musicSettings) - else -> Api21MediaStoreExtractor(context, musicSettings) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Api30MediaStoreExtractor(context) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Api29MediaStoreExtractor(context) + else -> Api21MediaStoreExtractor(context) } } } -private abstract class BaseMediaStoreExtractor( - protected val context: Context, - private val musicSettings: MusicSettings -) : MediaStoreExtractor { - final override suspend fun query(): MediaStoreExtractor.Query { +private abstract class BaseMediaStoreExtractor(protected val context: Context) : + MediaStoreExtractor { + final override suspend fun query( + constraints: MediaStoreExtractor.Constraints + ): MediaStoreExtractor.Query { val start = System.currentTimeMillis() val args = mutableListOf() var selector = BASE_SELECTOR // Filter out audio that is not music, if enabled. - if (musicSettings.excludeNonMusic) { + if (constraints.excludeNonMusic) { logD("Excluding non-music") selector += " AND ${MediaStore.Audio.AudioColumns.IS_MUSIC}=1" } // Set up the projection to follow the music directory configuration. - val dirs = musicSettings.musicDirs - if (dirs.dirs.isNotEmpty()) { + if (constraints.musicDirs.dirs.isNotEmpty()) { selector += " AND " - if (!dirs.shouldInclude) { + if (!constraints.musicDirs.shouldInclude) { logD("Excluding directories in selector") // Without a NOT, the query will be restricted to the specified paths, resulting // in the "Include" mode. With a NOT, the specified paths will not be included, @@ -134,10 +133,10 @@ private abstract class BaseMediaStoreExtractor( // Specifying the paths to filter is version-specific, delegate to the concrete // implementations. - for (i in dirs.dirs.indices) { - if (addDirToSelector(dirs.dirs[i], args)) { + for (i in constraints.musicDirs.dirs.indices) { + if (addDirToSelector(constraints.musicDirs.dirs[i], args)) { selector += - if (i < dirs.dirs.lastIndex) { + if (i < constraints.musicDirs.dirs.lastIndex) { "$dirSelectorTemplate OR " } else { dirSelectorTemplate @@ -362,8 +361,7 @@ private abstract class BaseMediaStoreExtractor( // Note: The separation between version-specific backends may not be the cleanest. To preserve // speed, we only want to add redundancy on known issues, not with possible issues. -private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSettings) : - BaseMediaStoreExtractor(context, musicSettings) { +private class Api21MediaStoreExtractor(context: Context) : BaseMediaStoreExtractor(context) { override val projection: Array get() = super.projection + @@ -447,10 +445,8 @@ private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSet * @author Alexander Capehart (OxygenCobalt) */ @RequiresApi(Build.VERSION_CODES.Q) -private abstract class BaseApi29MediaStoreExtractor( - context: Context, - musicSettings: MusicSettings -) : BaseMediaStoreExtractor(context, musicSettings) { +private abstract class BaseApi29MediaStoreExtractor(context: Context) : + BaseMediaStoreExtractor(context) { override val projection: Array get() = super.projection + @@ -512,8 +508,7 @@ private abstract class BaseApi29MediaStoreExtractor( * @author Alexander Capehart (OxygenCobalt) */ @RequiresApi(Build.VERSION_CODES.Q) -private class Api29MediaStoreExtractor(context: Context, musicSettings: MusicSettings) : - BaseApi29MediaStoreExtractor(context, musicSettings) { +private class Api29MediaStoreExtractor(context: Context) : BaseApi29MediaStoreExtractor(context) { override val projection: Array get() = super.projection + arrayOf(MediaStore.Audio.AudioColumns.TRACK) @@ -553,8 +548,7 @@ private class Api29MediaStoreExtractor(context: Context, musicSettings: MusicSet * @author Alexander Capehart (OxygenCobalt) */ @RequiresApi(Build.VERSION_CODES.R) -private class Api30MediaStoreExtractor(context: Context, musicSettings: MusicSettings) : - BaseApi29MediaStoreExtractor(context, musicSettings) { +private class Api30MediaStoreExtractor(context: Context) : BaseApi29MediaStoreExtractor(context) { override val projection: Array get() = super.projection + diff --git a/app/src/main/java/org/oxycblt/auxio/music/info/Name.kt b/app/src/main/java/org/oxycblt/auxio/music/info/Name.kt index 09f4d8035..30626f01e 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/info/Name.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/info/Name.kt @@ -23,12 +23,11 @@ import androidx.annotation.StringRes import androidx.annotation.VisibleForTesting import java.text.CollationKey import java.text.Collator -import org.oxycblt.auxio.music.MusicSettings /** * The name of a music item. * - * This class automatically implements + * This class automatically implements advanced sorting heuristics for music naming, * * @author Alexander Capehart */ @@ -80,7 +79,7 @@ sealed interface Name : Comparable { is Unknown -> 1 } - interface Factory { + sealed interface Factory { /** * Create a new instance of [Name.Known] * @@ -88,22 +87,16 @@ sealed interface Name : Comparable { * @param sort The raw sort name obtained from the music item */ fun parse(raw: String, sort: String?): Known + } - companion object { - /** - * Creates a new instance from the **current state** of the given [MusicSettings]'s - * user-defined name configuration. - * - * @param settings The [MusicSettings] to use. - * @return A [Factory] instance reflecting the configuration state. - */ - fun from(settings: MusicSettings) = - if (settings.intelligentSorting) { - IntelligentKnownName.Factory - } else { - SimpleKnownName.Factory - } - } + /** Produces a simple [Known] with basic sorting heuristics that are locale-independent. */ + data object SimpleFactory : Factory { + override fun parse(raw: String, sort: String?) = SimpleKnownName(raw, sort) + } + + /** Produces an intelligent [Known] with advanced, but more fragile heuristics. */ + data object IntelligentFactory : Factory { + override fun parse(raw: String, sort: String?) = IntelligentKnownName(raw, sort) } } @@ -137,7 +130,6 @@ private val punctRegex by lazy { Regex("[\\p{Punct}+]") } * * @author Alexander Capehart (OxygenCobalt) */ -@VisibleForTesting data class SimpleKnownName(override val raw: String, override val sort: String?) : Name.Known() { override val sortTokens = listOf(parseToken(sort ?: raw)) @@ -148,10 +140,6 @@ data class SimpleKnownName(override val raw: String, override val sort: String?) // Always use lexicographic mode since we aren't parsing any numeric components return SortToken(collationKey, SortToken.Type.LEXICOGRAPHIC) } - - data object Factory : Name.Known.Factory { - override fun parse(raw: String, sort: String?) = SimpleKnownName(raw, sort) - } } /** @@ -159,7 +147,6 @@ data class SimpleKnownName(override val raw: String, override val sort: String?) * * @author Alexander Capehart (OxygenCobalt) */ -@VisibleForTesting data class IntelligentKnownName(override val raw: String, override val sort: String?) : Name.Known() { override val sortTokens = parseTokens(sort ?: raw) @@ -208,10 +195,6 @@ data class IntelligentKnownName(override val raw: String, override val sort: Str } } - data object Factory : Name.Known.Factory { - override fun parse(raw: String, sort: String?) = IntelligentKnownName(raw, sort) - } - companion object { private val TOKEN_REGEX by lazy { Regex("(\\d+)|(\\D+)") } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/metadata/Separators.kt b/app/src/main/java/org/oxycblt/auxio/music/metadata/Separators.kt index 8d2740e74..678e1ef2f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/metadata/Separators.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/metadata/Separators.kt @@ -18,9 +18,6 @@ package org.oxycblt.auxio.music.metadata -import androidx.annotation.VisibleForTesting -import org.oxycblt.auxio.music.MusicSettings - /** * Defines the user-specified parsing of multi-value tags. This should be used to parse any tags * that may be delimited with a separator character. @@ -45,15 +42,12 @@ interface Separators { const val AND = '&' /** - * Creates a new instance from the **current state** of the given [MusicSettings]'s - * user-defined separator configuration. + * Creates a new instance from a string of separator characters to use. * - * @param settings The [MusicSettings] to use. - * @return A new [Separators] instance reflecting the configuration state. + * @param chars The separator characters to use. Each character in the string will be + * checked for when splitting a string list. + * @return A new [Separators] instance reflecting the separators. */ - fun from(settings: MusicSettings) = from(settings.separators) - - @VisibleForTesting fun from(chars: String) = if (chars.isNotEmpty()) { CharSeparators(chars.toSet()) diff --git a/app/src/main/java/org/oxycblt/auxio/music/user/UserLibrary.kt b/app/src/main/java/org/oxycblt/auxio/music/user/UserLibrary.kt index faae9594b..70943b7d3 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/user/UserLibrary.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/user/UserLibrary.kt @@ -22,7 +22,6 @@ import java.lang.Exception import javax.inject.Inject import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicRepository -import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.device.DeviceLibrary @@ -82,7 +81,8 @@ interface UserLibrary { */ suspend fun create( rawPlaylists: List, - deviceLibrary: DeviceLibrary + deviceLibrary: DeviceLibrary, + nameFactory: Name.Known.Factory ): MutableUserLibrary } } @@ -139,9 +139,7 @@ interface MutableUserLibrary : UserLibrary { suspend fun rewritePlaylist(playlist: Playlist, songs: List): Boolean } -class UserLibraryFactoryImpl -@Inject -constructor(private val playlistDao: PlaylistDao, private val musicSettings: MusicSettings) : +class UserLibraryFactoryImpl @Inject constructor(private val playlistDao: PlaylistDao) : UserLibrary.Factory { override suspend fun query() = try { @@ -155,22 +153,22 @@ constructor(private val playlistDao: PlaylistDao, private val musicSettings: Mus override suspend fun create( rawPlaylists: List, - deviceLibrary: DeviceLibrary + deviceLibrary: DeviceLibrary, + nameFactory: Name.Known.Factory ): MutableUserLibrary { - val nameFactory = Name.Known.Factory.from(musicSettings) val playlistMap = mutableMapOf() for (rawPlaylist in rawPlaylists) { val playlistImpl = PlaylistImpl.fromRaw(rawPlaylist, deviceLibrary, nameFactory) playlistMap[playlistImpl.uid] = playlistImpl } - return UserLibraryImpl(playlistDao, playlistMap, musicSettings) + return UserLibraryImpl(playlistDao, playlistMap, nameFactory) } } private class UserLibraryImpl( private val playlistDao: PlaylistDao, private val playlistMap: MutableMap, - private val musicSettings: MusicSettings + private val nameFactory: Name.Known.Factory ) : MutableUserLibrary { override fun hashCode() = playlistMap.hashCode() @@ -186,7 +184,7 @@ private class UserLibraryImpl( override fun findPlaylist(name: String) = playlistMap.values.find { it.name.raw == name } override suspend fun createPlaylist(name: String, songs: List): Playlist? { - val playlistImpl = PlaylistImpl.from(name, songs, Name.Known.Factory.from(musicSettings)) + val playlistImpl = PlaylistImpl.from(name, songs, nameFactory) synchronized(this) { playlistMap[playlistImpl.uid] = playlistImpl } val rawPlaylist = RawPlaylist( @@ -209,9 +207,7 @@ private class UserLibraryImpl( val playlistImpl = synchronized(this) { requireNotNull(playlistMap[playlist.uid]) { "Cannot rename invalid playlist" } - .also { - playlistMap[it.uid] = it.edit(name, Name.Known.Factory.from(musicSettings)) - } + .also { playlistMap[it.uid] = it.edit(name, nameFactory) } } return try { diff --git a/app/src/test/java/org/oxycblt/auxio/music/info/NameTest.kt b/app/src/test/java/org/oxycblt/auxio/music/info/NameTest.kt index fd80d51c4..078a1f154 100644 --- a/app/src/test/java/org/oxycblt/auxio/music/info/NameTest.kt +++ b/app/src/test/java/org/oxycblt/auxio/music/info/NameTest.kt @@ -18,30 +18,14 @@ package org.oxycblt.auxio.music.info -import io.mockk.every -import io.mockk.mockk import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals -import org.junit.Assert.assertTrue import org.junit.Test -import org.oxycblt.auxio.music.MusicSettings class NameTest { - @Test - fun name_simple_from_settings() { - val musicSettings = mockk { every { intelligentSorting } returns false } - assertTrue(Name.Known.Factory.from(musicSettings) is SimpleKnownName.Factory) - } - - @Test - fun name_intelligent_from_settings() { - val musicSettings = mockk { every { intelligentSorting } returns true } - assertTrue(Name.Known.Factory.from(musicSettings) is IntelligentKnownName.Factory) - } - @Test fun name_simple_withoutPunct() { - val name = SimpleKnownName("Loveless", null) + val name = Name.Known.SimpleFactory.parse("Loveless", null) assertEquals("Loveless", name.raw) assertEquals(null, name.sort) assertEquals("L", name.thumb) @@ -52,7 +36,7 @@ class NameTest { @Test fun name_simple_withPunct() { - val name = SimpleKnownName("alt-J", null) + val name = Name.Known.SimpleFactory.parse("alt-J", null) assertEquals("alt-J", name.raw) assertEquals(null, name.sort) assertEquals("A", name.thumb) @@ -63,7 +47,7 @@ class NameTest { @Test fun name_simple_oopsAllPunct() { - val name = SimpleKnownName("!!!", null) + val name = Name.Known.SimpleFactory.parse("!!!", null) assertEquals("!!!", name.raw) assertEquals(null, name.sort) assertEquals("!", name.thumb) @@ -74,7 +58,7 @@ class NameTest { @Test fun name_simple_spacedPunct() { - val name = SimpleKnownName("& Yet & Yet", null) + val name = Name.Known.SimpleFactory.parse("& Yet & Yet", null) assertEquals("& Yet & Yet", name.raw) assertEquals(null, name.sort) assertEquals("Y", name.thumb) @@ -85,7 +69,7 @@ class NameTest { @Test fun name_simple_withSort() { - val name = SimpleKnownName("The Smile", "Smile") + val name = Name.Known.SimpleFactory.parse("The Smile", "Smile") assertEquals("The Smile", name.raw) assertEquals("Smile", name.sort) assertEquals("S", name.thumb) @@ -96,7 +80,7 @@ class NameTest { @Test fun name_intelligent_withoutPunct_withoutArticle_withoutNumerics() { - val name = IntelligentKnownName("Loveless", null) + val name = Name.Known.IntelligentFactory.parse("Loveless", null) assertEquals("Loveless", name.raw) assertEquals(null, name.sort) assertEquals("L", name.thumb) @@ -107,7 +91,7 @@ class NameTest { @Test fun name_intelligent_withoutPunct_withoutArticle_withSpacedStartNumerics() { - val name = IntelligentKnownName("15 Step", null) + val name = Name.Known.IntelligentFactory.parse("15 Step", null) assertEquals("15 Step", name.raw) assertEquals(null, name.sort) assertEquals("#", name.thumb) @@ -121,7 +105,7 @@ class NameTest { @Test fun name_intelligent_withoutPunct_withoutArticle_withPackedStartNumerics() { - val name = IntelligentKnownName("23Kid", null) + val name = Name.Known.IntelligentFactory.parse("23Kid", null) assertEquals("23Kid", name.raw) assertEquals(null, name.sort) assertEquals("#", name.thumb) @@ -135,7 +119,7 @@ class NameTest { @Test fun name_intelligent_withoutPunct_withoutArticle_withSpacedMiddleNumerics() { - val name = IntelligentKnownName("Foo 1 2 Bar", null) + val name = Name.Known.IntelligentFactory.parse("Foo 1 2 Bar", null) assertEquals("Foo 1 2 Bar", name.raw) assertEquals(null, name.sort) assertEquals("F", name.thumb) @@ -158,7 +142,7 @@ class NameTest { @Test fun name_intelligent_withoutPunct_withoutArticle_withPackedMiddleNumerics() { - val name = IntelligentKnownName("Foo12Bar", null) + val name = Name.Known.IntelligentFactory.parse("Foo12Bar", null) assertEquals("Foo12Bar", name.raw) assertEquals(null, name.sort) assertEquals("F", name.thumb) @@ -175,7 +159,7 @@ class NameTest { @Test fun name_intelligent_withoutPunct_withoutArticle_withSpacedEndNumerics() { - val name = IntelligentKnownName("Foo 1", null) + val name = Name.Known.IntelligentFactory.parse("Foo 1", null) assertEquals("Foo 1", name.raw) assertEquals(null, name.sort) assertEquals("F", name.thumb) @@ -189,7 +173,7 @@ class NameTest { @Test fun name_intelligent_withoutPunct_withoutArticle_withPackedEndNumerics() { - val name = IntelligentKnownName("Error404", null) + val name = Name.Known.IntelligentFactory.parse("Error404", null) assertEquals("Error404", name.raw) assertEquals(null, name.sort) assertEquals("E", name.thumb) @@ -203,7 +187,7 @@ class NameTest { @Test fun name_intelligent_withoutPunct_withThe_withoutNumerics() { - val name = IntelligentKnownName("The National Anthem", null) + val name = Name.Known.IntelligentFactory.parse("The National Anthem", null) assertEquals("The National Anthem", name.raw) assertEquals(null, name.sort) assertEquals("N", name.thumb) @@ -214,7 +198,7 @@ class NameTest { @Test fun name_intelligent_withoutPunct_withAn_withoutNumerics() { - val name = IntelligentKnownName("An Eagle in Your Mind", null) + val name = Name.Known.IntelligentFactory.parse("An Eagle in Your Mind", null) assertEquals("An Eagle in Your Mind", name.raw) assertEquals(null, name.sort) assertEquals("E", name.thumb) @@ -225,7 +209,7 @@ class NameTest { @Test fun name_intelligent_withoutPunct_withA_withoutNumerics() { - val name = IntelligentKnownName("A Song For Our Fathers", null) + val name = Name.Known.IntelligentFactory.parse("A Song For Our Fathers", null) assertEquals("A Song For Our Fathers", name.raw) assertEquals(null, name.sort) assertEquals("S", name.thumb) @@ -236,7 +220,7 @@ class NameTest { @Test fun name_intelligent_withPunct_withoutArticle_withoutNumerics() { - val name = IntelligentKnownName("alt-J", null) + val name = Name.Known.IntelligentFactory.parse("alt-J", null) assertEquals("alt-J", name.raw) assertEquals(null, name.sort) assertEquals("A", name.thumb) @@ -247,7 +231,7 @@ class NameTest { @Test fun name_intelligent_oopsAllPunct_withoutArticle_withoutNumerics() { - val name = IntelligentKnownName("!!!", null) + val name = Name.Known.IntelligentFactory.parse("!!!", null) assertEquals("!!!", name.raw) assertEquals(null, name.sort) assertEquals("!", name.thumb) @@ -258,7 +242,7 @@ class NameTest { @Test fun name_intelligent_withoutPunct_shortArticle_withNumerics() { - val name = IntelligentKnownName("the 1", null) + val name = Name.Known.IntelligentFactory.parse("the 1", null) assertEquals("the 1", name.raw) assertEquals(null, name.sort) assertEquals("#", name.thumb) @@ -269,7 +253,7 @@ class NameTest { @Test fun name_intelligent_spacedPunct_withoutArticle_withoutNumerics() { - val name = IntelligentKnownName("& Yet & Yet", null) + val name = Name.Known.IntelligentFactory.parse("& Yet & Yet", null) assertEquals("& Yet & Yet", name.raw) assertEquals(null, name.sort) assertEquals("Y", name.thumb) @@ -280,7 +264,7 @@ class NameTest { @Test fun name_intelligent_withPunct_withoutArticle_withNumerics() { - val name = IntelligentKnownName("Design : 2 : 3", null) + val name = Name.Known.IntelligentFactory.parse("Design : 2 : 3", null) assertEquals("Design : 2 : 3", name.raw) assertEquals(null, name.sort) assertEquals("D", name.thumb) @@ -300,7 +284,7 @@ class NameTest { @Test fun name_intelligent_oopsAllPunct_withoutArticle_oopsAllNumerics() { - val name = IntelligentKnownName("2 + 2 = 5", null) + val name = Name.Known.IntelligentFactory.parse("2 + 2 = 5", null) assertEquals("2 + 2 = 5", name.raw) assertEquals(null, name.sort) assertEquals("#", name.thumb) @@ -323,7 +307,7 @@ class NameTest { @Test fun name_intelligent_withSort() { - val name = IntelligentKnownName("The Smile", "Smile") + val name = Name.Known.IntelligentFactory.parse("The Smile", "Smile") assertEquals("The Smile", name.raw) assertEquals("Smile", name.sort) assertEquals("S", name.thumb) @@ -334,40 +318,40 @@ class NameTest { @Test fun name_equals_simple() { - val a = SimpleKnownName("The Same", "Same") - val b = SimpleKnownName("The Same", "Same") + val a = Name.Known.SimpleFactory.parse("The Same", "Same") + val b = Name.Known.SimpleFactory.parse("The Same", "Same") assertEquals(a, b) } @Test fun name_equals_differentSort() { - val a = SimpleKnownName("The Same", "Same") - val b = SimpleKnownName("The Same", null) + val a = Name.Known.SimpleFactory.parse("The Same", "Same") + val b = Name.Known.SimpleFactory.parse("The Same", null) assertNotEquals(a, b) assertNotEquals(a.hashCode(), b.hashCode()) } @Test fun name_equals_intelligent_differentTokens() { - val a = IntelligentKnownName("The Same", "Same") - val b = IntelligentKnownName("Same", "Same") + val a = Name.Known.IntelligentFactory.parse("The Same", "Same") + val b = Name.Known.IntelligentFactory.parse("Same", "Same") assertNotEquals(a, b) assertNotEquals(a.hashCode(), b.hashCode()) } @Test fun name_compareTo_simple_withoutSort_withoutArticle_withoutNumeric() { - val a = SimpleKnownName("A", null) - val b = SimpleKnownName("B", null) + val a = Name.Known.SimpleFactory.parse("A", null) + val b = Name.Known.SimpleFactory.parse("B", null) assertEquals(-1, a.compareTo(b)) } @Test fun name_compareTo_simple_withoutSort_withArticle_withoutNumeric() { - val a = SimpleKnownName("A Brain in a Bottle", null) - val b = SimpleKnownName("Acid Rain", null) - val c = SimpleKnownName("Boralis / Contrastellar", null) - val d = SimpleKnownName("Breathe In", null) + val a = Name.Known.SimpleFactory.parse("A Brain in a Bottle", null) + val b = Name.Known.SimpleFactory.parse("Acid Rain", null) + val c = Name.Known.SimpleFactory.parse("Boralis / Contrastellar", null) + val d = Name.Known.SimpleFactory.parse("Breathe In", null) assertEquals(-1, a.compareTo(b)) assertEquals(-1, a.compareTo(c)) assertEquals(-1, a.compareTo(d)) @@ -375,40 +359,40 @@ class NameTest { @Test fun name_compareTo_simple_withSort_withoutArticle_withNumeric() { - val a = SimpleKnownName("15 Step", null) - val b = SimpleKnownName("128 Harps", null) - val c = SimpleKnownName("1969", null) + val a = Name.Known.SimpleFactory.parse("15 Step", null) + val b = Name.Known.SimpleFactory.parse("128 Harps", null) + val c = Name.Known.SimpleFactory.parse("1969", null) assertEquals(1, a.compareTo(b)) assertEquals(-1, a.compareTo(c)) } @Test fun name_compareTo_simple_withPartialSort() { - val a = SimpleKnownName("A", "C") - val b = SimpleKnownName("B", null) + val a = Name.Known.SimpleFactory.parse("A", "C") + val b = Name.Known.SimpleFactory.parse("B", null) assertEquals(1, a.compareTo(b)) } @Test fun name_compareTo_simple_withSort() { - val a = SimpleKnownName("D", "A") - val b = SimpleKnownName("C", "B") + val a = Name.Known.SimpleFactory.parse("D", "A") + val b = Name.Known.SimpleFactory.parse("C", "B") assertEquals(-1, a.compareTo(b)) } @Test fun name_compareTo_intelligent_withoutSort_withoutArticle_withoutNumeric() { - val a = IntelligentKnownName("A", null) - val b = IntelligentKnownName("B", null) + val a = Name.Known.IntelligentFactory.parse("A", null) + val b = Name.Known.IntelligentFactory.parse("B", null) assertEquals(-1, a.compareTo(b)) } @Test fun name_compareTo_intelligent_withoutSort_withArticle_withoutNumeric() { - val a = IntelligentKnownName("A Brain in a Bottle", null) - val b = IntelligentKnownName("Acid Rain", null) - val c = IntelligentKnownName("Boralis / Contrastellar", null) - val d = IntelligentKnownName("Breathe In", null) + val a = Name.Known.IntelligentFactory.parse("A Brain in a Bottle", null) + val b = Name.Known.IntelligentFactory.parse("Acid Rain", null) + val c = Name.Known.IntelligentFactory.parse("Boralis / Contrastellar", null) + val d = Name.Known.IntelligentFactory.parse("Breathe In", null) assertEquals(1, a.compareTo(b)) assertEquals(1, a.compareTo(c)) assertEquals(-1, a.compareTo(d)) @@ -416,9 +400,9 @@ class NameTest { @Test fun name_compareTo_intelligent_withoutSort_withoutArticle_withNumeric() { - val a = IntelligentKnownName("15 Step", null) - val b = IntelligentKnownName("128 Harps", null) - val c = IntelligentKnownName("1969", null) + val a = Name.Known.IntelligentFactory.parse("15 Step", null) + val b = Name.Known.IntelligentFactory.parse("128 Harps", null) + val c = Name.Known.IntelligentFactory.parse("1969", null) assertEquals(-1, a.compareTo(b)) assertEquals(-1, b.compareTo(c)) assertEquals(-2, a.compareTo(c)) @@ -426,15 +410,15 @@ class NameTest { @Test fun name_compareTo_intelligent_withPartialSort_withoutArticle_withoutNumeric() { - val a = SimpleKnownName("A", "C") - val b = SimpleKnownName("B", null) + val a = Name.Known.SimpleFactory.parse("A", "C") + val b = Name.Known.SimpleFactory.parse("B", null) assertEquals(1, a.compareTo(b)) } @Test fun name_compareTo_intelligent_withSort_withoutArticle_withoutNumeric() { - val a = IntelligentKnownName("D", "A") - val b = IntelligentKnownName("C", "B") + val a = Name.Known.IntelligentFactory.parse("D", "A") + val b = Name.Known.IntelligentFactory.parse("C", "B") assertEquals(-1, a.compareTo(b)) } @@ -447,7 +431,7 @@ class NameTest { @Test fun name_compareTo_mixed() { val a = Name.Unknown(0) - val b = IntelligentKnownName("A", null) + val b = Name.Known.IntelligentFactory.parse("A", null) assertEquals(-1, a.compareTo(b)) } }