commit b3751367d1c3c1567768d720f20d2a5d91637960
parent 1a2fe81a958a5aec6451f83df071dbaed8e91941
Author: Wim Dupont <wim@wimdupont.com>
Date: Sat, 26 Aug 2023 14:39:26 +0200
updated
Diffstat:
11 files changed, 214 insertions(+), 176 deletions(-)
diff --git a/README.adoc b/README.adoc
@@ -0,0 +1,11 @@
+= WordStudentAdvanced
+
+Utilizes public https://couchdb.apache.org/[CouchDB] database.
+
+== Setup
+
+. Create a csv file with words to learn; separated by new lines and/or semicolons
+. Create application.properties file under src/main/resources with following properties:
+.. 'csv.dir': path of the csv file
+.. 'couchdb.connection-url': couchDb url (example: http://localhost:5984/wordtester)
+.. 'dictionary.client.connection-url': api url (https://api.dictionaryapi.dev/api/v2/entries/en/)
diff --git a/README.md b/README.md
@@ -1,8 +0,0 @@
-# WordStudentAdvanced
-
-## Setup
-
-1. Create application.properties file under src/main/resources
-2. Create a csv file
-3. Add the path of the csv file to application.properties with property name 'csv.dir'
-4. Add words to the csv file separated by semicolon (;)
diff --git a/src/main/java/com/wimdupont/WordStudentAdvancedApplication.java b/src/main/java/com/wimdupont/WordStudentAdvancedApplication.java
@@ -1,29 +1,21 @@
package com.wimdupont;
import com.wimdupont.client.DictionaryApi;
+import com.wimdupont.client.WordRepository;
import com.wimdupont.service.WordFetcher;
-import com.wimdupont.service.WordService;
import com.wimdupont.service.WordTester;
-public class WordStudentAdvancedApplication {
+import java.util.List;
- private static final WordService WORD_SERVICE = new WordService();
- private static final WordFetcher WORD_FETCHER = new WordFetcher();
- private static final DictionaryApi DICTIONARY_API = new DictionaryApi();
+public class WordStudentAdvancedApplication {
public static void main(String[] args) {
+ List<String> words = new WordFetcher().fetch();
+ System.out.printf("Words in csv file: %s%n", words.size());
- new WordTester(WORD_FETCHER, WORD_SERVICE).process();
-// var words = WORD_FETCHER.fetch();
-//
-// words.forEach(word -> {
-// var result = DICTIONARY_API.getDictionary(word);
-// result.ifPresent(WORD_SERVICE::save);
-// });
-// var result = new WordService().findByWord("acumen");
-//
-// System.out.println(result.get());
-// System.exit(0);
+ var wordTester = new WordTester(new WordRepository(), new DictionaryApi());
+ wordTester.saveNew(words);
+ wordTester.startTest(words);
}
}
diff --git a/src/main/java/com/wimdupont/client/Client.java b/src/main/java/com/wimdupont/client/Client.java
@@ -8,6 +8,7 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
@@ -16,6 +17,10 @@ public class Client {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final Map<String, String> POST_HEADER_MAP = Map.of("Content-type", "application/json");
+ public static ObjectMapper getObjectMapper(){
+ return OBJECT_MAPPER;
+ }
+
public static <T> Optional<T> get(String url, Class<T> responseClazz) {
try {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
@@ -38,7 +43,7 @@ public class Client {
POST_HEADER_MAP.forEach(connection::setRequestProperty);
connection.setDoOutput(true);
OutputStream outStream = connection.getOutputStream();
- OutputStreamWriter outStreamWriter = new OutputStreamWriter(outStream, "UTF-8");
+ OutputStreamWriter outStreamWriter = new OutputStreamWriter(outStream, StandardCharsets.UTF_8);
outStreamWriter.write(body);
outStreamWriter.flush();
outStreamWriter.close();
diff --git a/src/main/java/com/wimdupont/client/WordRepository.java b/src/main/java/com/wimdupont/client/WordRepository.java
@@ -0,0 +1,43 @@
+package com.wimdupont.client;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.wimdupont.config.ApplicationProperties;
+import com.wimdupont.model.db.WordCouchDocument;
+import com.wimdupont.model.db.WordSelectorResponse;
+import com.wimdupont.model.dto.WordDto;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+
+public class WordRepository {
+
+ private final ApplicationProperties applicationProperties = ApplicationProperties.getInstance();
+
+ public List<String> findAllWords() {
+ return Client.get(applicationProperties.getCouchdbUrl() + "/_all_docs?include_docs=true", WordCouchDocument.class)
+ .map(wordCouchDocument -> wordCouchDocument.rows().stream().map(row -> row.doc().word()).toList())
+ .orElseGet(ArrayList::new);
+ }
+
+ public Optional<WordDto> findByWord(String word) {
+ var url = applicationProperties.getCouchdbUrl() + "/_find";
+ var requestString = String.format("{ \"selector\": { \"word\": { \"$eq\": \"%s\" } } }", word);
+ return Client.post(url, WordSelectorResponse.class, requestString).flatMap(f -> f.docs().stream().findAny());
+ }
+
+ public void save(WordDto wordDto) {
+ if (findByWord(wordDto.word()).isEmpty()) {
+ var url = applicationProperties.getCouchdbUrl();
+ try {
+ Client.post(url, WordSelectorResponse.class, new ObjectMapper().writeValueAsString(wordDto));
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ System.out.printf("Word %s already saved", wordDto.word());
+ }
+ }
+}
diff --git a/src/main/java/com/wimdupont/model/dto/DefinitionDto.java b/src/main/java/com/wimdupont/model/dto/DefinitionDto.java
@@ -1,14 +0,0 @@
-package com.wimdupont.model.dto;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-import java.util.List;
-
-@JsonIgnoreProperties(ignoreUnknown = true)
-public record DefinitionDto(
- String definition,
- String example,
- List<String> synonyms,
- List<String> antonyms
-) {
-}
diff --git a/src/main/java/com/wimdupont/model/dto/DictionaryDto.java b/src/main/java/com/wimdupont/model/dto/DictionaryDto.java
@@ -8,9 +8,6 @@ import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public record DictionaryDto(
String word,
- String phonetic,
- List<PhoneticDto> phonetics,
- String origin,
- List<MeaningDto> meanings
+ List<Object> meanings
) {
}
diff --git a/src/main/java/com/wimdupont/model/dto/MeaningDto.java b/src/main/java/com/wimdupont/model/dto/MeaningDto.java
@@ -1,13 +0,0 @@
-package com.wimdupont.model.dto;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-import java.util.List;
-
-@JsonIgnoreProperties(ignoreUnknown = true)
-public record MeaningDto(
- String partOfSpeech,
- List<DefinitionDto> definitions
-) {
-
-}
diff --git a/src/main/java/com/wimdupont/model/dto/PhoneticDto.java b/src/main/java/com/wimdupont/model/dto/PhoneticDto.java
@@ -1,11 +0,0 @@
-package com.wimdupont.model.dto;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-@JsonIgnoreProperties(ignoreUnknown = true)
-public record PhoneticDto(
- String text,
- String audio
-) {
-}
-
diff --git a/src/main/java/com/wimdupont/service/WordService.java b/src/main/java/com/wimdupont/service/WordService.java
@@ -1,44 +0,0 @@
-package com.wimdupont.service;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.wimdupont.client.Client;
-import com.wimdupont.config.ApplicationProperties;
-import com.wimdupont.model.db.WordCouchDocument;
-import com.wimdupont.model.db.WordSelectorResponse;
-import com.wimdupont.model.dto.WordDto;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-
-public class WordService {
-
- private final ApplicationProperties applicationProperties = ApplicationProperties.getInstance();
-
- public List<String> findAllWords() {
- return Client.get(applicationProperties.getCouchdbUrl() + "/_all_docs?include_docs=true", WordCouchDocument.class)
- .map(wordCouchDocument -> wordCouchDocument.rows().stream().map(row -> row.doc().word()).toList())
- .orElseGet(ArrayList::new);
- }
-
- public Optional<WordDto> findByWord(String word) {
- var url = applicationProperties.getCouchdbUrl() + "/_find";
- var requestString = String.format("{ \"selector\": { \"word\": { \"$eq\": \"%s\" } } }", word);
- return Client.post(url, WordSelectorResponse.class, requestString).flatMap(f -> f.docs().stream().findAny());
- }
-
- public void save(WordDto wordDto) {
- if (findByWord(wordDto.word()).isEmpty()) {
- var url = applicationProperties.getCouchdbUrl();
- try {
- Client.post(url, WordSelectorResponse.class, new ObjectMapper().writeValueAsString(wordDto));
- } catch (JsonProcessingException e) {
- throw new RuntimeException(e);
- }
- } else {
- System.out.printf("Word %s already saved", wordDto.word());
- }
- }
-}
diff --git a/src/main/java/com/wimdupont/service/WordTester.java b/src/main/java/com/wimdupont/service/WordTester.java
@@ -1,110 +1,190 @@
package com.wimdupont.service;
-import com.wimdupont.model.dto.DictionaryDto;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.wimdupont.client.Client;
+import com.wimdupont.client.DictionaryApi;
+import com.wimdupont.client.WordRepository;
import com.wimdupont.model.dto.WordDto;
+import javax.swing.AbstractAction;
+import javax.swing.ActionMap;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.InputMap;
import javax.swing.JButton;
+import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
+import javax.swing.JScrollPane;
import javax.swing.JTextArea;
+import javax.swing.KeyStroke;
import java.awt.BorderLayout;
-import java.awt.GridBagLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class WordTester {
- /*
- - navigate with hjkl, arrows, spacebar,...
- - close frame
- - add database options: remove word, fully clean database,...
- -
- -
- */
-
-
- private final WordFetcher wordFetcher;
- private final WordService wordService;
-
- public WordTester(WordFetcher wordFetcher,
- WordService wordService) {
- this.wordFetcher = wordFetcher;
- this.wordService = wordService;
- }
- public void process() {
- List<String> words = wordFetcher.fetch();
- System.out.printf("Words in csv file: %s%n", words.size());
+ private final WordRepository wordRepository;
+ private final DictionaryApi dictionaryApi;
+ private final InputMap inputMap;
+ private static final String PREVIOUS = "Previous";
+ private static final String SHOW = "Show";
+ private static final String NEXT = "Next";
+
+ public WordTester(WordRepository wordRepository,
+ DictionaryApi dictionaryApi) {
+ this.wordRepository = wordRepository;
+ this.dictionaryApi = dictionaryApi;
+ this.inputMap = initiateInputMap();
+ }
+ public void startTest(List<String> words) {
Collections.shuffle(words);
- panelItUp(words);
- System.out.println("========================");
- System.out.println("All words have been tested.");
+ showUI(words);
}
- private JTextArea createShowPanel() {
- var dictionaryPanel = new JTextArea();
- dictionaryPanel.setLineWrap(true);
- dictionaryPanel.setEditable(false);
- return dictionaryPanel;
+ public void saveNew(List<String> words) {
+ List<String> savedWordList = wordRepository.findAllWords();
+ System.out.printf("Words in database: %s%n", savedWordList.size());
+ AtomicInteger count = new AtomicInteger(0);
+ words.stream()
+ .filter(csvWord -> !savedWordList.contains(csvWord))
+ .forEach(newWord -> {
+ var response = dictionaryApi.getDictionary(newWord);
+ if (response.isEmpty()) {
+ System.out.printf("No results found for '%s', no data saved%n", newWord);
+ count.get();
+ } else {
+ wordRepository.save(response.get());
+ System.out.printf("Saved new word %s%n", response.get());
+ count.getAndIncrement();
+ }
+ });
+
+ if (count.get() > 0)
+ System.out.printf("Saved %s words%n", count.get());
+ }
+
+ private InputMap initiateInputMap() {
+ var inputMap = new InputMap();
+ inputMap.put(KeyStroke.getKeyStroke("LEFT"), PREVIOUS);
+ inputMap.put(KeyStroke.getKeyStroke("H"), PREVIOUS);
+ inputMap.put(KeyStroke.getKeyStroke("L"), NEXT);
+ inputMap.put(KeyStroke.getKeyStroke("RIGHT"), NEXT);
+ inputMap.put(KeyStroke.getKeyStroke("DOWN"), SHOW);
+ inputMap.put(KeyStroke.getKeyStroke("SPACE"), SHOW);
+ inputMap.put(KeyStroke.getKeyStroke("J"), SHOW);
+ return inputMap;
}
- private void panelItUp(List<String> words) {
+ private void showUI(List<String> words) {
AtomicInteger index = new AtomicInteger(0);
JFrame jFrame = new JFrame();
jFrame.setLayout(new BorderLayout());
- final JPanel jPanel = new JPanel();
- jPanel.add(new JLabel("A Panel"));
- jFrame.add(jPanel, BorderLayout.CENTER);
- var wordPanel = new JPanel(new GridBagLayout());
+ var wordPanel = new JPanel();
+ wordPanel.setFocusable(false);
+ wordPanel.setLayout(new BoxLayout(wordPanel, BoxLayout.Y_AXIS));
var wordField = new JLabel(words.get(index.get()));
+ wordPanel.add(Box.createRigidArea(new Dimension(20, 20)));
wordPanel.add(wordField);
- var showPanel = createShowPanel();
+ wordField.setFont(new Font(Font.SERIF, Font.BOLD, 30));
+ wordPanel.add(Box.createRigidArea(new Dimension(20, 20)));
+ var showPanel = createShowPanel(index, wordField, words);
+ wordPanel.add(new JScrollPane(showPanel));
+ jFrame.add(wordPanel, BorderLayout.CENTER);
+ jFrame.add(createButtonPanel(showPanel), BorderLayout.SOUTH);
+ jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ jFrame.pack();
+ jFrame.setVisible(true);
+ }
+
+ private JTextArea createShowPanel(AtomicInteger index, JLabel wordField, List<String> words) {
+ var dictionaryPanel = new JTextArea();
+ dictionaryPanel.setLineWrap(true);
+ dictionaryPanel.setEditable(false);
+ dictionaryPanel.setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap);
+ dictionaryPanel.setBackground(new Color(0, 0, 51));
+ dictionaryPanel.setForeground(new Color(150, 250, 250));
+ dictionaryPanel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 15));
+ var actionMap = new ActionMap();
+ actionMap.put(PREVIOUS, toActionListener(previous(index, wordField, words, dictionaryPanel)));
+ actionMap.put(SHOW, toActionListener(show(index, words, dictionaryPanel)));
+ actionMap.put(NEXT, toActionListener(next(index, wordField, words, dictionaryPanel)));
+ dictionaryPanel.setActionMap(actionMap);
+ return dictionaryPanel;
+ }
+
+ private JPanel createButtonPanel(JTextArea showPanel) {
+ JButton previousButton = new JButton(PREVIOUS);
+ previousButton.addActionListener(showPanel.getActionMap().get(PREVIOUS));
+ previousButton.setFocusable(false);
+ JButton showButton = new JButton(SHOW);
+ showButton.setFocusable(false);
+ showButton.addActionListener(showPanel.getActionMap().get(SHOW));
+ JButton nextButton = new JButton(NEXT);
+ nextButton.setFocusable(false);
+ nextButton.addActionListener(showPanel.getActionMap().get(NEXT));
+
+ var btnPanel = new JPanel();
+ btnPanel.add(previousButton);
+ btnPanel.add(showButton);
+ btnPanel.add(nextButton);
- wordPanel.add(showPanel);
+ return btnPanel;
+ }
- JButton previousButton = new JButton("Previous");
- previousButton.addActionListener(e -> {
+ private Runnable previous(AtomicInteger index, JLabel wordField, List<String> words, JTextArea showPanel) {
+ return () -> {
if (index.get() > 0) {
wordField.setText(words.get(index.decrementAndGet()));
+ showPanel.setText(null);
}
- });
- JButton showButton = new JButton("Show");
- showButton.addActionListener(e -> {
- var word = wordService.findByWord(words.get(index.get()));
- word.ifPresent(wordDto -> showPanel.setText(toMeaning(wordDto)));
- //TODO else grab and save
+ };
+ }
- });
+ private Runnable show(AtomicInteger index, List<String> words, JTextArea showPanel) {
+ return () -> {
+ var word = wordRepository.findByWord(words.get(index.get()));
+ word.ifPresent(wordDto -> showPanel.setText(toMeaning(wordDto)));
+ showPanel.setVisible(true);
+ };
+ }
- JButton nextButton = new JButton("Next");
- nextButton.addActionListener(e -> {
+ private Runnable next(AtomicInteger index, JLabel wordField, List<String> words, JTextArea showPanel) {
+ return () -> {
if (index.get() < words.size()) {
wordField.setText(words.get(index.incrementAndGet()));
+ showPanel.setText(null);
}
- });
- var btnPanel = new JPanel();
- btnPanel.add(previousButton);
- btnPanel.add(showButton);
- btnPanel.add(nextButton);
+ };
+ }
+
+ private AbstractAction toActionListener(Runnable runnable) {
+ return new AbstractAction() {
+
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ runnable.run();
+ }
+ };
- jFrame.add(wordPanel, BorderLayout.CENTER);
- jFrame.add(btnPanel, BorderLayout.SOUTH);
- jFrame.pack();
- jFrame.setVisible(true);
}
private String toMeaning(WordDto wordDto) {
- return """
- Word: %s
- Meaning: %s
- """
- .formatted(wordDto.word() + System.lineSeparator(),
- wordDto.dictionaryResults().stream()
- .map(DictionaryDto::meanings)
- .toList());
+ try {
+ return Client.getObjectMapper()
+ .writerWithDefaultPrettyPrinter()
+ .writeValueAsString(wordDto.dictionaryResults());
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
}
}