From 07a7d5a0f0142d90c24249d48f9aa59bcb25d635 Mon Sep 17 00:00:00 2001
From: jez04 <david.jezek@post.cz>
Date: Thu, 24 Apr 2025 00:53:09 +0200
Subject: [PATCH] feat: :tada: solution lab 10

---
 scores.csv                                    |   5 +
 src/main/java/lab/MyEdit.java                 |  13 ++
 src/main/java/lab/data/Score.java             |  10 +-
 src/main/java/lab/gui/EditController.java     | 106 +++++++---
 src/main/resources/msg.properties             |   5 +
 src/main/resources/msg_cs.properties          |   5 +
 src/main/resources/msg_cs_CZ.properties       |   4 +
 src/main/resources/msg_en.properties          |   6 +
 .../structure/test/ClassStructureTest.java    | 182 +++---------------
 9 files changed, 149 insertions(+), 187 deletions(-)
 create mode 100644 src/main/java/lab/MyEdit.java
 create mode 100644 src/main/resources/msg.properties
 create mode 100644 src/main/resources/msg_cs.properties
 create mode 100644 src/main/resources/msg_cs_CZ.properties
 create mode 100644 src/main/resources/msg_en.properties

diff --git a/scores.csv b/scores.csv
index 1ed1b37..b6a2212 100644
--- a/scores.csv
+++ b/scores.csv
@@ -4,3 +4,8 @@ TikTokTornado;210;EASY
 ScriptSensei;158;EASY
 SnapMasteraaa;167000;null
 ScriptSenseidddd;1580000;null
+TikTokTornado;210;EASY
+ScriptSensei;158000;MEDIUM
+TikTokTornado;210000;EASY
+TikTokTornado;210;EASY
+ScriptSensei;158;EASY
diff --git a/src/main/java/lab/MyEdit.java b/src/main/java/lab/MyEdit.java
new file mode 100644
index 0000000..07b46a5
--- /dev/null
+++ b/src/main/java/lab/MyEdit.java
@@ -0,0 +1,13 @@
+package lab;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface MyEdit {
+    public boolean readOnly() default false;
+    public boolean visible() default true;
+}
diff --git a/src/main/java/lab/data/Score.java b/src/main/java/lab/data/Score.java
index c6219a5..137e6fb 100644
--- a/src/main/java/lab/data/Score.java
+++ b/src/main/java/lab/data/Score.java
@@ -8,7 +8,7 @@ import jakarta.persistence.Enumerated;
 import jakarta.persistence.GeneratedValue;
 import jakarta.persistence.GenerationType;
 import jakarta.persistence.Id;
