From 2ccb4fc9d9848a8a7a1cce35c04008cc7477a353 Mon Sep 17 00:00:00 2001 From: jez04 <david.jezek@post.cz> Date: Wed, 30 Apr 2025 00:33:49 +0200 Subject: [PATCH] feat: lab 10 assignment --- pom.xml | 4 +- scores.csv | 6 + src/main/java/lab/data/Level.java | 12 +- src/main/java/lab/gui/App.java | 24 +++ src/main/java/lab/gui/EditController.java | 87 +++++++++ .../java/lab/gui/MainScreenController.java | 21 ++ src/main/java/lab/storage/FileStorage.java | 9 +- src/main/java/module-info.java | 1 + src/main/resources/lab/gui/application.css | 16 ++ src/main/resources/lab/gui/edit.fxml | 48 +++++ .../structure/test/ClassStructureTest.java | 182 +++--------------- 11 files changed, 244 insertions(+), 166 deletions(-) create mode 100644 scores.csv create mode 100644 src/main/java/lab/gui/EditController.java create mode 100644 src/main/resources/lab/gui/edit.fxml diff --git a/pom.xml b/pom.xml index 8ed4160..4c0db0f 100644 --- a/pom.xml +++ b/pom.xml @@ -3,9 +3,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cz.vsb.fei.java2</groupId> - <artifactId>java2-lab09-v2</artifactId> + <artifactId>java2-lab10-v2</artifactId> <version>0.0.1-SNAPHOST</version> - <name>java2-lab09-v2</name> + <name>java2-lab10-v2</name> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> diff --git a/scores.csv b/scores.csv new file mode 100644 index 0000000..e8a4626 --- /dev/null +++ b/scores.csv @@ -0,0 +1,6 @@ +JavaJester;274;HARD +DataDetective;296;HARD +AppArchitect;88;MEDIUM +DataDynamo;186;EASY +AppArchitect;88;MEDIUM +CodeConductor;273;MEDIUM diff --git a/src/main/java/lab/data/Level.java b/src/main/java/lab/data/Level.java index fc4e031..3fbaed9 100644 --- a/src/main/java/lab/data/Level.java +++ b/src/main/java/lab/data/Level.java @@ -1,5 +1,15 @@ package lab.data; public enum Level { - EASY, MEDIUM, HARD; + EASY, MEDIUM, HARD; + + public static Level from(String s) { + for (Level l : values()) { + if (l.toString().equals(s)) { + return l; + } + } + return null; + } + } diff --git a/src/main/java/lab/gui/App.java b/src/main/java/lab/gui/App.java index bb20828..5eab6f4 100644 --- a/src/main/java/lab/gui/App.java +++ b/src/main/java/lab/gui/App.java @@ -10,9 +10,12 @@ import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.ButtonType; +import javafx.stage.Modality; import javafx.stage.Stage; +import javafx.stage.StageStyle; import javafx.stage.WindowEvent; import lab.Config; +import lab.data.Score; import lab.storage.FileStorage; import lombok.extern.log4j.Log4j2; @@ -27,6 +30,7 @@ import lombok.extern.log4j.Log4j2; public class App extends Application { private GameController gameController; + private MainScreenController menuController; private boolean viewMode; @@ -71,6 +75,7 @@ public class App extends Application { // Construct a main window with a canvas. FXMLLoader menuLoader = new FXMLLoader(getClass().getResource("/lab/gui/mainScreen.fxml")); Parent root = menuLoader.load(); + menuController = menuLoader.getController(); MainScreenController menuController = menuLoader.getController(); menuController.setApp(this); Scene scene = new Scene(root); @@ -79,6 +84,25 @@ public class App extends Application { primaryStage.setScene(scene); } + public Stage createDialogStage(Score score) throws IOException { + Stage dialog = new Stage(); + dialog.initStyle(StageStyle.UTILITY); + FXMLLoader editLoader = new FXMLLoader(getClass().getResource("/lab/gui/edit.fxml")); + Parent root = editLoader.load(); + EditController editController = editLoader.getController(); + editController.setStage(dialog); + editController.setObjectToEdit(score); + editController.setMainScreenController(menuController); + Scene scene = new Scene(root); + URL cssUrl = getClass().getResource("application.css"); + scene.getStylesheets().add(cssUrl.toString()); + dialog.setScene(scene); + dialog.initModality(Modality.APPLICATION_MODAL); + dialog.initOwner(primaryStage); + return dialog; + + } + @Override public void stop() throws Exception { if(gameController != null) { diff --git a/src/main/java/lab/gui/EditController.java b/src/main/java/lab/gui/EditController.java new file mode 100644 index 0000000..701243e --- /dev/null +++ b/src/main/java/lab/gui/EditController.java @@ -0,0 +1,87 @@ +package lab.gui; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.geometry.HPos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; +import javafx.stage.Stage; +import lab.data.Score; +import lombok.Setter; + +/** + * + */ +public class EditController { + + private static Logger log = LogManager.getLogger(EditController.class); + + @FXML + private Button btnOk; + + @FXML + private Button btnCancel; + + @FXML + private Label txtTitle; + + @FXML + private GridPane content; + + private Object data; + + private App app; + + private Map<String, TextField> nameToTextField = new HashMap<>(); + + @Setter + private Stage stage; + + @Setter + private MainScreenController mainScreenController; + + @FXML + void btnOkAction(ActionEvent event) { + //TODO: add code to read values from text fields + + mainScreenController.updateData((Score)data); + stage.hide(); + } + + @FXML + void btnCancelAction(ActionEvent event) { + stage.hide(); + } + + @FXML + void initialize() { + log.info("Screen initialized."); + } + + public void setObjectToEdit(Object data) { + this.data = data; + log.info("data set {}", data); + content.getChildren().clear(); + + //TODO: code to detect properties and add rows to dialog + } + + private void addDialogRow(int rowNumber, String name, String descriptionName, String stringValue, + boolean editable) { + Label label = new Label(descriptionName); + TextField textField = new TextField(stringValue); + textField.setEditable(editable); + nameToTextField.put(name, textField); + content.addRow(rowNumber, label, textField); + GridPane.setHalignment(label, HPos.RIGHT); + } + +} diff --git a/src/main/java/lab/gui/MainScreenController.java b/src/main/java/lab/gui/MainScreenController.java index 6ccdf77..ea90e3d 100644 --- a/src/main/java/lab/gui/MainScreenController.java +++ b/src/main/java/lab/gui/MainScreenController.java @@ -13,11 +13,13 @@ import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.SelectionMode; import javafx.scene.control.TableColumn; +import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleGroup; import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.MouseButton; import lab.Config; import lab.data.Level; import lab.data.Score; @@ -110,6 +112,11 @@ public class MainScreenController { this.scores.getItems().addAll(scores); } + public void updateData(Score score) { + Config.getInstance().getScoreStorageInterface().save(score); + updateScoreTable(Config.getInstance().getScoreStorageInterface().getAll()); + } + @FXML void initialize() { assert difficult != null : "fx:id=\"difficult\" was not injected: check your FXML file 'mainScreen.fxml'."; @@ -123,6 +130,20 @@ public class MainScreenController { difficultColumn.setCellValueFactory(new PropertyValueFactory<>("level")); btnDelete.setDisable(true); + scores.setRowFactory(tableView -> { + TableRow<Score> row = new TableRow<>(); + row.setOnMouseClicked(event -> { + if (event.getClickCount() >= 2 && event.getButton() == MouseButton.PRIMARY && row.getItem() != null) { + try { + MainScreenController.this.app.createDialogStage(row.getItem()).show(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + return row; + }); + scores.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); scores.getSelectionModel().getSelectedItems() .addListener((ListChangeListener.Change<? extends Score> change) -> diff --git a/src/main/java/lab/storage/FileStorage.java b/src/main/java/lab/storage/FileStorage.java index 828ca06..77f1df6 100644 --- a/src/main/java/lab/storage/FileStorage.java +++ b/src/main/java/lab/storage/FileStorage.java @@ -10,6 +10,7 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Stream; +import lab.data.Level; import lab.data.Score; public class FileStorage implements ScoreStorageInterface { @@ -21,7 +22,7 @@ public class FileStorage implements ScoreStorageInterface { if (Files.exists(Paths.get(SCORE_FILE_NAME))) { try (Stream<String> lines = Files.lines(Paths.get(SCORE_FILE_NAME))) { List<Score> result = lines.map(line -> line.split(";")) - .map(parts -> new Score(null, parts[0], Integer.parseInt(parts[1]), null)).toList(); + .map(parts -> new Score(null, parts[0], Integer.parseInt(parts[1]), Level.from(parts[2]))).toList(); return new ArrayList<>(result); } catch (IOException e) { e.printStackTrace(); @@ -34,7 +35,7 @@ public class FileStorage implements ScoreStorageInterface { public List<Score> getFirstTen() { List<Score> all = getAll(); Collections.sort(all, Comparator.comparing(Score::getPoints).reversed()); - return all.subList(0, 10); + return all.subList(0, Math.min(all.size(), 10)); } @Override @@ -51,9 +52,9 @@ public class FileStorage implements ScoreStorageInterface { } private void storeAll(List<Score> all) { - List<String> lines = all.stream().map(s -> String.format("%s;%d", s.getName(), s.getPoints())).toList(); + List<String> lines = all.stream().map(s -> String.format("%s;%d;%s", s.getName(), s.getPoints(), s.getLevel())).toList(); try { - Files.write(Paths.get(SCORE_FILE_NAME), lines, StandardOpenOption.TRUNCATE_EXISTING); + Files.write(Paths.get(SCORE_FILE_NAME), lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index a98974e..7651c61 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -8,6 +8,7 @@ module cz.vsb.fei.java2.lab03_module { requires com.h2database; requires jakarta.persistence; requires org.hibernate.orm.core; + requires java.desktop; opens lab.gui to javafx.fxml; opens lab.data to javafx.base,org.hibernate.orm.core; diff --git a/src/main/resources/lab/gui/application.css b/src/main/resources/lab/gui/application.css index 1200dd3..0248ecd 100644 --- a/src/main/resources/lab/gui/application.css +++ b/src/main/resources/lab/gui/application.css @@ -24,6 +24,22 @@ Label { -fx-background-size: stretch; } +#content { + -fx-grid-lines-visible: true; +} + +#btnOk { + -fx-background-color: RGBA(0.0,255.0,0.0,0.5); +} + +#btnCancel { + -fx-background-color: RGBA(150.0,150.0,0.0,0.5); +} + +.actionButton:hover { + -fx-text-fill: white; +} + #playButton { -fx-background-color: RGBA(255.0,0.0,0.0,0.5); } diff --git a/src/main/resources/lab/gui/edit.fxml b/src/main/resources/lab/gui/edit.fxml new file mode 100644 index 0000000..b80cc5b --- /dev/null +++ b/src/main/resources/lab/gui/edit.fxml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.RowConstraints?> +<?import javafx.scene.text.Font?> + +<BorderPane fx:id="menuPanel" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="lab.gui.EditController"> + <bottom> + <HBox BorderPane.alignment="CENTER"> + <children> + <Button fx:id="btnCancel" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#btnCancelAction" styleClass="actionButton" text="Cancle" HBox.hgrow="ALWAYS"> + <font> + <Font name="System Bold" size="51.0" /> + </font> + </Button> + <Button fx:id="btnOk" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#btnOkAction" styleClass="actionButton" text="Ok" HBox.hgrow="ALWAYS"> + <font> + <Font name="System Bold" size="51.0" /> + </font> + </Button> + </children> + </HBox> + </bottom> + <center> + <GridPane fx:id="content" BorderPane.alignment="CENTER"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" /> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" /> + </columnConstraints> + <rowConstraints> + <RowConstraints minHeight="10.0" vgrow="NEVER" /> + </rowConstraints> + </GridPane> + </center> + <top> + <Label fx:id="txtTitle" text="Title" BorderPane.alignment="CENTER"> + <font> + <Font name="System Bold" size="48.0" /> + </font> + </Label> + </top> +</BorderPane> diff --git a/src/test/java/jez04/structure/test/ClassStructureTest.java b/src/test/java/jez04/structure/test/ClassStructureTest.java index 9fa7dff..2e2be8b 100644 --- a/src/test/java/jez04/structure/test/ClassStructureTest.java +++ b/src/test/java/jez04/structure/test/ClassStructureTest.java @@ -2,176 +2,40 @@ package jez04.structure.test; import static org.hamcrest.MatcherAssert.assertThat; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Objects; -import java.util.regex.Pattern; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; -import cz.vsb.fei.kelvin.unittest.ProjectContains; import cz.vsb.fei.kelvin.unittest.StructureHelper; import cz.vsb.fei.kelvin.unittest.TextFileContains; -import cz.vsb.fei.kelvin.unittest.XmlFileContains; -import lab.data.Level; -import lab.data.Score; -import lab.storage.JpaConnector; class ClassStructureTest { StructureHelper helper = StructureHelper.getInstance(ClassStructureTest.class); - @Test - void jakartaAndHibernateAsDependencyTest() throws URISyntaxException { - assertThat(TextFileContains.getProjectRoot(getClass()), new XmlFileContains("pom.xml", - "/project/dependencies/dependency/artifactId[text() = 'jakarta.persistence-api']")); - assertThat(TextFileContains.getProjectRoot(getClass()), new XmlFileContains("pom.xml", - "/project/dependencies/dependency/artifactId[text() = 'hibernate-core']")); + // @formatter:off + @ParameterizedTest + @CsvSource({ + "Score.java,@MyEdit,4", + "EditController.java,Introspector.getBeanInfo,1", + "EditController.java,BeanInfo,1", + "EditController.java,PropertyDescriptor,1", + "EditController.java,getDeclaredField,1", + "EditController.java,getAnnotation,1", + "EditController.java,\\.readOnly\\(\\),1", + "EditController.java,\\.visible\\(\\),1", + "MyEdit.java,@interface,1", + "MyEdit.java,@Retention,1", + "MyEdit.java,RetentionPolicy\\.RUNTIME,1", + "MyEdit.java,readOnly\\(\\),1", + "MyEdit.java,visible\\(\\),1", + "msg(_.*)?\\.properties,points,2", + }) + // @formatter:on + void anotaceTest(String file, String annotation, int count) throws URISyntaxException { + assertThat(TextFileContains.getProjectRoot(getClass()), new TextFileContains(file, annotation).count(count).useRegExpForName(true)); } - @Test - void jakartaInModulInfoTest() throws URISyntaxException { - assertThat(TextFileContains.getProjectRoot(getClass()), - new TextFileContains("module-info.java", "jakarta.persistence;")); - assertThat(TextFileContains.getProjectRoot(getClass()), - new TextFileContains("module-info.java", "opens\\s+lab.data;|opens\\s+lab.data\\s+to\\s+javafx.base,org.hibernate.orm.core;")); - } - - @Test - void pesistenceXmlTest() throws URISyntaxException { - ProjectContains projectContains = new ProjectContains("persistence.xml"); - assertThat(TextFileContains.getProjectRoot(getClass()), projectContains); - assertThat(projectContains.getFoundFiles(), Matchers.not(Matchers.hasSize(0))); - Path persistenceXml = projectContains.getFoundFiles().getFirst(); - assertThat(persistenceXml.toAbsolutePath().toString(), - Matchers.endsWith(Paths.get("resources", "META-INF", "persistence.xml").toString())); - } - - @Test - void useEnumeratedTest() throws URISyntaxException { - assertThat(TextFileContains.getProjectRoot(getClass()), - new TextFileContains("Score.java", "@Enumerated\\(\\s*EnumType.STRING\\s*\\)")); - } - - @TestMethodOrder(OrderAnnotation.class) - @Nested - class JpaConnectorTests { - - private Score template = new Score(null, "Tester", 100, Level.EASY); - private JpaConnector connector; - - @BeforeEach - void init() { - connector = new JpaConnector(); - } - - @AfterEach - void cleanUp() { - connector.stop(); - } - boolean same(Score s1, Score s2) { - return s1.getLevel() == s2.getLevel() && s1.getPoints() == s2.getPoints() - && Objects.equals(s1.getName(), s2.getName()); - } - - @Test - @Order(100) - void jpaScoreInsertTest() { - Score savedScore = connector.save(template); - assertThat(savedScore.getId(), Matchers.notNullValue()); - } - - @Test - @Order(200) - void jpaScoreReadTest() { - List<Score> savedScores = connector.getAll().stream().filter(s -> same(s, template)).toList(); - assertThat(savedScores, Matchers.not(Matchers.hasSize(0))); - } - - @Test - @Order(250) - void jpaReadSortedTest() { - for (int i = 0; i < 20; i++) { - connector.save(template.toBuilder().points(i).build()); - } - List<Score> result = connector.getFirstTen(); - assertThat(result, Matchers.hasSize(10)); - for (int i = 0; i < result.size()-1; i++) { - assertThat(result.get(i).getPoints(), Matchers.greaterThanOrEqualTo(result.get(i+1).getPoints())); - } - } - - @Test - @Order(300) - void jpaScoreModifyTest() { - List<Score> savedScores = connector.getAll().stream().filter(s -> same(s, template)).toList(); - List<Long> savedIds = savedScores.stream().map(Score::getId).toList(); - assertThat(savedScores, Matchers.not(Matchers.hasSize(0))); - for (Score score : savedScores) { - score.setLevel(Level.HARD); - connector.save(score); - } - List<Score> modifiedScores = connector.getAll().stream().filter(s -> savedIds.contains(s.getId())).toList(); - assertThat(modifiedScores, Matchers.not(Matchers.hasSize(0))); - List<Score> originalDataScores = connector.getAll().stream().filter(s -> same(s, template)).toList(); - assertThat(originalDataScores, Matchers.hasSize(0)); - } - - @Test - @Order(400) - void jpaScoreDeleteTest() { - template.setLevel(Level.HARD); - List<Score> savedScores = connector.getAll().stream().filter(s -> same(s, template)).toList(); - List<Long> savedIds = savedScores.stream().map(Score::getId).toList(); - assertThat(savedScores, Matchers.not(Matchers.hasSize(0))); - connector.delete(savedScores); - List<Score> modifiedScores = connector.getAll().stream().filter(s -> savedIds.contains(s.getId())).toList(); - assertThat(modifiedScores, Matchers.hasSize(0)); - List<Score> originalDataScores = connector.getAll().stream().filter(s -> same(s, template)).toList(); - assertThat(originalDataScores, Matchers.hasSize(0)); - } - - @Test - @Order(400) - void jpaMergeTest() { - connector.save(template); - connector.find(template.getId()); - Score copy = template.toBuilder().points(500).build(); - Score result = connector.save(copy); - assertThat(result, Matchers.not(Matchers.sameInstance(copy))); - } - - @Test - @Order(500) - void jpamodifyNoPersistNoMergeTest() throws URISyntaxException, IOException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException { - template.setName("aaa"); - connector.save(template); - connector.modifyNoPersistOrMerge(template.getId(), score -> score.setName("ok")); - Object em = connector.getEntityManager(); - em.getClass().getMethod("clear").invoke(em); - - assertThat(connector.find(template.getId()).getName(), Matchers.equalTo("ok")); - TextFileContains textFileContains = new TextFileContains("JpaConnector.java", - "void\\s+modifyNoPersistOrMerge[\\s\\S]*}").multiline(true); - assertThat(TextFileContains.getProjectRoot(ClassStructureTest.class), textFileContains); - Path score = textFileContains.getFoundFiles().getFirst(); - String src = Files.readString(score); - String method = Pattern.compile("void\\s+modifyNoPersistOrMerge[\\s\\S]*}").matcher(src).results().findFirst().get().group(); - assertThat(method, Matchers.not(Matchers.containsString("persist"))); - assertThat(method, Matchers.not(Matchers.containsString("merge"))); - } - } } -- GitLab