Compare commits

...

4 Commits

Author SHA1 Message Date
cfff342a3a
Replace JavaFX services with Kotlin coroutines
All checks were successful
Pull Request / build (pull_request) Successful in 4m4s
2024-11-02 18:53:53 +01:00
a92a57bffe
Add coroutines dependencies 2024-10-25 21:34:03 +02:00
959a5c2680
Refactor SearchController 2024-10-24 21:13:10 +02:00
a1a7567bd0
Refactir SqliteDictionary 2024-10-21 21:13:27 +02:00
12 changed files with 180 additions and 287 deletions

View File

@ -24,6 +24,8 @@ dependencies {
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.html.jvm) implementation(libs.kotlinx.html.jvm)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.javafx)
implementation(libs.segment) implementation(libs.segment)

View File

@ -10,6 +10,7 @@ sqlite-jdbc = "3.46.0.1"
kotlinx-serialization-json = "1.7.1" kotlinx-serialization-json = "1.7.1"
kotlinx-html-jvm = "0.11.0" kotlinx-html-jvm = "0.11.0"
kotlinx-coroutines = "1.9.0"
segment = "0.3.1" segment = "0.3.1"
@ -27,6 +28,8 @@ sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite-jdbc" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
kotlinx-html-jvm = { module = "org.jetbrains.kotlinx:kotlinx-html-jvm", version.ref = "kotlinx-html-jvm" } kotlinx-html-jvm = { module = "org.jetbrains.kotlinx:kotlinx-html-jvm", version.ref = "kotlinx-html-jvm" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-javafx = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-javafx", version.ref = "kotlinx-coroutines" }
segment = { module = "com.github.houbb:segment", version.ref = "segment" } segment = { module = "com.github.houbb:segment", version.ref = "segment" }
slf4j-nop = { module = "org.slf4j:slf4j-nop", version.ref = "slf4j" } slf4j-nop = { module = "org.slf4j:slf4j-nop", version.ref = "slf4j" }

View File

@ -1,28 +1,25 @@
package com.marvinelsen.willow package com.marvinelsen.willow
import com.marvinelsen.willow.domain.SearchMode import com.marvinelsen.willow.domain.SearchMode
import com.marvinelsen.willow.domain.SqliteDictionary
import com.marvinelsen.willow.ui.DictionaryEntryFx import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.SentenceFx import com.marvinelsen.willow.ui.SentenceFx
import com.marvinelsen.willow.ui.services.FindCharacterService import com.marvinelsen.willow.ui.toDomain
import com.marvinelsen.willow.ui.services.FindSentencesService import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.services.FindWordsBeginningService
import com.marvinelsen.willow.ui.services.FindWordsContainingService
import com.marvinelsen.willow.ui.services.SearchService
import com.marvinelsen.willow.ui.util.ClipboardHelper import com.marvinelsen.willow.ui.util.ClipboardHelper
import javafx.beans.property.BooleanProperty
import javafx.beans.property.ObjectProperty import javafx.beans.property.ObjectProperty
import javafx.beans.property.ReadOnlyBooleanProperty import javafx.beans.property.ReadOnlyBooleanProperty
import javafx.beans.property.ReadOnlyObjectProperty import javafx.beans.property.ReadOnlyObjectProperty
import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.collections.FXCollections import javafx.collections.FXCollections
import javafx.collections.ObservableList import javafx.collections.ObservableList
import javafx.event.EventHandler import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
class Model( class Model(
private val searchService: SearchService, private val dictionary: SqliteDictionary,
private val findWordsBeginningService: FindWordsBeginningService,
private val findWordsContainingService: FindWordsContainingService,
private val findCharacterService: FindCharacterService,
private val findSentencesService: FindSentencesService,
) { ) {
private val internalSelectedEntry: ObjectProperty<DictionaryEntryFx> = SimpleObjectProperty() private val internalSelectedEntry: ObjectProperty<DictionaryEntryFx> = SimpleObjectProperty()
private val internalSearchResults: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList() private val internalSearchResults: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
@ -31,6 +28,12 @@ class Model(
private val internalCharacters: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList() private val internalCharacters: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
private val internalSentences: ObservableList<SentenceFx> = FXCollections.observableArrayList() private val internalSentences: ObservableList<SentenceFx> = FXCollections.observableArrayList()
private val internalIsSearching: BooleanProperty = SimpleBooleanProperty(false)
private val internalIsFindingWordsBeginning: BooleanProperty = SimpleBooleanProperty(false)
private val internalIsFindingWordsContaining: BooleanProperty = SimpleBooleanProperty(false)
private val internalIsFindingCharacters: BooleanProperty = SimpleBooleanProperty(false)
private val internalIsFindingSentences: BooleanProperty = SimpleBooleanProperty(false)
val selectedEntry: ReadOnlyObjectProperty<DictionaryEntryFx> = internalSelectedEntry val selectedEntry: ReadOnlyObjectProperty<DictionaryEntryFx> = internalSelectedEntry
val searchResults: ObservableList<DictionaryEntryFx> = val searchResults: ObservableList<DictionaryEntryFx> =
@ -44,54 +47,68 @@ class Model(
val sentences: ObservableList<SentenceFx> = val sentences: ObservableList<SentenceFx> =
FXCollections.unmodifiableObservableList(internalSentences) FXCollections.unmodifiableObservableList(internalSentences)
val isSearching: ReadOnlyBooleanProperty = searchService.runningProperty() val isSearching: ReadOnlyBooleanProperty = internalIsSearching
val isFindingWordsBeginning: ReadOnlyBooleanProperty = findWordsBeginningService.runningProperty() val isFindingWordsBeginning: ReadOnlyBooleanProperty = internalIsFindingWordsBeginning
val isFindingWordsContaining: ReadOnlyBooleanProperty = findWordsContainingService.runningProperty() val isFindingWordsContaining: ReadOnlyBooleanProperty = internalIsFindingWordsContaining
val isFindingCharacters: ReadOnlyBooleanProperty = findCharacterService.runningProperty() val isFindingCharacters: ReadOnlyBooleanProperty = internalIsFindingCharacters
val isFindingSentences: ReadOnlyBooleanProperty = findSentencesService.runningProperty() val isFindingSentences: ReadOnlyBooleanProperty = internalIsFindingSentences
init { private val coroutineScope = MainScope()
searchService.onSucceeded = EventHandler {
internalSearchResults.setAll(searchService.value)
}
findWordsBeginningService.onSucceeded = EventHandler {
internalWordsBeginning.setAll(findWordsBeginningService.value)
}
findWordsContainingService.onSucceeded = EventHandler {
internalWordsContaining.setAll(findWordsContainingService.value)
}
findCharacterService.onSucceeded = EventHandler {
internalCharacters.setAll(findCharacterService.value)
}
findSentencesService.onSucceeded = EventHandler {
internalSentences.setAll(findSentencesService.value)
}
}
fun search(query: String, searchMode: SearchMode) { fun search(query: String, searchMode: SearchMode) {
searchService.searchQuery = query coroutineScope.launch {
searchService.searchMode = searchMode internalIsSearching.value = true
searchService.restart() internalSearchResults.setAll(dictionary.search(query, searchMode).map { it.toFx() })
internalIsSearching.value = false
}
} }
fun findWordsBeginning() { fun findWordsBeginning() {
findWordsBeginningService.entry = internalSelectedEntry.value coroutineScope.launch {
findWordsBeginningService.restart() internalIsFindingWordsBeginning.value = true
internalWordsBeginning.setAll(
dictionary
.findWordsBeginningWith(internalSelectedEntry.value.toDomain())
.map { it.toFx() }
)
internalIsFindingWordsBeginning.value = false
}
} }
fun findWordsContaining() { fun findWordsContaining() {
findWordsContainingService.entry = internalSelectedEntry.value coroutineScope.launch {
findWordsContainingService.restart() internalIsFindingWordsContaining.value = true
internalWordsContaining.setAll(
dictionary
.findWordsContaining(internalSelectedEntry.value.toDomain())
.map { it.toFx() }
)
internalIsFindingWordsContaining.value = false
}
} }
fun findCharacters() { fun findCharacters() {
findCharacterService.entry = internalSelectedEntry.value coroutineScope.launch {
findCharacterService.restart() internalIsFindingCharacters.value = true
internalCharacters.setAll(
dictionary
.findCharactersOf(internalSelectedEntry.value.toDomain())
.map { it.toFx() }
)
internalIsFindingCharacters.value = false
}
} }
fun findSentences() { fun findSentences() {
findSentencesService.entry = internalSelectedEntry.value coroutineScope.launch {
findSentencesService.restart() internalIsFindingSentences.value = true
internalSentences.setAll(
dictionary
.findExampleSentencesFor(internalSelectedEntry.value.toDomain())
.map { it.toFx() }
)
internalIsFindingSentences.value = false
}
} }
fun selectEntry(entry: DictionaryEntryFx) { fun selectEntry(entry: DictionaryEntryFx) {

View File

@ -7,11 +7,6 @@ import com.marvinelsen.willow.ui.controllers.MainController
import com.marvinelsen.willow.ui.controllers.MenuController import com.marvinelsen.willow.ui.controllers.MenuController
import com.marvinelsen.willow.ui.controllers.SearchController import com.marvinelsen.willow.ui.controllers.SearchController
import com.marvinelsen.willow.ui.controllers.SearchResultsController import com.marvinelsen.willow.ui.controllers.SearchResultsController
import com.marvinelsen.willow.ui.services.FindCharacterService
import com.marvinelsen.willow.ui.services.FindSentencesService
import com.marvinelsen.willow.ui.services.FindWordsBeginningService
import com.marvinelsen.willow.ui.services.FindWordsContainingService
import com.marvinelsen.willow.ui.services.SearchService
import javafx.application.Application import javafx.application.Application
import javafx.fxml.FXMLLoader import javafx.fxml.FXMLLoader
import javafx.scene.Scene import javafx.scene.Scene
@ -45,17 +40,8 @@ class WillowApplication : Application() {
autoCommit = false autoCommit = false
} }
val dictionary = SqliteDictionary(connection) val dictionary = SqliteDictionary(connection)
val searchService = SearchService(dictionary)
val findWordsBeginningService = FindWordsBeginningService(dictionary)
val findWordsContainingService = FindWordsContainingService(dictionary)
val findCharacterService = FindCharacterService(dictionary)
val findSentenceService = FindSentencesService(dictionary)
val model = Model( val model = Model(
searchService, dictionary,
findWordsBeginningService,
findWordsContainingService,
findCharacterService,
findSentenceService
) )
val config = Config() val config = Config()
config.load() config.load()

View File

@ -1,12 +1,12 @@
package com.marvinelsen.willow.domain package com.marvinelsen.willow.domain
interface Dictionary { interface Dictionary {
fun search(query: String, searchMode: SearchMode): List<DictionaryEntry> suspend fun search(query: String, searchMode: SearchMode): List<DictionaryEntry>
fun findWordsBeginning(entry: DictionaryEntry): List<DictionaryEntry> suspend fun findWordsBeginningWith(entry: DictionaryEntry): List<DictionaryEntry>
fun findWordsContaining(entry: DictionaryEntry): List<DictionaryEntry> suspend fun findWordsContaining(entry: DictionaryEntry): List<DictionaryEntry>
fun findCharacters(entry: DictionaryEntry): List<DictionaryEntry> suspend fun findCharactersOf(entry: DictionaryEntry): List<DictionaryEntry>
fun findSentencesContaining(entry: DictionaryEntry): List<Sentence> suspend fun findExampleSentencesFor(entry: DictionaryEntry): List<Sentence>
} }

View File

@ -8,14 +8,18 @@ import com.github.houbb.segment.support.segment.impl.Segments
import com.github.houbb.segment.support.segment.mode.impl.SegmentModes import com.github.houbb.segment.support.segment.mode.impl.SegmentModes
import com.github.houbb.segment.support.segment.result.impl.SegmentResultHandlers import com.github.houbb.segment.support.segment.result.impl.SegmentResultHandlers
import com.github.houbb.segment.support.tagging.pos.tag.impl.SegmentPosTaggings import com.github.houbb.segment.support.tagging.pos.tag.impl.SegmentPosTaggings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.sql.Connection import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.ResultSet import java.sql.ResultSet
class SqliteDictionary(private val connection: Connection) : Dictionary { class SqliteDictionary(private val connection: Connection) : Dictionary {
private val whitespaceRegex = """\s+""".toRegex() private val whitespaceRegex = """\s+""".toRegex()
private val dispatcher = Dispatchers.IO
.limitedParallelism(1)
private val segmentBs = SegmentBs.newInstance() private val segmentBs = SegmentBs.newInstance()
.segment(Segments.defaults()) .segment(Segments.defaults())
.segmentData(SegmentPhraseDatas.define()) .segmentData(SegmentPhraseDatas.define())
@ -24,41 +28,29 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
.posTagging(SegmentPosTaggings.simple()) .posTagging(SegmentPosTaggings.simple())
.posData(SegmentPosDatas.define()) .posData(SegmentPosDatas.define())
private val searchSimplifiedPreparedStatement: PreparedStatement by lazy { private val searchSimplifiedSql = """
connection.prepareStatement(
"""
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
FROM entry FROM entry
WHERE simplified GLOB ? WHERE simplified GLOB ?
ORDER BY character_count ASC ORDER BY character_count ASC
""".trimIndent() """.trimIndent()
)
}
private val searchTraditionalPreparedStatement: PreparedStatement by lazy { private val searchTraditionalSql = """
connection.prepareStatement(
"""
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
FROM entry FROM entry
WHERE traditional GLOB ? WHERE traditional GLOB ?
ORDER BY character_count ASC ORDER BY character_count ASC
""".trimIndent() """.trimIndent()
)
}
private val searchPinyinPreparedStatement: PreparedStatement by lazy { private val searchPinyinSql = """
connection.prepareStatement(
"""
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
FROM entry FROM entry
WHERE searchable_pinyin GLOB ? WHERE searchable_pinyin GLOB ?
OR searchable_pinyin_with_tone_numbers GLOB ? OR searchable_pinyin_with_tone_numbers GLOB ?
ORDER BY character_count ASC ORDER BY character_count ASC
""".trimIndent() """.trimIndent()
)
}
private val searchSegments = """ private val searchSegmentsSql = """
WITH cte(id, segment) AS (VALUES ?) WITH cte(id, segment) AS (VALUES ?)
SELECT entry.traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT entry.traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
FROM entry INNER JOIN cte FROM entry INNER JOIN cte
@ -66,113 +58,113 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
ORDER BY cte.id ORDER BY cte.id
""".trimIndent() """.trimIndent()
private val findWordsBeginning: PreparedStatement by lazy { private val findWordsBeginningSql = """
connection.prepareStatement(
"""
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
FROM entry FROM entry
WHERE traditional GLOB ? WHERE traditional GLOB ?
ORDER BY character_count ASC ORDER BY character_count ASC
""".trimIndent() """.trimIndent()
)
}
private val findWordsContaining: PreparedStatement by lazy { private val findWordsContainingSql = """
connection.prepareStatement(
"""
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
FROM entry FROM entry
WHERE traditional LIKE ? WHERE traditional LIKE ?
ORDER BY character_count ASC ORDER BY character_count ASC
""".trimIndent() """.trimIndent()
)
}
private val findCharacters = """ private val findCharactersSql = """
WITH cte(id, character, syllable) AS (VALUES ?) WITH cte(id, character, pinyin) AS (VALUES ?)
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
FROM entry INNER JOIN cte FROM entry INNER JOIN cte
ON cte.character = entry.traditional ON cte.character = entry.traditional
WHERE cte.syllable = entry.pinyin_with_tone_numbers WHERE cte.pinyin = entry.pinyin_with_tone_numbers
ORDER BY cte.id ORDER BY cte.id
""".trimIndent() """.trimIndent()
private val findSentences: PreparedStatement by lazy { private val findExampleSentenceSql = """
connection.prepareStatement(
"""
SELECT traditional, simplified SELECT traditional, simplified
FROM sentence FROM sentence
WHERE traditional LIKE ? WHERE traditional LIKE ?
ORDER BY character_count ASC ORDER BY character_count ASC
""".trimIndent() """.trimIndent()
)
}
override fun search(query: String, searchMode: SearchMode) = when (searchMode) { override suspend fun search(query: String, searchMode: SearchMode) = withContext(dispatcher) {
when (searchMode) {
SearchMode.SIMPLIFIED -> searchSimplified(query) SearchMode.SIMPLIFIED -> searchSimplified(query)
SearchMode.TRADITIONAL -> searchTraditional(query) SearchMode.TRADITIONAL -> searchTraditional(query)
SearchMode.PINYIN -> searchPinyin(query) SearchMode.PINYIN -> searchPinyin(query)
SearchMode.SEGMENTS -> searchSegments(query) SearchMode.SEGMENTS -> searchSegmentsOf(query)
}
} }
private fun searchSimplified(query: String): List<DictionaryEntry> { private fun searchSimplified(simplified: String) =
searchSimplifiedPreparedStatement.setString(1, "$query*") connection.prepareStatement(searchSimplifiedSql).use { preparedStatement ->
preparedStatement.setString(1, "$simplified*")
val resultSet: ResultSet = searchSimplifiedPreparedStatement.executeQuery() preparedStatement.executeQuery().use {
it.toListOfDictionaryEntries()
return resultSet.toListOfDictionaryEntries() }
} }
private fun searchTraditional(query: String): List<DictionaryEntry> { private fun searchTraditional(traditional: String) =
searchTraditionalPreparedStatement.setString(1, "$query*") connection.prepareStatement(searchTraditionalSql).use { preparedStatement ->
preparedStatement.setString(1, "$traditional*")
val resultSet: ResultSet = searchTraditionalPreparedStatement.executeQuery() preparedStatement.executeQuery().use {
it.toListOfDictionaryEntries()
return resultSet.toListOfDictionaryEntries() }
} }
private fun searchPinyin(query: String): List<DictionaryEntry> { private fun searchPinyin(pinyin: String): List<DictionaryEntry> {
val sanitizedQuery = query.lowercase().replace(whitespaceRegex, "") val sanitizedPinyin = pinyin.lowercase().replace(whitespaceRegex, "")
searchPinyinPreparedStatement.setString(1, "$sanitizedQuery*") return connection.prepareStatement(searchPinyinSql).use { preparedStatement ->
searchPinyinPreparedStatement.setString(2, "$sanitizedQuery*") preparedStatement.setString(1, "$sanitizedPinyin*")
preparedStatement.setString(2, "$sanitizedPinyin*")
val resultSet: ResultSet = searchPinyinPreparedStatement.executeQuery() preparedStatement.executeQuery().use { resultSet ->
resultSet.toListOfDictionaryEntries()
return resultSet.toListOfDictionaryEntries() }
}
} }
private fun searchSegments(phrase: String): List<DictionaryEntry> { private fun searchSegmentsOf(phrase: String): List<DictionaryEntry> {
val segments = segmentBs.segment(phrase, SegmentResultHandlers.word()) val segments = segmentBs.segment(phrase, SegmentResultHandlers.word())
val segmentsListString = segments val segmentsListString = segments
.mapIndexed { index, s -> "($index, '$s')" } .mapIndexed { index, segment -> "($index, '$segment')" }
.joinToString(",") .joinToString(",")
val query = searchSegments.replace("?", segmentsListString) val query = searchSegmentsSql.replace("?", segmentsListString)
val resultSet: ResultSet = connection.createStatement().executeQuery(query) return connection.createStatement().use { statement ->
statement.executeQuery(query).use { resultSet ->
return resultSet.toListOfDictionaryEntries() resultSet.toListOfDictionaryEntries()
}
}
} }
override fun findWordsContaining(entry: DictionaryEntry): List<DictionaryEntry> { override suspend fun findWordsContaining(entry: DictionaryEntry) = withContext(dispatcher) {
findWordsContaining.setString(1, "_%${entry.traditional}%") connection.prepareStatement(findWordsContainingSql).use { preparedStatement ->
preparedStatement.setString(1, "_%${entry.traditional}%")
val resultSet: ResultSet = findWordsContaining.executeQuery() preparedStatement.executeQuery().use { resultSet ->
resultSet.toListOfDictionaryEntries()
return resultSet.toListOfDictionaryEntries() }
}
} }
override fun findWordsBeginning(entry: DictionaryEntry): List<DictionaryEntry> { override suspend fun findWordsBeginningWith(entry: DictionaryEntry) = withContext(dispatcher) {
findWordsBeginning.setString(1, "${entry.traditional}?*") connection.prepareStatement(findWordsBeginningSql).use { preparedStatement ->
preparedStatement.setString(1, "${entry.traditional}?*")
val resultSet: ResultSet = findWordsBeginning.executeQuery() preparedStatement.executeQuery().use { resultSet ->
resultSet.toListOfDictionaryEntries()
return resultSet.toListOfDictionaryEntries() }
}
} }
override fun findCharacters(entry: DictionaryEntry): List<DictionaryEntry> { override suspend fun findCharactersOf(entry: DictionaryEntry) = withContext(dispatcher) {
val pinyinSyllablesWithToneNumbers = entry.pinyinWithToneNumbers val pinyinSyllablesWithToneNumbers = entry.pinyinWithToneNumbers
.lowercase() .lowercase()
.split(" ") .split(" ")
@ -190,19 +182,23 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
.mapIndexed { index, s -> "($index, '${s.first}', '${s.second}')" } .mapIndexed { index, s -> "($index, '${s.first}', '${s.second}')" }
.joinToString(",") .joinToString(",")
val query = findCharacters.replace("?", queryInput) val query = findCharactersSql.replace("?", queryInput)
val resultSet: ResultSet = connection.createStatement().executeQuery(query) connection.createStatement().use { statement ->
statement.executeQuery(query).use { resultSet ->
return resultSet.toListOfDictionaryEntries() resultSet.toListOfDictionaryEntries()
}
}
} }
override fun findSentencesContaining(entry: DictionaryEntry): List<Sentence> { override suspend fun findExampleSentencesFor(entry: DictionaryEntry) = withContext(dispatcher) {
findSentences.setString(1, "_%${entry.traditional}%") connection.prepareStatement(findExampleSentenceSql).use { preparedStatement ->
preparedStatement.setString(1, "_%${entry.traditional}%")
val resultSet: ResultSet = findSentences.executeQuery() preparedStatement.executeQuery().use { resultSet ->
resultSet.toListOfSentences()
return resultSet.toListOfSentences() }
}
} }
} }
@ -219,10 +215,8 @@ private fun ResultSet.toDictionaryEntry() = DictionaryEntry(
) )
private fun ResultSet.toListOfDictionaryEntries() = buildList { private fun ResultSet.toListOfDictionaryEntries() = buildList {
this@toListOfDictionaryEntries.use { while (this@toListOfDictionaryEntries.next()) {
while (it.next()) { add(this@toListOfDictionaryEntries.toDictionaryEntry())
add(it.toDictionaryEntry())
}
} }
} }
@ -232,9 +226,7 @@ private fun ResultSet.toSentence() = Sentence(
) )
private fun ResultSet.toListOfSentences() = buildList { private fun ResultSet.toListOfSentences() = buildList {
this@toListOfSentences.use { while (this@toListOfSentences.next()) {
while (it.next()) { add(this@toListOfSentences.toSentence())
add(it.toSentence())
}
} }
} }

