music: use factory to create known names

Implement a new Name.Known.Factory instance that replaces the usage of
Name.Known.from.

This again allows songs to be differentiated on tag interpretation and
is generally easier to test.
This commit is contained in:
Alexander Capehart 2023-08-18 15:27:45 -06:00
parent c1655a9eca
commit fcffb56021
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
9 changed files with 263 additions and 207 deletions

View file

@ -223,7 +223,8 @@ constructor(
private val mediaStoreExtractor: MediaStoreExtractor, private val mediaStoreExtractor: MediaStoreExtractor,
private val tagExtractor: TagExtractor, private val tagExtractor: TagExtractor,
private val deviceLibraryFactory: DeviceLibrary.Factory, private val deviceLibraryFactory: DeviceLibrary.Factory,
private val userLibraryFactory: UserLibrary.Factory private val userLibraryFactory: UserLibrary.Factory,
private val musicSettings: MusicSettings
) : MusicRepository { ) : MusicRepository {
private val updateListeners = mutableListOf<MusicRepository.UpdateListener>() private val updateListeners = mutableListOf<MusicRepository.UpdateListener>()
private val indexingListeners = mutableListOf<MusicRepository.IndexingListener>() private val indexingListeners = mutableListOf<MusicRepository.IndexingListener>()

View file

@ -32,6 +32,7 @@ import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.fs.contentResolverSafe import org.oxycblt.auxio.music.fs.contentResolverSafe
import org.oxycblt.auxio.music.fs.useQuery 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.music.metadata.Separators
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull
@ -108,7 +109,7 @@ interface DeviceLibrary {
*/ */
suspend fun create( suspend fun create(
rawSongs: Channel<RawSong>, rawSongs: Channel<RawSong>,
processedSongs: Channel<RawSong> processedSongs: Channel<RawSong>,
): DeviceLibraryImpl ): DeviceLibraryImpl
} }
} }
@ -119,7 +120,8 @@ class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: Mu
rawSongs: Channel<RawSong>, rawSongs: Channel<RawSong>,
processedSongs: Channel<RawSong> processedSongs: Channel<RawSong>
): DeviceLibraryImpl { ): DeviceLibraryImpl {
val separators = Separators.from(musicSettings.separators) val nameFactory = Name.Known.Factory.from(musicSettings)
val separators = Separators.from(musicSettings)
val songGrouping = mutableMapOf<Music.UID, SongImpl>() val songGrouping = mutableMapOf<Music.UID, SongImpl>()
val albumGrouping = mutableMapOf<RawAlbum.Key, Grouping<RawAlbum, SongImpl>>() val albumGrouping = mutableMapOf<RawAlbum.Key, Grouping<RawAlbum, SongImpl>>()
@ -130,7 +132,7 @@ class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: Mu
// All music information is grouped as it is indexed by other components. // All music information is grouped as it is indexed by other components.
for (rawSong in rawSongs) { 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 // 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 // UID is sufficient for something like this, and also prevents collisions from
// causing severe issues elsewhere. // 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 // Now that all songs are processed, also process albums and group them into their
// respective artists. // 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 (album in albums) {
for (rawArtist in album.rawArtists) { for (rawArtist in album.rawArtists) {
val key = RawArtist.Key(rawArtist) 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. // Artists and genres do not need to be grouped and can be processed immediately.
val artists = artistGrouping.values.mapTo(mutableSetOf()) { ArtistImpl(it, musicSettings) } val artists = artistGrouping.values.mapTo(mutableSetOf()) { ArtistImpl(it, nameFactory) }
val genres = genreGrouping.values.mapTo(mutableSetOf()) { GenreImpl(it, musicSettings) } val genres = genreGrouping.values.mapTo(mutableSetOf()) { GenreImpl(it, nameFactory) }
return DeviceLibraryImpl(songGrouping.values.toSet(), albums, artists, genres) return DeviceLibraryImpl(songGrouping.values.toSet(), albums, artists, genres)
} }

View file

@ -25,7 +25,6 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.fs.MimeType import org.oxycblt.auxio.music.fs.MimeType
@ -48,12 +47,15 @@ import org.oxycblt.auxio.util.update
* Library-backed implementation of [Song]. * Library-backed implementation of [Song].
* *
* @param rawSong The [RawSong] to derive the member data from. * @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 separators The [Separators] to parse multi-value tags with.
* @param musicSettings [MusicSettings] to for user parsing configuration.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class SongImpl(private val rawSong: RawSong, musicSettings: MusicSettings, separators: Separators) : class SongImpl(
Song { private val rawSong: RawSong,
nameFactory: Name.Known.Factory,
separators: Separators
) : Song {
override val uid = override val uid =
// Attempt to use a MusicBrainz ID first before falling back to a hashed 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) } 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) update(rawSong.albumArtistNames)
} }
override val name = override val name =
Name.Known.from( nameFactory.parse(
requireNotNull(rawSong.name) { "Invalid raw: No title" }, requireNotNull(rawSong.name) { "Invalid raw: No title" }, rawSong.sortName)
rawSong.sortName,
musicSettings)
override val track = rawSong.track override val track = rawSong.track
override val disc = rawSong.disc?.let { Disc(it, rawSong.subtitle) } 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]. * Library-backed implementation of [Album].
* *
* @param grouping [Grouping] to derive the member data from. * @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) * @author Alexander Capehart (OxygenCobalt)
*/ */
class AlbumImpl( class AlbumImpl(grouping: Grouping<RawAlbum, SongImpl>, nameFactory: Name.Known.Factory) : Album {
grouping: Grouping<RawAlbum, SongImpl>,
musicSettings: MusicSettings,
) : Album {
private val rawAlbum = grouping.raw.inner private val rawAlbum = grouping.raw.inner
override val uid = override val uid =
@ -275,7 +272,7 @@ class AlbumImpl(
update(rawAlbum.name) update(rawAlbum.name)
update(rawAlbum.rawArtists.map { it.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 dates: Date.Range?
override val releaseType = rawAlbum.releaseType ?: ReleaseType.Album(null) override val releaseType = rawAlbum.releaseType ?: ReleaseType.Album(null)
override val coverUri = CoverUri(rawAlbum.mediaStoreId.toCoverUri(), grouping.raw.src.uri) override val coverUri = CoverUri(rawAlbum.mediaStoreId.toCoverUri(), grouping.raw.src.uri)
@ -376,10 +373,10 @@ class AlbumImpl(
* Library-backed implementation of [Artist]. * Library-backed implementation of [Artist].
* *
* @param grouping [Grouping] to derive the member data from. * @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) * @author Alexander Capehart (OxygenCobalt)
*/ */
class ArtistImpl(grouping: Grouping<RawArtist, Music>, musicSettings: MusicSettings) : Artist { class ArtistImpl(grouping: Grouping<RawArtist, Music>, nameFactory: Name.Known.Factory) : Artist {
private val rawArtist = grouping.raw.inner private val rawArtist = grouping.raw.inner
override val uid = override val uid =
@ -387,7 +384,7 @@ class ArtistImpl(grouping: Grouping<RawArtist, Music>, musicSettings: MusicSetti
rawArtist.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ARTISTS, it) } rawArtist.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ARTISTS, it) }
?: Music.UID.auxio(MusicType.ARTISTS) { update(rawArtist.name) } ?: Music.UID.auxio(MusicType.ARTISTS) { update(rawArtist.name) }
override val 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) ?: Name.Unknown(R.string.def_artist)
override val songs: Set<Song> override val songs: Set<Song>
@ -473,15 +470,15 @@ class ArtistImpl(grouping: Grouping<RawArtist, Music>, musicSettings: MusicSetti
* Library-backed implementation of [Genre]. * Library-backed implementation of [Genre].
* *
* @param grouping [Grouping] to derive the member data from. * @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) * @author Alexander Capehart (OxygenCobalt)
*/ */
class GenreImpl(grouping: Grouping<RawGenre, SongImpl>, musicSettings: MusicSettings) : Genre { class GenreImpl(grouping: Grouping<RawGenre, SongImpl>, nameFactory: Name.Known.Factory) : Genre {
private val rawGenre = grouping.raw.inner private val rawGenre = grouping.raw.inner
override val uid = Music.UID.auxio(MusicType.GENRES) { update(rawGenre.name) } override val uid = Music.UID.auxio(MusicType.GENRES) { update(rawGenre.name) }
override val 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) ?: Name.Unknown(R.string.def_genre)
override val songs: Set<Song> override val songs: Set<Song>

View file

@ -57,36 +57,6 @@ sealed interface Name : Comparable<Name> {
/** A tokenized version of the name that will be compared. */ /** A tokenized version of the name that will be compared. */
@VisibleForTesting(VisibleForTesting.PROTECTED) abstract val sortTokens: List<SortToken> @VisibleForTesting(VisibleForTesting.PROTECTED) abstract val sortTokens: List<SortToken>
/** 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<SortToken> {
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 final override val thumb: String
get() = get() =
// TODO: Remove these safety checks once you have real unit testing // TODO: Remove these safety checks once you have real unit testing
@ -110,20 +80,30 @@ sealed interface Name : Comparable<Name> {
is Unknown -> 1 is Unknown -> 1
} }
companion object { interface Factory {
/** /**
* Create a new instance of [Name.Known] * Create a new instance of [Name.Known]
* *
* @param raw The raw name obtained from the music item * @param raw The raw name obtained from the music item
* @param sort The raw sort 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 = fun parse(raw: String, sort: String?): Known
if (musicSettings.intelligentSorting) {
IntelligentKnownName(raw, sort) companion object {
} else { /**
SimpleKnownName(raw, sort) * 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) * @author Alexander Capehart (OxygenCobalt)
*/ */
private data class SimpleKnownName(override val raw: String, override val sort: String?) : @VisibleForTesting
Name.Known() { data class SimpleKnownName(override val raw: String, override val sort: String?) : Name.Known() {
override val sortTokens = listOf(parseToken(sort ?: raw)) override val sortTokens = listOf(parseToken(sort ?: raw))
private fun parseToken(name: String): SortToken { 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 // Always use lexicographic mode since we aren't parsing any numeric components
return SortToken(collationKey, SortToken.Type.LEXICOGRAPHIC) 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) * @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() { Name.Known() {
override val sortTokens = parseTokens(sort ?: raw) 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 { companion object {
private val TOKEN_REGEX by lazy { Regex("(\\d+)|(\\D+)") } 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<SortToken> {
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
}
}

View file

@ -18,6 +18,9 @@
package org.oxycblt.auxio.music.metadata 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 * 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. * that may be delimited with a separator character.
@ -40,9 +43,22 @@ interface Separators {
const val SLASH = '/' const val SLASH = '/'
const val PLUS = '+' const val PLUS = '+'
const val AND = '&' 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) = @VisibleForTesting
if (selector.isNotEmpty()) CharSeparators(selector.toSet()) else NoSeparators fun from(chars: String) =
if (chars.isNotEmpty()) {
CharSeparators(chars.toSet())
} else {
NoSeparators
}
} }
} }

View file

@ -77,7 +77,6 @@ private class TagWorkerImpl(
private val rawSong: RawSong, private val rawSong: RawSong,
private val future: Future<TrackGroupArray> private val future: Future<TrackGroupArray>
) : TagWorker { ) : TagWorker {
override fun poll(): RawSong? { override fun poll(): RawSong? {
if (!future.isDone) { if (!future.isDone) {
// Not done yet, nothing to do. // Not done yet, nothing to do.

View file

@ -19,7 +19,6 @@
package org.oxycblt.auxio.music.user package org.oxycblt.auxio.music.user
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song 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]. * Clone the data in this instance to a new [PlaylistImpl] with the given [name].
* *
* @param name The new name to use. * @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) = fun edit(name: String, nameFactory: Name.Known.Factory) =
PlaylistImpl(uid, Name.Known.from(name, null, musicSettings), songs) PlaylistImpl(uid, nameFactory.parse(name, null), songs)
/** /**
* Clone the data in this instance to a new [PlaylistImpl] with the given [Song]s. * 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 name The name of the playlist.
* @param songs The songs to initially populate the playlist with. * @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<Song>, musicSettings: MusicSettings) = fun from(name: String, songs: List<Song>, nameFactory: Name.Known.Factory) =
PlaylistImpl( PlaylistImpl(Music.UID.auxio(MusicType.PLAYLISTS), nameFactory.parse(name, null), songs)
Music.UID.auxio(MusicType.PLAYLISTS),
Name.Known.from(name, null, musicSettings),
songs)
/** /**
* Populate a new instance from a read [RawPlaylist]. * Populate a new instance from a read [RawPlaylist].
* *
* @param rawPlaylist The [RawPlaylist] to read from. * @param rawPlaylist The [RawPlaylist] to read from.
* @param deviceLibrary The [DeviceLibrary] to initialize 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( fun fromRaw(
rawPlaylist: RawPlaylist, rawPlaylist: RawPlaylist,
deviceLibrary: DeviceLibrary, deviceLibrary: DeviceLibrary,
musicSettings: MusicSettings nameFactory: Name.Known.Factory
) = ) =
PlaylistImpl( PlaylistImpl(
rawPlaylist.playlistInfo.playlistUid, rawPlaylist.playlistInfo.playlistUid,
Name.Known.from(rawPlaylist.playlistInfo.name, null, musicSettings), nameFactory.parse(rawPlaylist.playlistInfo.name, null),
rawPlaylist.songs.mapNotNull { deviceLibrary.findSong(it.songUid) }) rawPlaylist.songs.mapNotNull { deviceLibrary.findSong(it.songUid) })
} }
} }

View file

@ -26,6 +26,7 @@ import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.device.DeviceLibrary 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.logD
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
@ -144,7 +145,9 @@ constructor(private val playlistDao: PlaylistDao, private val musicSettings: Mus
UserLibrary.Factory { UserLibrary.Factory {
override suspend fun query() = override suspend fun query() =
try { try {
playlistDao.readRawPlaylists() val rawPlaylists = playlistDao.readRawPlaylists()
logD("Successfully read ${rawPlaylists.size} playlists")
rawPlaylists
} catch (e: Exception) { } catch (e: Exception) {
logE("Unable to read playlists: $e") logE("Unable to read playlists: $e")
listOf() listOf()
@ -154,11 +157,10 @@ constructor(private val playlistDao: PlaylistDao, private val musicSettings: Mus
rawPlaylists: List<RawPlaylist>, rawPlaylists: List<RawPlaylist>,
deviceLibrary: DeviceLibrary deviceLibrary: DeviceLibrary
): MutableUserLibrary { ): MutableUserLibrary {
logD("Successfully read ${rawPlaylists.size} playlists") val nameFactory = Name.Known.Factory.from(musicSettings)
// Convert the database playlist information to actual usable playlists.
val playlistMap = mutableMapOf<Music.UID, PlaylistImpl>() val playlistMap = mutableMapOf<Music.UID, PlaylistImpl>()
for (rawPlaylist in rawPlaylists) { for (rawPlaylist in rawPlaylists) {
val playlistImpl = PlaylistImpl.fromRaw(rawPlaylist, deviceLibrary, musicSettings) val playlistImpl = PlaylistImpl.fromRaw(rawPlaylist, deviceLibrary, nameFactory)
playlistMap[playlistImpl.uid] = playlistImpl playlistMap[playlistImpl.uid] = playlistImpl
} }
return UserLibraryImpl(playlistDao, playlistMap, musicSettings) 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 fun findPlaylist(name: String) = playlistMap.values.find { it.name.raw == name }
override suspend fun createPlaylist(name: String, songs: List<Song>): Playlist? { override suspend fun createPlaylist(name: String, songs: List<Song>): 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 } synchronized(this) { playlistMap[playlistImpl.uid] = playlistImpl }
val rawPlaylist = val rawPlaylist =
RawPlaylist( RawPlaylist(
@ -207,7 +209,9 @@ private class UserLibraryImpl(
val playlistImpl = val playlistImpl =
synchronized(this) { synchronized(this) {
requireNotNull(playlistMap[playlist.uid]) { "Cannot rename invalid playlist" } 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 { return try {

View file

@ -22,342 +22,352 @@ import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.MusicSettings
class NameTest { class NameTest {
private fun mockIntelligentSorting(enabled: Boolean) = @Test
mockk<MusicSettings>() { every { intelligentSorting } returns enabled } fun name_simple_from_settings() {
val musicSettings = mockk<MusicSettings> { every { intelligentSorting } returns false }
assertTrue(Name.Known.Factory.from(musicSettings) is SimpleKnownName.Factory)
}
@Test @Test
fun name_from_simple_withoutPunct() { fun name_intelligent_from_settings() {
val name = Name.Known.from("Loveless", null, mockIntelligentSorting(false)) val musicSettings = mockk<MusicSettings> { 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("Loveless", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("L", name.thumb) assertEquals("L", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("Loveless", only.collationKey.sourceString) assertEquals("Loveless", only.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
fun name_from_simple_withPunct() { fun name_simple_withPunct() {
val name = Name.Known.from("alt-J", null, mockIntelligentSorting(false)) val name = SimpleKnownName("alt-J", null)
assertEquals("alt-J", name.raw) assertEquals("alt-J", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("A", name.thumb) assertEquals("A", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("altJ", only.collationKey.sourceString) assertEquals("altJ", only.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
fun name_from_simple_oopsAllPunct() { fun name_simple_oopsAllPunct() {
val name = Name.Known.from("!!!", null, mockIntelligentSorting(false)) val name = SimpleKnownName("!!!", null)
assertEquals("!!!", name.raw) assertEquals("!!!", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("!", name.thumb) assertEquals("!", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("!!!", only.collationKey.sourceString) assertEquals("!!!", only.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
fun name_from_simple_spacedPunct() { fun name_simple_spacedPunct() {
val name = Name.Known.from("& Yet & Yet", null, mockIntelligentSorting(false)) val name = SimpleKnownName("& Yet & Yet", null)
assertEquals("& Yet & Yet", name.raw) assertEquals("& Yet & Yet", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("Y", name.thumb) assertEquals("Y", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Yet Yet", first.collationKey.sourceString) assertEquals("Yet Yet", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
} }
@Test @Test
fun name_from_simple_withSort() { fun name_simple_withSort() {
val name = Name.Known.from("The Smile", "Smile", mockIntelligentSorting(false)) val name = SimpleKnownName("The Smile", "Smile")
assertEquals("The Smile", name.raw) assertEquals("The Smile", name.raw)
assertEquals("Smile", name.sort) assertEquals("Smile", name.sort)
assertEquals("S", name.thumb) assertEquals("S", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("Smile", only.collationKey.sourceString) assertEquals("Smile", only.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
fun name_from_intelligent_withoutPunct_withoutArticle_withoutNumerics() { fun name_intelligent_withoutPunct_withoutArticle_withoutNumerics() {
val name = Name.Known.from("Loveless", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("Loveless", null)
assertEquals("Loveless", name.raw) assertEquals("Loveless", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("L", name.thumb) assertEquals("L", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("Loveless", only.collationKey.sourceString) assertEquals("Loveless", only.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
fun name_from_intelligent_withoutPunct_withoutArticle_withSpacedStartNumerics() { fun name_intelligent_withoutPunct_withoutArticle_withSpacedStartNumerics() {
val name = Name.Known.from("15 Step", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("15 Step", null)
assertEquals("15 Step", name.raw) assertEquals("15 Step", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("#", name.thumb) assertEquals("#", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("15", first.collationKey.sourceString) assertEquals("15", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, first.type) assertEquals(SortToken.Type.NUMERIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("Step", second.collationKey.sourceString) assertEquals("Step", second.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, second.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type)
} }
@Test @Test
fun name_from_intelligent_withoutPunct_withoutArticle_withPackedStartNumerics() { fun name_intelligent_withoutPunct_withoutArticle_withPackedStartNumerics() {
val name = Name.Known.from("23Kid", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("23Kid", null)
assertEquals("23Kid", name.raw) assertEquals("23Kid", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("#", name.thumb) assertEquals("#", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("23", first.collationKey.sourceString) assertEquals("23", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, first.type) assertEquals(SortToken.Type.NUMERIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("Kid", second.collationKey.sourceString) assertEquals("Kid", second.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, second.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type)
} }
@Test @Test
fun name_from_intelligent_withoutPunct_withoutArticle_withSpacedMiddleNumerics() { fun name_intelligent_withoutPunct_withoutArticle_withSpacedMiddleNumerics() {
val name = Name.Known.from("Foo 1 2 Bar", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("Foo 1 2 Bar", null)
assertEquals("Foo 1 2 Bar", name.raw) assertEquals("Foo 1 2 Bar", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("F", name.thumb) assertEquals("F", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Foo", first.collationKey.sourceString) assertEquals("Foo", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("1", second.collationKey.sourceString) assertEquals("1", second.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, second.type) assertEquals(SortToken.Type.NUMERIC, second.type)
val third = name.sortTokens[2] val third = name.sortTokens[2]
assertEquals(" ", third.collationKey.sourceString) assertEquals(" ", third.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, third.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type)
val fourth = name.sortTokens[3] val fourth = name.sortTokens[3]
assertEquals("2", fourth.collationKey.sourceString) assertEquals("2", fourth.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, fourth.type) assertEquals(SortToken.Type.NUMERIC, fourth.type)
val fifth = name.sortTokens[4] val fifth = name.sortTokens[4]
assertEquals("Bar", fifth.collationKey.sourceString) assertEquals("Bar", fifth.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, fifth.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, fifth.type)
} }
@Test @Test
fun name_from_intelligent_withoutPunct_withoutArticle_withPackedMiddleNumerics() { fun name_intelligent_withoutPunct_withoutArticle_withPackedMiddleNumerics() {
val name = Name.Known.from("Foo12Bar", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("Foo12Bar", null)
assertEquals("Foo12Bar", name.raw) assertEquals("Foo12Bar", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("F", name.thumb) assertEquals("F", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Foo", first.collationKey.sourceString) assertEquals("Foo", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("12", second.collationKey.sourceString) assertEquals("12", second.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, second.type) assertEquals(SortToken.Type.NUMERIC, second.type)
val third = name.sortTokens[2] val third = name.sortTokens[2]
assertEquals("Bar", third.collationKey.sourceString) assertEquals("Bar", third.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, third.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type)
} }
@Test @Test
fun name_from_intelligent_withoutPunct_withoutArticle_withSpacedEndNumerics() { fun name_intelligent_withoutPunct_withoutArticle_withSpacedEndNumerics() {
val name = Name.Known.from("Foo 1", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("Foo 1", null)
assertEquals("Foo 1", name.raw) assertEquals("Foo 1", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("F", name.thumb) assertEquals("F", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Foo", first.collationKey.sourceString) assertEquals("Foo", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("1", second.collationKey.sourceString) assertEquals("1", second.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, second.type) assertEquals(SortToken.Type.NUMERIC, second.type)
} }
@Test @Test
fun name_from_intelligent_withoutPunct_withoutArticle_withPackedEndNumerics() { fun name_intelligent_withoutPunct_withoutArticle_withPackedEndNumerics() {
val name = Name.Known.from("Error404", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("Error404", null)
assertEquals("Error404", name.raw) assertEquals("Error404", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("E", name.thumb) assertEquals("E", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Error", first.collationKey.sourceString) assertEquals("Error", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("404", second.collationKey.sourceString) assertEquals("404", second.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, second.type) assertEquals(SortToken.Type.NUMERIC, second.type)
} }
@Test @Test
fun name_from_intelligent_withoutPunct_withThe_withoutNumerics() { fun name_intelligent_withoutPunct_withThe_withoutNumerics() {
val name = Name.Known.from("The National Anthem", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("The National Anthem", null)
assertEquals("The National Anthem", name.raw) assertEquals("The National Anthem", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("N", name.thumb) assertEquals("N", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("National Anthem", first.collationKey.sourceString) assertEquals("National Anthem", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
} }
@Test @Test
fun name_from_intelligent_withoutPunct_withAn_withoutNumerics() { fun name_intelligent_withoutPunct_withAn_withoutNumerics() {
val name = Name.Known.from("An Eagle in Your Mind", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("An Eagle in Your Mind", null)
assertEquals("An Eagle in Your Mind", name.raw) assertEquals("An Eagle in Your Mind", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("E", name.thumb) assertEquals("E", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Eagle in Your Mind", first.collationKey.sourceString) assertEquals("Eagle in Your Mind", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
} }
@Test @Test
fun name_from_intelligent_withoutPunct_withA_withoutNumerics() { fun name_intelligent_withoutPunct_withA_withoutNumerics() {
val name = Name.Known.from("A Song For Our Fathers", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("A Song For Our Fathers", null)
assertEquals("A Song For Our Fathers", name.raw) assertEquals("A Song For Our Fathers", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("S", name.thumb) assertEquals("S", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Song For Our Fathers", first.collationKey.sourceString) assertEquals("Song For Our Fathers", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
} }
@Test @Test
fun name_from_intelligent_withPunct_withoutArticle_withoutNumerics() { fun name_intelligent_withPunct_withoutArticle_withoutNumerics() {
val name = Name.Known.from("alt-J", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("alt-J", null)
assertEquals("alt-J", name.raw) assertEquals("alt-J", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("A", name.thumb) assertEquals("A", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("altJ", only.collationKey.sourceString) assertEquals("altJ", only.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
fun name_from_intelligent_oopsAllPunct_withoutArticle_withoutNumerics() { fun name_intelligent_oopsAllPunct_withoutArticle_withoutNumerics() {
val name = Name.Known.from("!!!", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("!!!", null)
assertEquals("!!!", name.raw) assertEquals("!!!", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("!", name.thumb) assertEquals("!", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("!!!", only.collationKey.sourceString) assertEquals("!!!", only.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
fun name_from_intelligent_withoutPunct_shortArticle_withNumerics() { fun name_intelligent_withoutPunct_shortArticle_withNumerics() {
val name = Name.Known.from("the 1", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("the 1", null)
assertEquals("the 1", name.raw) assertEquals("the 1", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("#", name.thumb) assertEquals("#", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("1", first.collationKey.sourceString) assertEquals("1", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, first.type) assertEquals(SortToken.Type.NUMERIC, first.type)
} }
@Test @Test
fun name_from_intelligent_spacedPunct_withoutArticle_withoutNumerics() { fun name_intelligent_spacedPunct_withoutArticle_withoutNumerics() {
val name = Name.Known.from("& Yet & Yet", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("& Yet & Yet", null)
assertEquals("& Yet & Yet", name.raw) assertEquals("& Yet & Yet", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("Y", name.thumb) assertEquals("Y", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Yet Yet", first.collationKey.sourceString) assertEquals("Yet Yet", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
} }
@Test @Test
fun name_from_intelligent_withPunct_withoutArticle_withNumerics() { fun name_intelligent_withPunct_withoutArticle_withNumerics() {
val name = Name.Known.from("Design : 2 : 3", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("Design : 2 : 3", null)
assertEquals("Design : 2 : 3", name.raw) assertEquals("Design : 2 : 3", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("D", name.thumb) assertEquals("D", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Design", first.collationKey.sourceString) assertEquals("Design", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("2", second.collationKey.sourceString) assertEquals("2", second.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, second.type) assertEquals(SortToken.Type.NUMERIC, second.type)
val third = name.sortTokens[2] val third = name.sortTokens[2]
assertEquals(" ", third.collationKey.sourceString) assertEquals(" ", third.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, third.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type)
val fourth = name.sortTokens[3] val fourth = name.sortTokens[3]
assertEquals("3", fourth.collationKey.sourceString) assertEquals("3", fourth.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, fourth.type) assertEquals(SortToken.Type.NUMERIC, fourth.type)
} }
@Test @Test
fun name_from_intelligent_oopsAllPunct_withoutArticle_oopsAllNumerics() { fun name_intelligent_oopsAllPunct_withoutArticle_oopsAllNumerics() {
val name = Name.Known.from("2 + 2 = 5", null, mockIntelligentSorting(true)) val name = IntelligentKnownName("2 + 2 = 5", null)
assertEquals("2 + 2 = 5", name.raw) assertEquals("2 + 2 = 5", name.raw)
assertEquals(null, name.sort) assertEquals(null, name.sort)
assertEquals("#", name.thumb) assertEquals("#", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("2", first.collationKey.sourceString) assertEquals("2", first.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, first.type) assertEquals(SortToken.Type.NUMERIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals(" ", second.collationKey.sourceString) assertEquals(" ", second.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, second.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type)
val third = name.sortTokens[2] val third = name.sortTokens[2]
assertEquals("2", third.collationKey.sourceString) assertEquals("2", third.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, third.type) assertEquals(SortToken.Type.NUMERIC, third.type)
val fourth = name.sortTokens[3] val fourth = name.sortTokens[3]
assertEquals(" ", fourth.collationKey.sourceString) assertEquals(" ", fourth.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, fourth.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, fourth.type)
val fifth = name.sortTokens[4] val fifth = name.sortTokens[4]
assertEquals("5", fifth.collationKey.sourceString) assertEquals("5", fifth.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.NUMERIC, fifth.type) assertEquals(SortToken.Type.NUMERIC, fifth.type)
} }
@Test @Test
fun name_from_intelligent_withSort() { fun name_intelligent_withSort() {
val name = Name.Known.from("The Smile", "Smile", mockIntelligentSorting(true)) val name = IntelligentKnownName("The Smile", "Smile")
assertEquals("The Smile", name.raw) assertEquals("The Smile", name.raw)
assertEquals("Smile", name.sort) assertEquals("Smile", name.sort)
assertEquals("S", name.thumb) assertEquals("S", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("Smile", only.collationKey.sourceString) assertEquals("Smile", only.collationKey.sourceString)
assertEquals(Name.Known.SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
fun name_equals_simple() { fun name_equals_simple() {
val a = Name.Known.from("The Same", "Same", mockIntelligentSorting(false)) val a = SimpleKnownName("The Same", "Same")
val b = Name.Known.from("The Same", "Same", mockIntelligentSorting(false)) val b = SimpleKnownName("The Same", "Same")
assertEquals(a, b) assertEquals(a, b)
} }
@Test @Test
fun name_equals_differentSort() { fun name_equals_differentSort() {
val a = Name.Known.from("The Same", "Same", mockIntelligentSorting(false)) val a = SimpleKnownName("The Same", "Same")
val b = Name.Known.from("The Same", null, mockIntelligentSorting(false)) val b = SimpleKnownName("The Same", null)
assertNotEquals(a, b) assertNotEquals(a, b)
assertNotEquals(a.hashCode(), b.hashCode()) assertNotEquals(a.hashCode(), b.hashCode())
} }
@Test @Test
fun name_equals_intelligent_differentTokens() { fun name_equals_intelligent_differentTokens() {
val a = Name.Known.from("The Same", "Same", mockIntelligentSorting(true)) val a = IntelligentKnownName("The Same", "Same")
val b = Name.Known.from("Same", "Same", mockIntelligentSorting(true)) val b = IntelligentKnownName("Same", "Same")
assertNotEquals(a, b) assertNotEquals(a, b)
assertNotEquals(a.hashCode(), b.hashCode()) assertNotEquals(a.hashCode(), b.hashCode())
} }
@Test @Test
fun name_compareTo_simple_withoutSort_withoutArticle_withoutNumeric() { fun name_compareTo_simple_withoutSort_withoutArticle_withoutNumeric() {
val a = Name.Known.from("A", null, mockIntelligentSorting(false)) val a = SimpleKnownName("A", null)
val b = Name.Known.from("B", null, mockIntelligentSorting(false)) val b = SimpleKnownName("B", null)
assertEquals(-1, a.compareTo(b)) assertEquals(-1, a.compareTo(b))
} }
@Test @Test
fun name_compareTo_simple_withoutSort_withArticle_withoutNumeric() { fun name_compareTo_simple_withoutSort_withArticle_withoutNumeric() {
val a = Name.Known.from("A Brain in a Bottle", null, mockIntelligentSorting(false)) val a = SimpleKnownName("A Brain in a Bottle", null)
val b = Name.Known.from("Acid Rain", null, mockIntelligentSorting(false)) val b = SimpleKnownName("Acid Rain", null)
val c = Name.Known.from("Boralis / Contrastellar", null, mockIntelligentSorting(false)) val c = SimpleKnownName("Boralis / Contrastellar", null)
val d = Name.Known.from("Breathe In", null, mockIntelligentSorting(false)) val d = SimpleKnownName("Breathe In", null)
assertEquals(-1, a.compareTo(b)) assertEquals(-1, a.compareTo(b))
assertEquals(-1, a.compareTo(c)) assertEquals(-1, a.compareTo(c))
assertEquals(-1, a.compareTo(d)) assertEquals(-1, a.compareTo(d))
@ -365,40 +375,40 @@ class NameTest {
@Test @Test
fun name_compareTo_simple_withSort_withoutArticle_withNumeric() { fun name_compareTo_simple_withSort_withoutArticle_withNumeric() {
val a = Name.Known.from("15 Step", null, mockIntelligentSorting(false)) val a = SimpleKnownName("15 Step", null)
val b = Name.Known.from("128 Harps", null, mockIntelligentSorting(false)) val b = SimpleKnownName("128 Harps", null)
val c = Name.Known.from("1969", null, mockIntelligentSorting(false)) val c = SimpleKnownName("1969", null)
assertEquals(1, a.compareTo(b)) assertEquals(1, a.compareTo(b))
assertEquals(-1, a.compareTo(c)) assertEquals(-1, a.compareTo(c))
} }
@Test @Test
fun name_compareTo_simple_withPartialSort() { fun name_compareTo_simple_withPartialSort() {
val a = Name.Known.from("A", "C", mockIntelligentSorting(false)) val a = SimpleKnownName("A", "C")
val b = Name.Known.from("B", null, mockIntelligentSorting(false)) val b = SimpleKnownName("B", null)
assertEquals(1, a.compareTo(b)) assertEquals(1, a.compareTo(b))
} }
@Test @Test
fun name_compareTo_simple_withSort() { fun name_compareTo_simple_withSort() {
val a = Name.Known.from("D", "A", mockIntelligentSorting(false)) val a = SimpleKnownName("D", "A")
val b = Name.Known.from("C", "B", mockIntelligentSorting(false)) val b = SimpleKnownName("C", "B")
assertEquals(-1, a.compareTo(b)) assertEquals(-1, a.compareTo(b))
} }
@Test @Test
fun name_compareTo_intelligent_withoutSort_withoutArticle_withoutNumeric() { fun name_compareTo_intelligent_withoutSort_withoutArticle_withoutNumeric() {
val a = Name.Known.from("A", null, mockIntelligentSorting(true)) val a = IntelligentKnownName("A", null)
val b = Name.Known.from("B", null, mockIntelligentSorting(true)) val b = IntelligentKnownName("B", null)
assertEquals(-1, a.compareTo(b)) assertEquals(-1, a.compareTo(b))
} }
@Test @Test
fun name_compareTo_intelligent_withoutSort_withArticle_withoutNumeric() { fun name_compareTo_intelligent_withoutSort_withArticle_withoutNumeric() {
val a = Name.Known.from("A Brain in a Bottle", null, mockIntelligentSorting(true)) val a = IntelligentKnownName("A Brain in a Bottle", null)
val b = Name.Known.from("Acid Rain", null, mockIntelligentSorting(true)) val b = IntelligentKnownName("Acid Rain", null)
val c = Name.Known.from("Boralis / Contrastellar", null, mockIntelligentSorting(true)) val c = IntelligentKnownName("Boralis / Contrastellar", null)
val d = Name.Known.from("Breathe In", null, mockIntelligentSorting(true)) val d = IntelligentKnownName("Breathe In", null)
assertEquals(1, a.compareTo(b)) assertEquals(1, a.compareTo(b))
assertEquals(1, a.compareTo(c)) assertEquals(1, a.compareTo(c))
assertEquals(-1, a.compareTo(d)) assertEquals(-1, a.compareTo(d))
@ -406,9 +416,9 @@ class NameTest {
@Test @Test
fun name_compareTo_intelligent_withoutSort_withoutArticle_withNumeric() { fun name_compareTo_intelligent_withoutSort_withoutArticle_withNumeric() {
val a = Name.Known.from("15 Step", null, mockIntelligentSorting(true)) val a = IntelligentKnownName("15 Step", null)
val b = Name.Known.from("128 Harps", null, mockIntelligentSorting(true)) val b = IntelligentKnownName("128 Harps", null)
val c = Name.Known.from("1969", null, mockIntelligentSorting(true)) val c = IntelligentKnownName("1969", null)
assertEquals(-1, a.compareTo(b)) assertEquals(-1, a.compareTo(b))
assertEquals(-1, b.compareTo(c)) assertEquals(-1, b.compareTo(c))
assertEquals(-2, a.compareTo(c)) assertEquals(-2, a.compareTo(c))
@ -416,15 +426,28 @@ class NameTest {
@Test @Test
fun name_compareTo_intelligent_withPartialSort_withoutArticle_withoutNumeric() { fun name_compareTo_intelligent_withPartialSort_withoutArticle_withoutNumeric() {
val a = Name.Known.from("A", "C", mockIntelligentSorting(false)) val a = SimpleKnownName("A", "C")
val b = Name.Known.from("B", null, mockIntelligentSorting(false)) val b = SimpleKnownName("B", null)
assertEquals(1, a.compareTo(b)) assertEquals(1, a.compareTo(b))
} }
@Test @Test
fun name_compareTo_intelligent_withSort_withoutArticle_withoutNumeric() { fun name_compareTo_intelligent_withSort_withoutArticle_withoutNumeric() {
val a = Name.Known.from("D", "A", mockIntelligentSorting(true)) val a = IntelligentKnownName("D", "A")
val b = Name.Known.from("C", "B", mockIntelligentSorting(true)) 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)) assertEquals(-1, a.compareTo(b))
} }
} }