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..662eb49c5 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt @@ -223,7 +223,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() 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 eae4265ff..739faba8c 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 @@ -32,6 +32,7 @@ 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 +import org.oxycblt.auxio.music.info.Name import org.oxycblt.auxio.music.metadata.Separators import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.unlikelyToBeNull @@ -108,7 +109,7 @@ interface DeviceLibrary { */ suspend fun create( rawSongs: Channel, - processedSongs: Channel + processedSongs: Channel, ): DeviceLibraryImpl } } @@ -119,7 +120,8 @@ class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: Mu rawSongs: Channel, processedSongs: Channel ): DeviceLibraryImpl { - val separators = Separators.from(musicSettings.separators) + val nameFactory = Name.Known.Factory.from(musicSettings) + val separators = Separators.from(musicSettings) val songGrouping = mutableMapOf() val albumGrouping = mutableMapOf>() @@ -130,7 +132,7 @@ class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: Mu // All music information is grouped as it is indexed by other components. for (rawSong in rawSongs) { - val song = SongImpl(rawSong, musicSettings, separators) + val song = SongImpl(rawSong, nameFactory, separators) // At times the indexer produces duplicate songs, try to filter these. Comparing by // UID is sufficient for something like this, and also prevents collisions from // causing severe issues elsewhere. @@ -210,7 +212,7 @@ class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: Mu // Now that all songs are processed, also process albums and group them into their // respective artists. - val albums = albumGrouping.values.mapTo(mutableSetOf()) { AlbumImpl(it, musicSettings) } + val albums = albumGrouping.values.mapTo(mutableSetOf()) { AlbumImpl(it, nameFactory) } for (album in albums) { for (rawArtist in album.rawArtists) { val key = RawArtist.Key(rawArtist) @@ -246,8 +248,8 @@ class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: Mu } // Artists and genres do not need to be grouped and can be processed immediately. - val artists = artistGrouping.values.mapTo(mutableSetOf()) { ArtistImpl(it, musicSettings) } - val genres = genreGrouping.values.mapTo(mutableSetOf()) { GenreImpl(it, musicSettings) } + val artists = artistGrouping.values.mapTo(mutableSetOf()) { ArtistImpl(it, nameFactory) } + val genres = genreGrouping.values.mapTo(mutableSetOf()) { GenreImpl(it, nameFactory) } return DeviceLibraryImpl(songGrouping.values.toSet(), albums, artists, genres) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt index b8b20f06b..d513fcbff 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt @@ -25,7 +25,6 @@ import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.fs.MimeType @@ -48,12 +47,15 @@ import org.oxycblt.auxio.util.update * Library-backed implementation of [Song]. * * @param rawSong The [RawSong] to derive the member data from. + * @param nameFactory The [Name.Known.Factory] to interpret name information with. * @param separators The [Separators] to parse multi-value tags with. - * @param musicSettings [MusicSettings] to for user parsing configuration. * @author Alexander Capehart (OxygenCobalt) */ -class SongImpl(private val rawSong: RawSong, musicSettings: MusicSettings, separators: Separators) : - Song { +class SongImpl( + private val rawSong: RawSong, + nameFactory: Name.Known.Factory, + separators: Separators +) : Song { override val uid = // Attempt to use a MusicBrainz ID first before falling back to a hashed UID. rawSong.musicBrainzId?.toUuidOrNull()?.let { Music.UID.musicBrainz(MusicType.SONGS, it) } @@ -72,10 +74,8 @@ class SongImpl(private val rawSong: RawSong, musicSettings: MusicSettings, separ update(rawSong.albumArtistNames) } override val name = - Name.Known.from( - requireNotNull(rawSong.name) { "Invalid raw: No title" }, - rawSong.sortName, - musicSettings) + nameFactory.parse( + requireNotNull(rawSong.name) { "Invalid raw: No title" }, rawSong.sortName) override val track = rawSong.track override val disc = rawSong.disc?.let { Disc(it, rawSong.subtitle) } @@ -256,13 +256,10 @@ class SongImpl(private val rawSong: RawSong, musicSettings: MusicSettings, separ * Library-backed implementation of [Album]. * * @param grouping [Grouping] to derive the member data from. - * @param musicSettings [MusicSettings] to for user parsing configuration. + * @param nameFactory The [Name.Known.Factory] to interpret name information with. * @author Alexander Capehart (OxygenCobalt) */ -class AlbumImpl( - grouping: Grouping, - musicSettings: MusicSettings, -) : Album { +class AlbumImpl(grouping: Grouping, nameFactory: Name.Known.Factory) : Album { private val rawAlbum = grouping.raw.inner override val uid = @@ -275,7 +272,7 @@ class AlbumImpl( update(rawAlbum.name) update(rawAlbum.rawArtists.map { it.name }) } - override val name = Name.Known.from(rawAlbum.name, rawAlbum.sortName, musicSettings) + override val name = nameFactory.parse(rawAlbum.name, rawAlbum.sortName) override val dates: Date.Range? override val releaseType = rawAlbum.releaseType ?: ReleaseType.Album(null) override val coverUri = CoverUri(rawAlbum.mediaStoreId.toCoverUri(), grouping.raw.src.uri) @@ -376,10 +373,10 @@ class AlbumImpl( * Library-backed implementation of [Artist]. * * @param grouping [Grouping] to derive the member data from. - * @param musicSettings [MusicSettings] to for user parsing configuration. + * @param nameFactory The [Name.Known.Factory] to interpret name information with. * @author Alexander Capehart (OxygenCobalt) */ -class ArtistImpl(grouping: Grouping, musicSettings: MusicSettings) : Artist { +class ArtistImpl(grouping: Grouping, nameFactory: Name.Known.Factory) : Artist { private val rawArtist = grouping.raw.inner override val uid = @@ -387,7 +384,7 @@ class ArtistImpl(grouping: Grouping, musicSettings: MusicSetti rawArtist.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ARTISTS, it) } ?: Music.UID.auxio(MusicType.ARTISTS) { update(rawArtist.name) } override val name = - rawArtist.name?.let { Name.Known.from(it, rawArtist.sortName, musicSettings) } + rawArtist.name?.let { nameFactory.parse(it, rawArtist.sortName) } ?: Name.Unknown(R.string.def_artist) override val songs: Set @@ -473,15 +470,15 @@ class ArtistImpl(grouping: Grouping, musicSettings: MusicSetti * Library-backed implementation of [Genre]. * * @param grouping [Grouping] to derive the member data from. - * @param musicSettings [MusicSettings] to for user parsing configuration. + * @param nameFactory The [Name.Known.Factory] to interpret name information with. * @author Alexander Capehart (OxygenCobalt) */ -class GenreImpl(grouping: Grouping, musicSettings: MusicSettings) : Genre { +class GenreImpl(grouping: Grouping, nameFactory: Name.Known.Factory) : Genre { private val rawGenre = grouping.raw.inner override val uid = Music.UID.auxio(MusicType.GENRES) { update(rawGenre.name) } override val name = - rawGenre.name?.let { Name.Known.from(it, rawGenre.name, musicSettings) } + rawGenre.name?.let { nameFactory.parse(it, rawGenre.name) } ?: Name.Unknown(R.string.def_genre) override val songs: Set 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 c47e561cf..b0f0b029d 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 @@ -57,36 +57,6 @@ sealed interface Name : Comparable { /** A tokenized version of the name that will be compared. */ @VisibleForTesting(VisibleForTesting.PROTECTED) abstract val sortTokens: List - /** An individual part of a name string that can be compared intelligently. */ - @VisibleForTesting(VisibleForTesting.PROTECTED) - data class SortToken(val collationKey: CollationKey, val type: Type) : - Comparable { - override fun compareTo(other: SortToken): Int { - // Numeric tokens should always be lower than lexicographic tokens. - val modeComp = type.compareTo(other.type) - if (modeComp != 0) { - return modeComp - } - - // Numeric strings must be ordered by magnitude, thus immediately short-circuit - // the comparison if the lengths do not match. - if (type == Type.NUMERIC && - collationKey.sourceString.length != other.collationKey.sourceString.length) { - return collationKey.sourceString.length - other.collationKey.sourceString.length - } - - return collationKey.compareTo(other.collationKey) - } - - /** Denotes the type of comparison to be performed with this token. */ - enum class Type { - /** Compare as a digit string, like "65". */ - NUMERIC, - /** Compare as a standard alphanumeric string, like "65daysofstatic" */ - LEXICOGRAPHIC - } - } - final override val thumb: String get() = // TODO: Remove these safety checks once you have real unit testing @@ -110,20 +80,30 @@ sealed interface Name : Comparable { is Unknown -> 1 } - companion object { + interface Factory { /** * Create a new instance of [Name.Known] * * @param raw The raw name obtained from the music item * @param sort The raw sort name obtained from the music item - * @param musicSettings [MusicSettings] required for name configuration. */ - fun from(raw: String, sort: String?, musicSettings: MusicSettings): Known = - if (musicSettings.intelligentSorting) { - IntelligentKnownName(raw, sort) - } else { - SimpleKnownName(raw, sort) - } + 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 new [Factory] instance reflecting the configuration state. + */ + fun from(settings: MusicSettings) = + if (settings.intelligentSorting) { + IntelligentKnownName.Factory() + } else { + SimpleKnownName.Factory() + } + } } } @@ -157,8 +137,8 @@ private val punctRegex by lazy { Regex("[\\p{Punct}+]") } * * @author Alexander Capehart (OxygenCobalt) */ -private data class SimpleKnownName(override val raw: String, override val sort: String?) : - Name.Known() { +@VisibleForTesting +data class SimpleKnownName(override val raw: String, override val sort: String?) : Name.Known() { override val sortTokens = listOf(parseToken(sort ?: raw)) private fun parseToken(name: String): SortToken { @@ -168,6 +148,10 @@ private data class SimpleKnownName(override val raw: String, override val sort: // Always use lexicographic mode since we aren't parsing any numeric components return SortToken(collationKey, SortToken.Type.LEXICOGRAPHIC) } + + class Factory : Name.Known.Factory { + override fun parse(raw: String, sort: String?) = SimpleKnownName(raw, sort) + } } /** @@ -175,7 +159,8 @@ private data class SimpleKnownName(override val raw: String, override val sort: * * @author Alexander Capehart (OxygenCobalt) */ -private data class IntelligentKnownName(override val raw: String, override val sort: String?) : +@VisibleForTesting +data class IntelligentKnownName(override val raw: String, override val sort: String?) : Name.Known() { override val sortTokens = parseTokens(sort ?: raw) @@ -223,7 +208,40 @@ private data class IntelligentKnownName(override val raw: String, override val s } } + class 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+)") } } } + +/** An individual part of a name string that can be compared intelligently. */ +@VisibleForTesting(VisibleForTesting.PROTECTED) +data class SortToken(val collationKey: CollationKey, val type: Type) : Comparable { + override fun compareTo(other: SortToken): Int { + // Numeric tokens should always be lower than lexicographic tokens. + val modeComp = type.compareTo(other.type) + if (modeComp != 0) { + return modeComp + } + + // Numeric strings must be ordered by magnitude, thus immediately short-circuit + // the comparison if the lengths do not match. + if (type == Type.NUMERIC && + collationKey.sourceString.length != other.collationKey.sourceString.length) { + return collationKey.sourceString.length - other.collationKey.sourceString.length + } + + return collationKey.compareTo(other.collationKey) + } + + /** Denotes the type of comparison to be performed with this token. */ + enum class Type { + /** Compare as a digit string, like "65". */ + NUMERIC, + /** Compare as a standard alphanumeric string, like "65daysofstatic" */ + LEXICOGRAPHIC + } +} 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 5142c6905..989a7b128 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,6 +18,9 @@ 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. @@ -40,9 +43,22 @@ interface Separators { const val SLASH = '/' const val PLUS = '+' const val AND = '&' + /** + * Creates a new instance from the **current state** of the given [MusicSettings]'s + * user-defined separator configuration. + * + * @param settings The [MusicSettings] to use. + * @return A new [Separators] instance reflecting the configuration state. + */ + fun from(settings: MusicSettings) = from(settings.separators) - fun from(selector: String) = - if (selector.isNotEmpty()) CharSeparators(selector.toSet()) else NoSeparators + @VisibleForTesting + fun from(chars: String) = + if (chars.isNotEmpty()) { + CharSeparators(chars.toSet()) + } else { + NoSeparators + } } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/metadata/TagWorker.kt b/app/src/main/java/org/oxycblt/auxio/music/metadata/TagWorker.kt index fae02585e..196c7c0dc 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/metadata/TagWorker.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/metadata/TagWorker.kt @@ -77,7 +77,6 @@ private class TagWorkerImpl( private val rawSong: RawSong, private val future: Future ) : TagWorker { - override fun poll(): RawSong? { if (!future.isDone) { // Not done yet, nothing to do. diff --git a/app/src/main/java/org/oxycblt/auxio/music/user/PlaylistImpl.kt b/app/src/main/java/org/oxycblt/auxio/music/user/PlaylistImpl.kt index ffe7a5174..fe4418894 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/user/PlaylistImpl.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/user/PlaylistImpl.kt @@ -19,7 +19,6 @@ package org.oxycblt.auxio.music.user import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song @@ -51,10 +50,10 @@ private constructor( * Clone the data in this instance to a new [PlaylistImpl] with the given [name]. * * @param name The new name to use. - * @param musicSettings [MusicSettings] required for name configuration. + * @param nameFactory The [Name.Known.Factory] to interpret name information with. */ - fun edit(name: String, musicSettings: MusicSettings) = - PlaylistImpl(uid, Name.Known.from(name, null, musicSettings), songs) + fun edit(name: String, nameFactory: Name.Known.Factory) = + PlaylistImpl(uid, nameFactory.parse(name, null), songs) /** * Clone the data in this instance to a new [PlaylistImpl] with the given [Song]s. @@ -76,29 +75,26 @@ private constructor( * * @param name The name of the playlist. * @param songs The songs to initially populate the playlist with. - * @param musicSettings [MusicSettings] required for name configuration. + * @param nameFactory The [Name.Known.Factory] to interpret name information with. */ - fun from(name: String, songs: List, musicSettings: MusicSettings) = - PlaylistImpl( - Music.UID.auxio(MusicType.PLAYLISTS), - Name.Known.from(name, null, musicSettings), - songs) + fun from(name: String, songs: List, nameFactory: Name.Known.Factory) = + PlaylistImpl(Music.UID.auxio(MusicType.PLAYLISTS), nameFactory.parse(name, null), songs) /** * Populate a new instance from a read [RawPlaylist]. * * @param rawPlaylist The [RawPlaylist] to read from. * @param deviceLibrary The [DeviceLibrary] to initialize from. - * @param musicSettings [MusicSettings] required for name configuration. + * @param nameFactory The [Name.Known.Factory] to interpret name information with. */ fun fromRaw( rawPlaylist: RawPlaylist, deviceLibrary: DeviceLibrary, - musicSettings: MusicSettings + nameFactory: Name.Known.Factory ) = PlaylistImpl( rawPlaylist.playlistInfo.playlistUid, - Name.Known.from(rawPlaylist.playlistInfo.name, null, musicSettings), + nameFactory.parse(rawPlaylist.playlistInfo.name, null), rawPlaylist.songs.mapNotNull { deviceLibrary.findSong(it.songUid) }) } } 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 06de6d64f..faae9594b 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 @@ -26,6 +26,7 @@ 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 +import org.oxycblt.auxio.music.info.Name import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE @@ -144,7 +145,9 @@ constructor(private val playlistDao: PlaylistDao, private val musicSettings: Mus UserLibrary.Factory { override suspend fun query() = try { - playlistDao.readRawPlaylists() + val rawPlaylists = playlistDao.readRawPlaylists() + logD("Successfully read ${rawPlaylists.size} playlists") + rawPlaylists } catch (e: Exception) { logE("Unable to read playlists: $e") listOf() @@ -154,11 +157,10 @@ constructor(private val playlistDao: PlaylistDao, private val musicSettings: Mus rawPlaylists: List, deviceLibrary: DeviceLibrary ): MutableUserLibrary { - logD("Successfully read ${rawPlaylists.size} playlists") - // Convert the database playlist information to actual usable playlists. + val nameFactory = Name.Known.Factory.from(musicSettings) val playlistMap = mutableMapOf() for (rawPlaylist in rawPlaylists) { - val playlistImpl = PlaylistImpl.fromRaw(rawPlaylist, deviceLibrary, musicSettings) + val playlistImpl = PlaylistImpl.fromRaw(rawPlaylist, deviceLibrary, nameFactory) playlistMap[playlistImpl.uid] = playlistImpl } return UserLibraryImpl(playlistDao, playlistMap, musicSettings) @@ -184,7 +186,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, musicSettings) + val playlistImpl = PlaylistImpl.from(name, songs, Name.Known.Factory.from(musicSettings)) synchronized(this) { playlistMap[playlistImpl.uid] = playlistImpl } val rawPlaylist = RawPlaylist( @@ -207,7 +209,9 @@ private class UserLibraryImpl( val playlistImpl = synchronized(this) { requireNotNull(playlistMap[playlist.uid]) { "Cannot rename invalid playlist" } - .also { playlistMap[it.uid] = it.edit(name, musicSettings) } + .also { + playlistMap[it.uid] = it.edit(name, Name.Known.Factory.from(musicSettings)) + } } 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 f23f76f16..fd80d51c4 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 @@ -22,342 +22,352 @@ 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 { - private fun mockIntelligentSorting(enabled: Boolean) = - mockk() { every { intelligentSorting } returns enabled } + @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_from_simple_withoutPunct() { - val name = Name.Known.from("Loveless", null, mockIntelligentSorting(false)) + 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) assertEquals("Loveless", name.raw) assertEquals(null, name.sort) assertEquals("L", name.thumb) val only = name.sortTokens.single() assertEquals("Loveless", only.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) } @Test - fun name_from_simple_withPunct() { - val name = Name.Known.from("alt-J", null, mockIntelligentSorting(false)) + fun name_simple_withPunct() { + val name = SimpleKnownName("alt-J", null) assertEquals("alt-J", name.raw) assertEquals(null, name.sort) assertEquals("A", name.thumb) val only = name.sortTokens.single() assertEquals("altJ", only.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) } @Test - fun name_from_simple_oopsAllPunct() { - val name = Name.Known.from("!!!", null, mockIntelligentSorting(false)) + fun name_simple_oopsAllPunct() { + val name = SimpleKnownName("!!!", null) assertEquals("!!!", name.raw) assertEquals(null, name.sort) assertEquals("!", name.thumb) val only = name.sortTokens.single() assertEquals("!!!", only.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) } @Test - fun name_from_simple_spacedPunct() { - val name = Name.Known.from("& Yet & Yet", null, mockIntelligentSorting(false)) + fun name_simple_spacedPunct() { + val name = SimpleKnownName("& Yet & Yet", null) assertEquals("& Yet & Yet", name.raw) assertEquals(null, name.sort) assertEquals("Y", name.thumb) val first = name.sortTokens[0] assertEquals("Yet Yet", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) } @Test - fun name_from_simple_withSort() { - val name = Name.Known.from("The Smile", "Smile", mockIntelligentSorting(false)) + fun name_simple_withSort() { + val name = SimpleKnownName("The Smile", "Smile") assertEquals("The Smile", name.raw) assertEquals("Smile", name.sort) assertEquals("S", name.thumb) val only = name.sortTokens.single() assertEquals("Smile", only.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) } @Test - fun name_from_intelligent_withoutPunct_withoutArticle_withoutNumerics() { - val name = Name.Known.from("Loveless", null, mockIntelligentSorting(true)) + fun name_intelligent_withoutPunct_withoutArticle_withoutNumerics() { + val name = IntelligentKnownName("Loveless", null) assertEquals("Loveless", name.raw) assertEquals(null, name.sort) assertEquals("L", name.thumb) val only = name.sortTokens.single() assertEquals("Loveless", only.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) } @Test - fun name_from_intelligent_withoutPunct_withoutArticle_withSpacedStartNumerics() { - val name = Name.Known.from("15 Step", null, mockIntelligentSorting(true)) + fun name_intelligent_withoutPunct_withoutArticle_withSpacedStartNumerics() { + val name = IntelligentKnownName("15 Step", null) assertEquals("15 Step", name.raw) assertEquals(null, name.sort) assertEquals("#", name.thumb) val first = name.sortTokens[0] assertEquals("15", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, first.type) + assertEquals(SortToken.Type.NUMERIC, first.type) val second = name.sortTokens[1] assertEquals("Step", second.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, second.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type) } @Test - fun name_from_intelligent_withoutPunct_withoutArticle_withPackedStartNumerics() { - val name = Name.Known.from("23Kid", null, mockIntelligentSorting(true)) + fun name_intelligent_withoutPunct_withoutArticle_withPackedStartNumerics() { + val name = IntelligentKnownName("23Kid", null) assertEquals("23Kid", name.raw) assertEquals(null, name.sort) assertEquals("#", name.thumb) val first = name.sortTokens[0] assertEquals("23", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, first.type) + assertEquals(SortToken.Type.NUMERIC, first.type) val second = name.sortTokens[1] assertEquals("Kid", second.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, second.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type) } @Test - fun name_from_intelligent_withoutPunct_withoutArticle_withSpacedMiddleNumerics() { - val name = Name.Known.from("Foo 1 2 Bar", null, mockIntelligentSorting(true)) + fun name_intelligent_withoutPunct_withoutArticle_withSpacedMiddleNumerics() { + val name = IntelligentKnownName("Foo 1 2 Bar", null) assertEquals("Foo 1 2 Bar", name.raw) assertEquals(null, name.sort) assertEquals("F", name.thumb) val first = name.sortTokens[0] assertEquals("Foo", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) val second = name.sortTokens[1] assertEquals("1", second.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, second.type) + assertEquals(SortToken.Type.NUMERIC, second.type) val third = name.sortTokens[2] assertEquals(" ", third.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, third.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type) val fourth = name.sortTokens[3] assertEquals("2", fourth.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, fourth.type) + assertEquals(SortToken.Type.NUMERIC, fourth.type) val fifth = name.sortTokens[4] assertEquals("Bar", fifth.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, fifth.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, fifth.type) } @Test - fun name_from_intelligent_withoutPunct_withoutArticle_withPackedMiddleNumerics() { - val name = Name.Known.from("Foo12Bar", null, mockIntelligentSorting(true)) + fun name_intelligent_withoutPunct_withoutArticle_withPackedMiddleNumerics() { + val name = IntelligentKnownName("Foo12Bar", null) assertEquals("Foo12Bar", name.raw) assertEquals(null, name.sort) assertEquals("F", name.thumb) val first = name.sortTokens[0] assertEquals("Foo", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) val second = name.sortTokens[1] assertEquals("12", second.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, second.type) + assertEquals(SortToken.Type.NUMERIC, second.type) val third = name.sortTokens[2] assertEquals("Bar", third.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, third.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type) } @Test - fun name_from_intelligent_withoutPunct_withoutArticle_withSpacedEndNumerics() { - val name = Name.Known.from("Foo 1", null, mockIntelligentSorting(true)) + fun name_intelligent_withoutPunct_withoutArticle_withSpacedEndNumerics() { + val name = IntelligentKnownName("Foo 1", null) assertEquals("Foo 1", name.raw) assertEquals(null, name.sort) assertEquals("F", name.thumb) val first = name.sortTokens[0] assertEquals("Foo", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) val second = name.sortTokens[1] assertEquals("1", second.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, second.type) + assertEquals(SortToken.Type.NUMERIC, second.type) } @Test - fun name_from_intelligent_withoutPunct_withoutArticle_withPackedEndNumerics() { - val name = Name.Known.from("Error404", null, mockIntelligentSorting(true)) + fun name_intelligent_withoutPunct_withoutArticle_withPackedEndNumerics() { + val name = IntelligentKnownName("Error404", null) assertEquals("Error404", name.raw) assertEquals(null, name.sort) assertEquals("E", name.thumb) val first = name.sortTokens[0] assertEquals("Error", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) val second = name.sortTokens[1] assertEquals("404", second.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, second.type) + assertEquals(SortToken.Type.NUMERIC, second.type) } @Test - fun name_from_intelligent_withoutPunct_withThe_withoutNumerics() { - val name = Name.Known.from("The National Anthem", null, mockIntelligentSorting(true)) + fun name_intelligent_withoutPunct_withThe_withoutNumerics() { + val name = IntelligentKnownName("The National Anthem", null) assertEquals("The National Anthem", name.raw) assertEquals(null, name.sort) assertEquals("N", name.thumb) val first = name.sortTokens[0] assertEquals("National Anthem", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) } @Test - fun name_from_intelligent_withoutPunct_withAn_withoutNumerics() { - val name = Name.Known.from("An Eagle in Your Mind", null, mockIntelligentSorting(true)) + fun name_intelligent_withoutPunct_withAn_withoutNumerics() { + val name = IntelligentKnownName("An Eagle in Your Mind", null) assertEquals("An Eagle in Your Mind", name.raw) assertEquals(null, name.sort) assertEquals("E", name.thumb) val first = name.sortTokens[0] assertEquals("Eagle in Your Mind", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) } @Test - fun name_from_intelligent_withoutPunct_withA_withoutNumerics() { - val name = Name.Known.from("A Song For Our Fathers", null, mockIntelligentSorting(true)) + fun name_intelligent_withoutPunct_withA_withoutNumerics() { + val name = IntelligentKnownName("A Song For Our Fathers", null) assertEquals("A Song For Our Fathers", name.raw) assertEquals(null, name.sort) assertEquals("S", name.thumb) val first = name.sortTokens[0] assertEquals("Song For Our Fathers", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) } @Test - fun name_from_intelligent_withPunct_withoutArticle_withoutNumerics() { - val name = Name.Known.from("alt-J", null, mockIntelligentSorting(true)) + fun name_intelligent_withPunct_withoutArticle_withoutNumerics() { + val name = IntelligentKnownName("alt-J", null) assertEquals("alt-J", name.raw) assertEquals(null, name.sort) assertEquals("A", name.thumb) val only = name.sortTokens.single() assertEquals("altJ", only.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) } @Test - fun name_from_intelligent_oopsAllPunct_withoutArticle_withoutNumerics() { - val name = Name.Known.from("!!!", null, mockIntelligentSorting(true)) + fun name_intelligent_oopsAllPunct_withoutArticle_withoutNumerics() { + val name = IntelligentKnownName("!!!", null) assertEquals("!!!", name.raw) assertEquals(null, name.sort) assertEquals("!", name.thumb) val only = name.sortTokens.single() assertEquals("!!!", only.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) } @Test - fun name_from_intelligent_withoutPunct_shortArticle_withNumerics() { - val name = Name.Known.from("the 1", null, mockIntelligentSorting(true)) + fun name_intelligent_withoutPunct_shortArticle_withNumerics() { + val name = IntelligentKnownName("the 1", null) assertEquals("the 1", name.raw) assertEquals(null, name.sort) assertEquals("#", name.thumb) val first = name.sortTokens[0] assertEquals("1", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, first.type) + assertEquals(SortToken.Type.NUMERIC, first.type) } @Test - fun name_from_intelligent_spacedPunct_withoutArticle_withoutNumerics() { - val name = Name.Known.from("& Yet & Yet", null, mockIntelligentSorting(true)) + fun name_intelligent_spacedPunct_withoutArticle_withoutNumerics() { + val name = IntelligentKnownName("& Yet & Yet", null) assertEquals("& Yet & Yet", name.raw) assertEquals(null, name.sort) assertEquals("Y", name.thumb) val first = name.sortTokens[0] assertEquals("Yet Yet", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) } @Test - fun name_from_intelligent_withPunct_withoutArticle_withNumerics() { - val name = Name.Known.from("Design : 2 : 3", null, mockIntelligentSorting(true)) + fun name_intelligent_withPunct_withoutArticle_withNumerics() { + val name = IntelligentKnownName("Design : 2 : 3", null) assertEquals("Design : 2 : 3", name.raw) assertEquals(null, name.sort) assertEquals("D", name.thumb) val first = name.sortTokens[0] assertEquals("Design", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) val second = name.sortTokens[1] assertEquals("2", second.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, second.type) + assertEquals(SortToken.Type.NUMERIC, second.type) val third = name.sortTokens[2] assertEquals(" ", third.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, third.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type) val fourth = name.sortTokens[3] assertEquals("3", fourth.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, fourth.type) + assertEquals(SortToken.Type.NUMERIC, fourth.type) } @Test - fun name_from_intelligent_oopsAllPunct_withoutArticle_oopsAllNumerics() { - val name = Name.Known.from("2 + 2 = 5", null, mockIntelligentSorting(true)) + fun name_intelligent_oopsAllPunct_withoutArticle_oopsAllNumerics() { + val name = IntelligentKnownName("2 + 2 = 5", null) assertEquals("2 + 2 = 5", name.raw) assertEquals(null, name.sort) assertEquals("#", name.thumb) val first = name.sortTokens[0] assertEquals("2", first.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, first.type) + assertEquals(SortToken.Type.NUMERIC, first.type) val second = name.sortTokens[1] assertEquals(" ", second.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, second.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type) val third = name.sortTokens[2] assertEquals("2", third.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, third.type) + assertEquals(SortToken.Type.NUMERIC, third.type) val fourth = name.sortTokens[3] assertEquals(" ", fourth.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, fourth.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, fourth.type) val fifth = name.sortTokens[4] assertEquals("5", fifth.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.NUMERIC, fifth.type) + assertEquals(SortToken.Type.NUMERIC, fifth.type) } @Test - fun name_from_intelligent_withSort() { - val name = Name.Known.from("The Smile", "Smile", mockIntelligentSorting(true)) + fun name_intelligent_withSort() { + val name = IntelligentKnownName("The Smile", "Smile") assertEquals("The Smile", name.raw) assertEquals("Smile", name.sort) assertEquals("S", name.thumb) val only = name.sortTokens.single() assertEquals("Smile", only.collationKey.sourceString) - assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) + assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) } @Test fun name_equals_simple() { - val a = Name.Known.from("The Same", "Same", mockIntelligentSorting(false)) - val b = Name.Known.from("The Same", "Same", mockIntelligentSorting(false)) + val a = SimpleKnownName("The Same", "Same") + val b = SimpleKnownName("The Same", "Same") assertEquals(a, b) } @Test fun name_equals_differentSort() { - val a = Name.Known.from("The Same", "Same", mockIntelligentSorting(false)) - val b = Name.Known.from("The Same", null, mockIntelligentSorting(false)) + val a = SimpleKnownName("The Same", "Same") + val b = SimpleKnownName("The Same", null) assertNotEquals(a, b) assertNotEquals(a.hashCode(), b.hashCode()) } @Test fun name_equals_intelligent_differentTokens() { - val a = Name.Known.from("The Same", "Same", mockIntelligentSorting(true)) - val b = Name.Known.from("Same", "Same", mockIntelligentSorting(true)) + val a = IntelligentKnownName("The Same", "Same") + val b = IntelligentKnownName("Same", "Same") assertNotEquals(a, b) assertNotEquals(a.hashCode(), b.hashCode()) } @Test fun name_compareTo_simple_withoutSort_withoutArticle_withoutNumeric() { - val a = Name.Known.from("A", null, mockIntelligentSorting(false)) - val b = Name.Known.from("B", null, mockIntelligentSorting(false)) + val a = SimpleKnownName("A", null) + val b = SimpleKnownName("B", null) assertEquals(-1, a.compareTo(b)) } @Test fun name_compareTo_simple_withoutSort_withArticle_withoutNumeric() { - val a = Name.Known.from("A Brain in a Bottle", null, mockIntelligentSorting(false)) - val b = Name.Known.from("Acid Rain", null, mockIntelligentSorting(false)) - val c = Name.Known.from("Boralis / Contrastellar", null, mockIntelligentSorting(false)) - val d = Name.Known.from("Breathe In", null, mockIntelligentSorting(false)) + 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) assertEquals(-1, a.compareTo(b)) assertEquals(-1, a.compareTo(c)) assertEquals(-1, a.compareTo(d)) @@ -365,40 +375,40 @@ class NameTest { @Test fun name_compareTo_simple_withSort_withoutArticle_withNumeric() { - val a = Name.Known.from("15 Step", null, mockIntelligentSorting(false)) - val b = Name.Known.from("128 Harps", null, mockIntelligentSorting(false)) - val c = Name.Known.from("1969", null, mockIntelligentSorting(false)) + val a = SimpleKnownName("15 Step", null) + val b = SimpleKnownName("128 Harps", null) + val c = SimpleKnownName("1969", null) assertEquals(1, a.compareTo(b)) assertEquals(-1, a.compareTo(c)) } @Test fun name_compareTo_simple_withPartialSort() { - val a = Name.Known.from("A", "C", mockIntelligentSorting(false)) - val b = Name.Known.from("B", null, mockIntelligentSorting(false)) + val a = SimpleKnownName("A", "C") + val b = SimpleKnownName("B", null) assertEquals(1, a.compareTo(b)) } @Test fun name_compareTo_simple_withSort() { - val a = Name.Known.from("D", "A", mockIntelligentSorting(false)) - val b = Name.Known.from("C", "B", mockIntelligentSorting(false)) + val a = SimpleKnownName("D", "A") + val b = SimpleKnownName("C", "B") assertEquals(-1, a.compareTo(b)) } @Test fun name_compareTo_intelligent_withoutSort_withoutArticle_withoutNumeric() { - val a = Name.Known.from("A", null, mockIntelligentSorting(true)) - val b = Name.Known.from("B", null, mockIntelligentSorting(true)) + val a = IntelligentKnownName("A", null) + val b = IntelligentKnownName("B", null) assertEquals(-1, a.compareTo(b)) } @Test fun name_compareTo_intelligent_withoutSort_withArticle_withoutNumeric() { - val a = Name.Known.from("A Brain in a Bottle", null, mockIntelligentSorting(true)) - val b = Name.Known.from("Acid Rain", null, mockIntelligentSorting(true)) - val c = Name.Known.from("Boralis / Contrastellar", null, mockIntelligentSorting(true)) - val d = Name.Known.from("Breathe In", null, mockIntelligentSorting(true)) + 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) assertEquals(1, a.compareTo(b)) assertEquals(1, a.compareTo(c)) assertEquals(-1, a.compareTo(d)) @@ -406,9 +416,9 @@ class NameTest { @Test fun name_compareTo_intelligent_withoutSort_withoutArticle_withNumeric() { - val a = Name.Known.from("15 Step", null, mockIntelligentSorting(true)) - val b = Name.Known.from("128 Harps", null, mockIntelligentSorting(true)) - val c = Name.Known.from("1969", null, mockIntelligentSorting(true)) + val a = IntelligentKnownName("15 Step", null) + val b = IntelligentKnownName("128 Harps", null) + val c = IntelligentKnownName("1969", null) assertEquals(-1, a.compareTo(b)) assertEquals(-1, b.compareTo(c)) assertEquals(-2, a.compareTo(c)) @@ -416,15 +426,28 @@ class NameTest { @Test fun name_compareTo_intelligent_withPartialSort_withoutArticle_withoutNumeric() { - val a = Name.Known.from("A", "C", mockIntelligentSorting(false)) - val b = Name.Known.from("B", null, mockIntelligentSorting(false)) + val a = SimpleKnownName("A", "C") + val b = SimpleKnownName("B", null) assertEquals(1, a.compareTo(b)) } @Test fun name_compareTo_intelligent_withSort_withoutArticle_withoutNumeric() { - val a = Name.Known.from("D", "A", mockIntelligentSorting(true)) - val b = Name.Known.from("C", "B", mockIntelligentSorting(true)) + val a = IntelligentKnownName("D", "A") + val b = IntelligentKnownName("C", "B") + assertEquals(-1, a.compareTo(b)) + } + + @Test + fun name_unknown() { + val a = Name.Unknown(0) + assertEquals("?", a.thumb) + } + + @Test + fun name_compareTo_mixed() { + val a = Name.Unknown(0) + val b = IntelligentKnownName("A", null) assertEquals(-1, a.compareTo(b)) } }