View File

@ -16,22 +16,18 @@ class SearchController(private val model: Model) {
@FXML @FXML
@Suppress("UnusedPrivateMember") @Suppress("UnusedPrivateMember")
private fun initialize() { private fun initialize() {
textFieldSearch.textProperty().addListener { _, _, newValue -> textFieldSearch.textProperty().addListener { _, _, _ -> search() }
if (newValue.isNullOrBlank()) { searchModeToggleGroup.selectedToggleProperty().addListener { _, _, _ -> search() }
return@addListener
} }
private fun search() {
val searchQuery = textFieldSearch.text
val searchMode = searchModeToggleGroup.selectedToggle.userData as SearchMode val searchMode = searchModeToggleGroup.selectedToggle.userData as SearchMode
model.search(newValue, searchMode)
if (searchQuery.isNullOrBlank()) {
return
} }
searchModeToggleGroup.selectedToggleProperty().addListener { _, _, newValue -> model.search(searchQuery, searchMode)
if (textFieldSearch.text.isNullOrBlank()) {
return@addListener
}
val searchMode = newValue.userData as SearchMode
model.search(textFieldSearch.text, searchMode)
}
} }
} }

View File

@ -1,20 +0,0 @@
package com.marvinelsen.willow.ui.services
import com.marvinelsen.willow.domain.Dictionary
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.toDomain
import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.util.task
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.concurrent.Service
class FindCharacterService(private val dictionary: Dictionary) : Service<ObservableList<DictionaryEntryFx>>() {
lateinit var entry: DictionaryEntryFx
override fun createTask() = task {
if (!this::entry.isInitialized) error("Entry is not initialized")
FXCollections.observableList(dictionary.findCharacters(entry.toDomain()).map { it.toFx() })
}
}