-
+import lab.MyEdit;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.EqualsAndHashCode;
@@ -31,11 +31,19 @@ public class Score {
 	
 	@Id
 	@GeneratedValue(strategy = GenerationType.AUTO)
+	@MyEdit(visible = false)
 	private Long id;
+	@MyEdit(readOnly = true)
 	private String name;
+	@MyEdit
 	private int points;
 	@Enumerated(EnumType.STRING)
+	@MyEdit
 	private Level level;
+
+	public String getVirtual(){
+		return "aaaa";
+	}
 	
 	
 	public static Score generate() {
diff --git a/src/main/java/lab/gui/EditController.java b/src/main/java/lab/gui/EditController.java
index 44e7355..abb9080 100644
--- a/src/main/java/lab/gui/EditController.java
+++ b/src/main/java/lab/gui/EditController.java
@@ -4,42 +4,25 @@ import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
 import java.beans.PropertyDescriptor;
-import java.io.IOException;
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.ResourceBundle;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import javafx.collections.ListChangeListener;
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.geometry.HPos;
-import javafx.scene.Group;
-import javafx.scene.Scene;
 import javafx.scene.control.Button;
 import javafx.scene.control.Label;
-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 javafx.scene.input.MouseEvent;
 import javafx.scene.layout.GridPane;
-import javafx.scene.text.Text;
-import javafx.stage.Modality;
 import javafx.stage.Stage;
-import javafx.stage.StageStyle;
-import lab.Setup;
+import lab.MyEdit;
 import lab.data.Score;
-import lab.game.Difficult;
 import lombok.Setter;
 
 /**
@@ -75,9 +58,52 @@ public class EditController {
 
 	@FXML
 	void btnOkAction(ActionEvent event) {
-		//TODO: add code to read values from text fields
-		
-		mainScreenController.updateData((Score)data);
+		try {
+			BeanInfo beanInfo = Introspector.getBeanInfo(data.getClass());
+			for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
+				try {
+					String name = propertyDescriptor.getName();
+					Field field = data.getClass().getDeclaredField(name);
+					MyEdit myEdit = field.getAnnotation(MyEdit.class);
+					if(myEdit == null || !myEdit.visible() || myEdit.readOnly()) {
+						continue;
+					}
+					TextField textField = nameToTextField.get(name);
+					String stringValue = textField.getText();
+					Object value = null;
+					if (propertyDescriptor.getPropertyType() == String.class) {
+						value = stringValue;
+					} else if (stringValue == null || stringValue.isBlank()) {
+						value = null;
+					} else if (propertyDescriptor.getPropertyType() == Integer.class
+							|| propertyDescriptor.getPropertyType() == int.class) {
+						value = Integer.parseInt(stringValue);
+					} else if (propertyDescriptor.getPropertyType() == Long.class
+							|| propertyDescriptor.getPropertyType() == long.class) {
+						value = Long.parseLong(stringValue);
+					} else if (propertyDescriptor.getPropertyType() == Double.class
+							|| propertyDescriptor.getPropertyType() == double.class) {
+						value = Double.parseDouble(stringValue);
+					} else if (propertyDescriptor.getPropertyType().isEnum()) {
+						for (Object enumConstant : propertyDescriptor.getPropertyType().getEnumConstants()) {
+							if (enumConstant.toString().equalsIgnoreCase(stringValue)) {
+								value = enumConstant;
+								break;
+							}
+						}
+					}
+					if (propertyDescriptor.getWriteMethod() != null) {
+						propertyDescriptor.getWriteMethod().invoke(data, value);
+					}
+				} catch (NoSuchFieldException e) {
+					log.warn("property {} has no field", propertyDescriptor.getName());
+				}
+			}
+		} catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
+			log.error(e);
+		}
+
+		mainScreenController.updateData((Score) data);
 		stage.hide();
 	}
 
@@ -93,15 +119,41 @@ public class EditController {
 
 	public void setObjectToEdit(Object data) {
 		this.data = data;
-		log.info("data set {}", data);
+		log.info("data set {}", data);		
 		content.getChildren().clear();
-		
-		//TODO: code to detect properties and add rows to dialog  
+		ResourceBundle msg = ResourceBundle.getBundle("msg");
+		try {
+			BeanInfo beanInfo = Introspector.getBeanInfo(data.getClass());
+			int row = 0;
+			for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
+				try {
+					Field field = data.getClass().getDeclaredField(propertyDescriptor.getName());
+					MyEdit myEdit = field.getAnnotation(MyEdit.class);
+					if (myEdit == null) {
+						continue;
+					}
+					if (!myEdit.visible()) {
+						continue;
+					}
+					String name = propertyDescriptor.getName();
+					Object value = propertyDescriptor.getReadMethod().invoke(data);
+					String localizedName = msg.getString(String.format("%s.%s", data.getClass().getSimpleName(), field.getName()));
+					addDialogRow(row, name, localizedName, value != null ? value.toString() : "", !myEdit.readOnly());
+					row++;
+				} catch (NoSuchFieldException e) {
+					log.warn("property '{}' has no field in class '{}'", propertyDescriptor.getName(), data.getClass().getCanonicalName());
+				}
+			}
+		} catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
+			log.error(e);
+		}
 	}
 
-	private void addDialogRow(int rowNumber, String name, String descriptionName, String stringValue) {
+	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/resources/msg.properties b/src/main/resources/msg.properties
new file mode 100644
index 0000000..d0655f9
--- /dev/null
+++ b/src/main/resources/msg.properties
@@ -0,0 +1,5 @@
+
+Score.id     = id
+Score.level  = level
+Score.name   = name
+Score.points = points
diff --git a/src/main/resources/msg_cs.properties b/src/main/resources/msg_cs.properties
new file mode 100644
index 0000000..e397613
--- /dev/null
+++ b/src/main/resources/msg_cs.properties
@@ -0,0 +1,5 @@
+
+Score.id     = Jednozna\u010Dn\u00FD identifik\u00E1tor
+Score.level  = Obt\u00ED\u017Enost
+Score.name   = Jm\u00E9no
+Score.points = Body
diff --git a/src/main/resources/msg_cs_CZ.properties b/src/main/resources/msg_cs_CZ.properties
new file mode 100644
index 0000000..73f926b
--- /dev/null
+++ b/src/main/resources/msg_cs_CZ.properties
@@ -0,0 +1,4 @@
+#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
+
+Score.id     = Jedine\u010Dn\u00FD identifik\u00E1tor
+Score.name   = N\u00E1zev
diff --git a/src/main/resources/msg_en.properties b/src/main/resources/msg_en.properties
new file mode 100644
index 0000000..83da6ab
--- /dev/null
+++ b/src/main/resources/msg_en.properties
@@ -0,0 +1,6 @@
+#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
+
+Score.id     = Unique identificator
+Score.level  = Level
+Score.name   = Name
+Score.points = Points
diff --git a/src/test/java/jez04/structure/test/ClassStructureTest.java b/src/test/java/jez04/structure/test/ClassStructureTest.java
index 21c3023..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\\slab.data;"));
-	}
-
-	@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