View File

@ -1,21 +0,0 @@
package com.marvinelsen.willow.ui.services
import com.marvinelsen.willow.domain.Dictionary
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.SentenceFx
import com.marvinelsen.willow.ui.toDomain
import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.util.task
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.concurrent.Service
class FindSentencesService(private val dictionary: Dictionary) : Service<ObservableList<SentenceFx>>() {
lateinit var entry: DictionaryEntryFx
override fun createTask() = task {
if (!this::entry.isInitialized) error("Entry is not initialized")
FXCollections.observableList(dictionary.findSentencesContaining(entry.toDomain()).map { it.toFx() })
}
}

View File

@ -1,20 +0,0 @@
package com.marvinelsen.willow.ui.services
import com.marvinelsen.willow.domain.Dictionary
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.toDomain
import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.util.task
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.concurrent.Service
class FindWordsBeginningService(private val dictionary: Dictionary) : Service<ObservableList<DictionaryEntryFx>>() {
lateinit var entry: DictionaryEntryFx
override fun createTask() = task {
if (!this::entry.isInitialized) error("Entry is not initialized")
FXCollections.observableList(dictionary.findWordsBeginning(entry.toDomain()).map { it.toFx() })
}
}

View File

@ -1,20 +0,0 @@
package com.marvinelsen.willow.ui.services
import com.marvinelsen.willow.domain.Dictionary
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.toDomain
import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.util.task
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.concurrent.Service
class FindWordsContainingService(private val dictionary: Dictionary) : Service<ObservableList<DictionaryEntryFx>>() {
lateinit var entry: DictionaryEntryFx
override fun createTask() = task {
if (!this::entry.isInitialized) error("Entry is not initialized")
FXCollections.observableList(dictionary.findWordsContaining(entry.toDomain()).map { it.toFx() })
}
}

View File

@ -1,22 +0,0 @@
package com.marvinelsen.willow.ui.services
import com.marvinelsen.willow.domain.Dictionary
import com.marvinelsen.willow.domain.SearchMode
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.util.task
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.concurrent.Service
class SearchService(private val dictionary: Dictionary) : Service<ObservableList<DictionaryEntryFx>>() {
lateinit var searchQuery: String
lateinit var searchMode: SearchMode
override fun createTask() = task {
if (!this::searchQuery.isInitialized) error("Search query is not initialized")
if (!this::searchMode.isInitialized) error("Search mode is not initialized")
FXCollections.observableList(dictionary.search(searchQuery, searchMode).map { it.toFx() })
}
}