From edfa98ab4ed785e6e91700dd844142b86f6b07d6 Mon Sep 17 00:00:00 2001
From: jez04 <david.jezek@post.cz>
Date: Mon, 2 Dec 2024 00:51:29 +0100
Subject: [PATCH] feat: :tada: solution

---
 .gitignore                                    |   2 +
 pom.xml                                       |   7 +
 src/main/java/lab/DbConnector.java            |  57 +++++
 src/main/java/lab/GameController.java         |  61 ++++++
 src/main/java/lab/Score.java                  |  64 ++++++
 src/main/java/lab/Ufo.java                    |  18 +-
 src/main/java/module-info.java                |   1 +
 src/main/resources/lab/gameWindow.fxml        |  23 +-
 .../structure/test/ClassStructureTest.java    | 120 ++++-------
 .../jez04/structure/test/StructureHelper.java | 204 ++++++++++++++----
 10 files changed, 431 insertions(+), 126 deletions(-)
 create mode 100644 src/main/java/lab/DbConnector.java
 create mode 100644 src/main/java/lab/Score.java

diff --git a/.gitignore b/.gitignore
index db44c8a..e312b71 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
 .settings/
 .project
 .classpath
+*.mv.db
+*.trace.db
diff --git a/pom.xml b/pom.xml
index 359f0cc..1a82f23 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,6 +12,13 @@
 		<maven.compiler.target>21</maven.compiler.target>
 	</properties>
 	<dependencies>
+		<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
+		<dependency>
+			<groupId>com.h2database</groupId>
+			<artifactId>h2</artifactId>
+			<version>2.3.232</version>
+		</dependency>
+
 		<dependency>
 			<groupId>org.openjfx</groupId>
 			<artifactId>javafx-controls</artifactId>
diff --git a/src/main/java/lab/DbConnector.java b/src/main/java/lab/DbConnector.java
new file mode 100644
index 0000000..9aba4e9
--- /dev/null
+++ b/src/main/java/lab/DbConnector.java
@@ -0,0 +1,57 @@
+package lab;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DbConnector {
+
+	private static final String JDBC_CONECTIN_STRING = "jdbc:h2:file:./scoreDB";
+
+	public static List<Score> getAll() {
+		return queryScore("select * from scores;");
+	}
+
+	public static List<Score> getFirstTen() {
+		return queryScore("select * from scores order by points  desc limit 10;");
+	}
+
+	private static List<Score> queryScore(String query) {
+		List<Score> result = new ArrayList<>();
+		try (Connection con = DriverManager.getConnection(JDBC_CONECTIN_STRING);
+				Statement stm = con.createStatement();
+				ResultSet rs = stm.executeQuery(query);) {
+			while (rs.next()) {
+				result.add(new Score(rs.getString("nick"), rs.getInt("points")));
+			}
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+		return result;
+	}
+
+	public static void createTable() {
+		try (Connection con = DriverManager.getConnection(JDBC_CONECTIN_STRING);
+				Statement stm = con.createStatement();) {
+			stm.executeUpdate("CREATE TABLE if not exists scores (nick VARCHAR(50) NOT NULL, points INT NOT NULL);");
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public static void insertScore(Score score) {
+		try (Connection con = DriverManager.getConnection(JDBC_CONECTIN_STRING);
+				PreparedStatement stm = con.prepareStatement("INSERT INTO scores VALUES (?, ?)");) {
+			stm.setString(1, score.getName());
+			stm.setInt(2, score.getPoints());
+			stm.executeUpdate();
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+	}
+}
diff --git a/src/main/java/lab/GameController.java b/src/main/java/lab/GameController.java
index 3521171..593d694 100644
--- a/src/main/java/lab/GameController.java
+++ b/src/main/java/lab/GameController.java
@@ -1,14 +1,30 @@
 package lab;
 
+import java.util.List;
+import java.util.stream.Stream;
+
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.geometry.Point2D;
 import javafx.scene.canvas.Canvas;
+import javafx.scene.control.Button;
 import javafx.scene.control.Label;
 import javafx.scene.control.Slider;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.control.cell.PropertyValueFactory;
 
 public class GameController {
 
+    @FXML
+    private Button btnGenerateScore;
+
+    @FXML
+    private Button btnLoadAll;
+
+    @FXML
+    private Button btnLoadFirstTen;
+
 	@FXML
 	private Slider angle;
 
@@ -18,6 +34,15 @@ public class GameController {
 	@FXML
 	private Canvas canvas;
 
+	@FXML
+    private TableView<Score> scores;
+    @FXML
+    private TableColumn<Score, String> nickColumn;
+
+    @FXML
+    private TableColumn<Score, Integer> pointsColumn;
+
+
 	private DrawingThread timer;
 
     @FXML
@@ -42,6 +67,28 @@ public class GameController {
 				() -> System.out.println("au!!!!"));
 	}
 
+    @FXML
+    void btnGenerateScoreAction(ActionEvent event) {
+    	Score score = Score.generate();
+    	this.scores.getItems().add(score);
+    	DbConnector.insertScore(score);
+    }
+
+    @FXML
+    void btnLoadAllAction(ActionEvent event) {
+    	updateScoreTable(DbConnector.getAll());
+    }
+
+    @FXML
+    void btnLoadFirstTenAction(ActionEvent event) {
+    	updateScoreTable(DbConnector.getFirstTen());
+    }
+    
+    private void updateScoreTable(List<Score> scores) {
+    	this.scores.getItems().clear();
+    	this.scores.getItems().addAll(scores);
+    }
+
 	private void updateHits() {
 		hits.setText(String.format("Hit count: %03d", hitcount));
 	}
@@ -61,6 +108,20 @@ public class GameController {
 		angle.valueProperty().addListener(
 				(observable, oldValue, newValue) -> 
 					timer.getWorld().getCannon().setAngle(newValue.doubleValue()));
+		
+		nickColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
+		pointsColumn.setCellValueFactory(new PropertyValueFactory<>("points"));
+		
+		initDB();
+
+	}
+	
+	private void initDB() {
+		scores.getItems().addAll(Stream.generate(Score::generate).limit(10).toList());
+		DbConnector.createTable();
+		DbConnector.getAll();
+		
+		
 	}
 
 	public void stop() {
diff --git a/src/main/java/lab/Score.java b/src/main/java/lab/Score.java
new file mode 100644
index 0000000..70d137a
--- /dev/null
+++ b/src/main/java/lab/Score.java
@@ -0,0 +1,64 @@
+package lab;
+
+import java.util.Random;
+
+public class Score {
+
+	private static final Random RANDOM = new Random();
+	
+	private String name;
+	private int points;
+	
+	public Score(String name, int points) {
+		this.name = name;
+		this.points = points;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public int getPoints() {
+		return points;
+	}
+
+	public void setPoints(int points) {
+		this.points = points;
+	}
+
+	@Override
+	public String toString() {
+		return "Score [name=" + name + ", points=" + points + "]";
+	}
+	
+	public static Score generate() {
+		return new Score(getRandomNick(), RANDOM.nextInt(50, 300));
+	}
+	
+	public static final String[] nicks = { "CyberSurfer", "PixelPioneer", "SocialSavvy", "DigitalDynamo", "ByteBuddy", "InstaGuru",
+			"TikTokTornado", "SnapMaster", "TweetTrendsetter", "ChatChampion", "HashtagHero", "EmojiEnthusiast",
+			"StoryStylist", "SelfieStar", "FilterFanatic", "VlogVirtuoso", "Memelord", "InfluencerInsider",
+			"StreamSupreme", "GeekyGizmo", "CodeCommander", "JavaJuggernaut", "ByteNinja", "SyntaxSamurai",
+			"ClassyCoder", "ObjectOmnipotent", "LoopLegend", "VariableVirtuoso", "DebugDemon", "CompilerCrusader",
+			"PixelProdigy", "VirtualVoyager", "AlgorithmAce", "DataDynamo", "ExceptionExpert", "BugBuster",
+			"SyntaxSorcerer", "CodeCrusader", "JavaJester", "NerdyNavigator", "CryptoCaptain", "SocialButterfly",
+			"AppArchitect", "WebWizard", "FunctionFreak", "PixelArtist", "CyberPhantom", "HackHero", "CacheChampion",
+			"ScreenSage", "WebWeaver", "LogicLover", "BitBlazer", "NetworkNomad", "ProtocolPioneer", "BinaryBoss",
+			"StackSultan", "SocialScribe", "RenderRuler", "ScriptSorcerer", "HTMLHero", "PixelProwler", "FrameFreak",
+			"DataDreamer", "BotBuilder", "ByteBishop", "KeyboardKnight", "DesignDaredevil", "JavaJuggler",
+			"SyntaxStrategist", "TechTactician", "ProgramProdigy", "BinaryBard", "PixelPoet", "GigabyteGuru",
+			"TechTrekker", "NetworkNinja", "DataDetective", "MatrixMaster", "CodeConductor", "AppAlchemist",
+			"ServerSage", "ClusterChampion", "ScriptSensei", "KeyboardKicker", "CacheCrafter", "SocialSpark",
+			"BinaryBeast", "CodeConnoisseur", "BitBrain", "VirtualVanguard", "SystemSculptor", "RenderRogue",
+			"CryptoConqueror", "MachineMonarch", "PixelPal", "CompilerCaptain", "BitBuilder", "TechTitan",
+			"CloudConqueror", "EchoExplorer", "FunctionFanatic", "RobotRanger" };
+	
+	public static String getRandomNick() {
+		return nicks[RANDOM.nextInt(nicks.length)];
+	}
+
+}
diff --git a/src/main/java/lab/Ufo.java b/src/main/java/lab/Ufo.java
index c55bfd7..013939b 100644
--- a/src/main/java/lab/Ufo.java
+++ b/src/main/java/lab/Ufo.java
@@ -10,7 +10,7 @@ import javafx.scene.image.Image;
 public class Ufo extends WorldEntity implements Collisionable {
 
 	private static final Random RANDOM = new Random();
-	private Image image = new Image(this.getClass().getResourceAsStream("ufo-small.gif"));
+	private Image image;
 	private Point2D velocity;
 
 	public Ufo(World world) {
@@ -23,9 +23,17 @@ public class Ufo extends WorldEntity implements Collisionable {
 		this.velocity = velocity;
 	}
 
+	private Image getImage() {
+		if (image == null) {
+			image = new Image(Ufo.class.getResourceAsStream("ufo-small.gif"));
+		}
+		return image;
+
+	}
+
 	@Override
 	public void drawInternal(GraphicsContext gc) {
-		gc.drawImage(image, getPosition().getX(), getPosition().getY());
+		gc.drawImage(getImage(), getPosition().getX(), getPosition().getY());
 	}
 
 	public void changeDirection() {
@@ -36,14 +44,14 @@ public class Ufo extends WorldEntity implements Collisionable {
 	public void simulate(double deltaT) {
 		position = position.add(velocity.multiply(deltaT));
 		position = new Point2D(position.getX() % world.getWidth(), position.getY());
-		if(position.getX() < -image.getWidth()) {
+		if (position.getX() < -getImage().getWidth()) {
 			position = new Point2D(world.getWidth(), position.getY());
 		}
 	}
 
 	@Override
 	public Rectangle2D getBoundingBox() {
-		return new Rectangle2D(position.getX(), position.getY(), image.getWidth(), image.getHeight());
+		return new Rectangle2D(position.getX(), position.getY(), getImage().getWidth(), getImage().getHeight());
 	}
 
 	@Override
@@ -53,7 +61,7 @@ public class Ufo extends WorldEntity implements Collisionable {
 
 	@Override
 	public void hitBy(Collisionable another) {
-		if(another instanceof BulletAnimated || another instanceof Bullet) {
+		if (another instanceof BulletAnimated || another instanceof Bullet) {
 			world.remove(this);
 		}
 	}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 736971c..51b9b1c 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -2,6 +2,7 @@ module lab01 {
     requires transitive javafx.controls;
     requires javafx.fxml;
     requires javafx.base;
+    requires java.sql;
     opens lab to javafx.fxml;
     exports lab;
 }
\ No newline at end of file
diff --git a/src/main/resources/lab/gameWindow.fxml b/src/main/resources/lab/gameWindow.fxml
index c2b94f1..7fc092a 100644
--- a/src/main/resources/lab/gameWindow.fxml
+++ b/src/main/resources/lab/gameWindow.fxml
@@ -6,11 +6,14 @@
 <?import javafx.scene.control.Button?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.control.Slider?>
+<?import javafx.scene.control.TableColumn?>
+<?import javafx.scene.control.TableView?>
 <?import javafx.scene.layout.BorderPane?>
 <?import javafx.scene.layout.HBox?>
+<?import javafx.scene.layout.VBox?>
 <?import javafx.scene.text.Font?>
 
-<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="lab.GameController">
+<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="471.0" prefWidth="901.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="lab.GameController">
    <bottom>
       <HBox alignment="TOP_CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
          <children>
@@ -43,4 +46,22 @@
    <top>
       <Label fx:id="hits" text="Hit count:  0" textAlignment="CENTER" BorderPane.alignment="CENTER" />
    </top>
+   <right>
+      <VBox maxHeight="1.7976931348623157E308" maxWidth="-Infinity" BorderPane.alignment="CENTER">
+         <children>
+            <TableView fx:id="scores" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308">
+              <columns>
+                <TableColumn fx:id="nickColumn" prefWidth="75.0" text="Nick" />
+                <TableColumn fx:id="pointsColumn" prefWidth="75.0" text="Points" />
+              </columns>
+               <columnResizePolicy>
+                  <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
+               </columnResizePolicy>
+            </TableView>
+            <Button fx:id="btnGenerateScore" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#btnGenerateScoreAction" text="Generate new score" />
+            <Button fx:id="btnLoadAll" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#btnLoadAllAction" text="Load all from DB" />
+            <Button fx:id="btnLoadFirstTen" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#btnLoadFirstTenAction" text="Load First 10 from DB" />
+         </children>
+      </VBox>
+   </right>
 </BorderPane>
diff --git a/src/test/java/jez04/structure/test/ClassStructureTest.java b/src/test/java/jez04/structure/test/ClassStructureTest.java
index 865fd16..b54aa04 100644
--- a/src/test/java/jez04/structure/test/ClassStructureTest.java
+++ b/src/test/java/jez04/structure/test/ClassStructureTest.java
@@ -2,38 +2,16 @@ package jez04.structure.test;
 
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-import java.io.ByteArrayOutputStream;
-import java.io.File;
 import java.io.IOException;
-import java.io.PrintStream;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
 import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.FileVisitResult;
-import java.nio.file.FileVisitor;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
 
-import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
-import org.reflections.Configuration;
-import org.reflections.Reflections;
-import org.reflections.scanners.Scanners;
-import org.reflections.util.ConfigurationBuilder;
 
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
 
 class ClassStructureTest {
 
@@ -52,82 +30,72 @@ class ClassStructureTest {
 	}
 
 	@Test
-	void gameControllerActionMethodTest() {
+	void gameControllerButtonActionMethodTest() {
 		helper.classExist("GameController");
 		Class<?> c = helper.getClass("GameController");
-		helper.hasMethodRegexp(c, ".*", void.class, ActionEvent.class);
+		long count = helper.countMethodRegexp(c, ".*", void.class, ActionEvent.class);
+		assertTrue(count > 1, "Only " + count+ " method handling button click found. Expected more then 1");
 	}
 
 	@Test
-	void gameControllerLambdasTest() {
+	void gameControllerTableViewTest() {
 		helper.classExist("GameController");
 		Class<?> c = helper.getClass("GameController");
-		long lamdaCount = helper.countMethodRegexp(c, "lambda\\$.*");
-		long innerClasscount = helper.countClassesRegexp(".*GameController\\$.*");
-		assertTrue(lamdaCount + innerClasscount >= 2,
-				"At least 2 inner classes or lamdas required for GameController but only "
-						+ (lamdaCount + innerClasscount) + " found.");
+		helper.hasProperty(c, ".*", TableView.class, false);
 	}
-
+	
 	@Test
-	void hitListenerExistenceTest() {
-		helper.classExist("HitListener");
-	}
-
-	@Test
-	void hitListenerEventMethodTest() {
-		helper.classExist("HitListener");
-		Class<?> c = helper.getClass("HitListener");
-		helper.hasMethod(c, "ufoDestroyed");
+	void gameControllerTableColumnTest() {
+		helper.classExist("GameController");
+		Class<?> c = helper.getClass("GameController");
+		helper.hasProperty(c, ".*", TableColumn.class, false);
 	}
 
 	@Test
-	void sceneCollectionTest() {
-		helper.classExist("World");
-		Class<?> c = helper.getClass("World");
-		long collectionCount = Arrays.asList(c.getDeclaredFields()).stream()
-				.filter(f -> Collection.class.isAssignableFrom(f.getType())).count();
-		assertTrue(collectionCount >= 3, "lab.Scene require atleast 3 filed of type/subtype Collection, but only "
-				+ collectionCount + " found.");
+	void dbConnectorTest() {
+		helper.classExistRegexp(".*[Dd][Bb][cC]on.*");
 	}
 
 	@Test
-	void worldMethodAddTest() {
-		helper.classExist("World");
-		Class<?> c = helper.getClass("World");
-		helper.hasMethodRegexp(c, "add.*", void.class, helper.getClass("DrawableSimulable"));
-		;
+	void dbConnectorGetAllTest() {
+		helper.classExistRegexp(".*[Dd][Bb][cC]on.*");
+		Class<?> scoreClass = helper.getClassRegexp(".*[sS]core.*");
+		Class<?> c = helper.getClassRegexp(".*[Dd][Bb][cC]on.*");
+		helper.hasMethodRegexp(c, ".*[aA]ll.*", List.class);
+		helper.hasMethodRegexp(c, ".*[iI]nsert.*", void.class, scoreClass);
 	}
-
 	@Test
-	void worldMethodRemoveTest() {
-		helper.classExist("World");
-		Class<?> c = helper.getClass("World");
-		helper.hasMethodRegexp(c, "remove.*", void.class, helper.getClass("DrawableSimulable"));
-		;
+	void dbConnectorInsertTest() {
+		helper.classExistRegexp(".*[Dd][Bb][cC]on.*");
+		Class<?> scoreClass = helper.getClassRegexp(".*[sS]core.*");
+		Class<?> c = helper.getClassRegexp(".*[Dd][Bb][cC]on.*");
+		helper.hasMethodRegexp(c, ".*[iI]nsert.*", void.class, scoreClass);
 	}
-
 	@Test
-	void bulletAnimatedMethodAddTest() {
-		helper.classExist("BulletAnimated");
-		Class<?> c = helper.getClass("BulletAnimated");
-		Class<?> l = helper.getClass("HitListener");
-		helper.hasMethodRegexp(c, "add.*", List.of(void.class, boolean.class), l);
+	void dbConnectorContainsJdbcTest() throws URISyntaxException, IOException {
+		helper.classExistRegexp(".*[Dd][Bb][cC]on.*");
+		Class<?> c = helper.getClassRegexp(".*[Dd][Bb][cC]on.*");
+		String src = helper.getSourceCode(c);
+		assertTrue(src.contains("jdbc:"), "No usage of jdbc detect.");
 	}
 
 	@Test
-	void bulletAnimatedMethodRemoveTest() {
-		helper.classExist("Ufo");
-		Class<?> c = helper.getClass("BulletAnimated");
-		Class<?> l = helper.getClass("HitListener");
-		helper.hasMethodRegexp(c, "remove.*", List.of(void.class, boolean.class), l);
+	void dbConnectorContainsDriverManagerTest() throws URISyntaxException, IOException {
+		helper.classExistRegexp(".*[Dd][Bb][cC]on.*");
+		Class<?> c = helper.getClassRegexp(".*[Dd][Bb][cC]on.*");
+		String src = helper.getSourceCode(c);
+		assertTrue(src.contains("DriverManager"), "No usage of DriverManager detect.");
 	}
 
 	@Test
-	void bulletAnimatedMethodFireTest() {
-		helper.classExist("BulletAnimated");
-		Class<?> c = helper.getClass("BulletAnimated");
-		assertTrue(helper.countMethodRegexp(c, "fire.*") > 0, "Method fire.* in LochNess not found.");
+	void dbConnectorContainsSqlTest() throws URISyntaxException, IOException {
+		helper.classExistRegexp(".*[Dd][Bb][cC]on.*");
+		Class<?> c = helper.getClassRegexp(".*[Dd][Bb][cC]on.*");
+		String src = helper.getSourceCode(c).toLowerCase();
+		assertTrue(src.contains("create "), "No usage of SQL create.");
+		assertTrue(src.contains("select "), "No usage of SQL select.");
+		assertTrue(src.contains("insert "), "No usage of SQL table.");
+		assertTrue(src.contains(" from "), "No usage of SQL from.");
+		assertTrue(src.contains(" table"), "No usage of SQL table.");
 	}
-
 }
diff --git a/src/test/java/jez04/structure/test/StructureHelper.java b/src/test/java/jez04/structure/test/StructureHelper.java
index aa10c81..f3aed5d 100644
--- a/src/test/java/jez04/structure/test/StructureHelper.java
+++ b/src/test/java/jez04/structure/test/StructureHelper.java
@@ -1,14 +1,17 @@
 package jez04.structure.test;
 
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.nio.file.FileVisitResult;
@@ -19,10 +22,12 @@ import java.nio.file.Paths;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import org.junit.jupiter.api.Assertions;
@@ -43,11 +48,31 @@ class StructureHelper {
 		assertTrue(allClasses.stream().anyMatch(c -> c.endsWith(name)), "Class/Interface " + name + " not found");
 	}
 
+	public void classExistRegexp(String name) {
+		assertTrue(allClasses.stream().anyMatch(c -> c.matches(name)), "Class/Interface " + name + " not found");
+	}
+
+	public Class<?> getClassDirectly(String name) {
+		return loadClass(name, name);
+	}
+
+	public Class<?> getClassRegexp(String name) {
+		String className = allClasses.stream().filter(c -> c.matches(name)).findAny().orElse(null);
+		if (className == null) {
+			Assertions.fail("Class " + name + " not found.");
+		}
+		return loadClass(name, className);
+	}
+
 	public Class<?> getClass(String name) {
 		String className = allClasses.stream().filter(c -> c.endsWith(name)).findAny().orElse(null);
 		if (className == null) {
 			Assertions.fail("Class " + name + " not found.");
 		}
+		return loadClass(name, className);
+	}
+
+	private Class<?> loadClass(String name, String className) {
 		try {
 			return Class.forName(className);
 		} catch (ClassNotFoundException e) {
@@ -108,48 +133,87 @@ class StructureHelper {
 						+ Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", ")));
 	}
 
+	public Method getMethod(Class<?> interfaceDef, String methodName, Class<?> returnType, Class<?>... params) {
+		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
+		List<Method> foundMethods = methods.stream().filter(m -> m.getName().contains(methodName))
+				.filter(m -> m.getReturnType().equals(returnType))
+				.filter(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))).toList();
+		if (foundMethods.isEmpty()) {
+			fail("No method " + methodName + " found");
+		}
+		if (foundMethods.size() > 1) {
+			fail("More then one method " + methodName + " found");
+		}
+		return foundMethods.get(0);
+	}
+
 	public long countMethodRegexp(Class<?> interfaceDef, String methodNameRegexp) {
 		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
 		return methods.stream().filter(m -> m.getName().matches(methodNameRegexp)).count();
 	}
+
+	public long countMethodReference(Class<?> interfaceDef) throws URISyntaxException, IOException {
+		Pattern p = Pattern.compile("::");
+		Matcher m = p.matcher(getSourceCode(interfaceDef));
+		return m.results().count();
+	}
+
+	public long countMethodReferenceOn(Class<?> interfaceDef, String to) {
+		try {
+			Pattern p = Pattern.compile(to + "::");
+			Matcher m = p.matcher(getSourceCode(interfaceDef));
+			return m.results().count();
+		} catch (URISyntaxException | IOException e) {
+			e.printStackTrace();
+			return 0;
+		}
+	}
+
 	public long countClassesRegexp(String classNameRegexp) {
 		return getNameOfAllClasses().stream().filter(className -> className.matches(classNameRegexp)).count();
 	}
 
+	public void hasConstructor(Class<?> classDef, Class<?>... params) {
+		getConstructor(classDef, params);
+	}
+
+	public Constructor<?> getConstructor(Class<?> classDef, Class<?>... params) {
+		List<Constructor<?>> constructors = Arrays.asList(classDef.getConstructors());
+		List<Constructor<?>> foundConstructors = constructors.stream()
+				.filter(m -> m.getParameterCount() == params.length)
+				.filter(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))).toList();
+		if (foundConstructors.isEmpty()) {
+			fail("No constructor found with parameters: "
+					+ Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", ")));
+		}
+		if (foundConstructors.size() > 1) {
+			fail("More then one constructor found with parameters: "
+					+ Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", ")));
+		}
+		return foundConstructors.get(0);
+	}
+
 	public void hasMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, Class<?> returnType,
 			Class<?>... params) {
 		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
 		assertTrue(methods.stream().anyMatch(m -> m.getName().matches(methodNameRegexp)),
 				"No method " + methodNameRegexp);
 		assertTrue(
-				methods.stream().filter(
-						m -> 
-						m.getName().matches(methodNameRegexp))
-						.filter(
-								m -> 
-								m.getReturnType().equals(returnType))
-						.anyMatch(m ->
-						Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))),
+				methods.stream().filter(m -> m.getName().matches(methodNameRegexp))
+						.filter(m -> m.getReturnType().equals(returnType))
+						.anyMatch(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))),
 				"Method " + methodNameRegexp + " has no all parrams:"
 						+ Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", ")));
 	}
 
-	public void hasMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, Collection<Class<?>> returnTypeOnOf,
+	public long countMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, Class<?> returnType,
 			Class<?>... params) {
 		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
 		assertTrue(methods.stream().anyMatch(m -> m.getName().matches(methodNameRegexp)),
 				"No method " + methodNameRegexp);
-		assertTrue(
-				methods.stream().filter(
-						m -> 
-						m.getName().matches(methodNameRegexp))
-						.filter(
-								m -> 
-								returnTypeOnOf.contains(m.getReturnType()))
-						.anyMatch(m ->
-						Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))),
-				"Method " + methodNameRegexp + " has no all parrams:"
-						+ Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", ")));
+		return methods.stream().filter(m -> m.getName().matches(methodNameRegexp))
+				.filter(m -> m.getReturnType().equals(returnType))
+				.filter(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))).count();
 	}
 
 	public void hasMethod(Class<?> interfaceDef, boolean finalTag, boolean abstractTag, String methodName,
@@ -179,16 +243,34 @@ class StructureHelper {
 				"Class " + clazz.getName() + " not extends class " + parentName);
 	}
 
+	public void hasExtends(Class<?> clazz, Class<?> parent) {
+		assertTrue(clazz.getSuperclass().equals(parent),
+				"Class " + clazz.getName() + " not extends class " + parent.getCanonicalName());
+	}
+
 	public void hasMethod(Class<?> interfaceDef, String methodName) {
 		List<Method> methods = Arrays.asList(interfaceDef.getMethods());
 		assertTrue(methods.stream().anyMatch(m -> m.getName().contains(methodName)), "No method " + methodName);
 	}
 
+	public String getSourceCode(Class<?> clazz) throws URISyntaxException, IOException {
+		URL myClassUrl = StructureHelper.class.getResource(this.getClass().getSimpleName() + ".class");
+		Path classRoot = Paths.get(myClassUrl.toURI());
+		while (!"test-classes".equals(classRoot.getFileName().toString())
+				&& !"classes".equals(classRoot.getFileName().toString())) {
+			classRoot = classRoot.getParent();
+		}
+		Path srcRoot = classRoot.getParent().getParent().resolve(Paths.get("src", "main", "java"));
+		System.out.println("class root: " + classRoot);
+		Path srcPath = srcRoot.resolve(clazz.getCanonicalName().replace(".", File.separator) + ".java");
+		return Files.readString(srcPath);
+	}
+
 	public Set<String> getNameOfAllClasses() {
-		List<String> initClassesName = new ArrayList<>();
-		dynamicaliFoundSomeClass(initClassesName);
-		initClassesName.addAll(List.of("lab.Routines", "lab.App", "lab.DrawingThread"));
-		for (String className : initClassesName) {
+		Set<String> allClassesName = new TreeSet<>();
+		dynamicalyFoundSomeClass(allClassesName);
+//		allClassesName.addAll(List.of("cz.vsb.fei.lab.App", "lab.Routines", "lab.App", "lab.DrawingThread"));
+		for (String className : allClassesName) {
 			try {
 				Class.forName(className);
 				break;
@@ -196,7 +278,6 @@ class StructureHelper {
 				System.out.println(String.format("Class '%s' cannot be loaded: %s", className, e.getMessage()));
 			}
 		}
-		Set<String> allClasses = new HashSet<>();
 		for (Package p : Package.getPackages()) {
 			if (p.getName().startsWith("java.") || p.getName().startsWith("com.") || p.getName().startsWith("jdk.")
 					|| p.getName().startsWith("javafx.") || p.getName().startsWith("org.")
@@ -204,36 +285,39 @@ class StructureHelper {
 					|| p.getName().startsWith("javassist")) {
 				continue;
 			}
-			System.out.println(p.getName());
+//			System.out.println(p.getName());
 			Configuration conf = new ConfigurationBuilder().addScanners(Scanners.SubTypes.filterResultsBy(pc -> true))
 					.forPackages(p.getName());
 			Reflections reflections = new Reflections(conf);
-			allClasses.addAll(reflections.getAll(Scanners.SubTypes.filterResultsBy(c -> {
-				System.out.println(c);
+			allClassesName.addAll(reflections.getAll(Scanners.SubTypes.filterResultsBy(c -> {
+//				System.out.println(">>> " + c);
 				return true;
 			})));
 		}
-		for (String string : allClasses) {
-			System.out.println(string);
-		}
-		return allClasses;
+		return allClassesName;
 	}
 
-	public void dynamicaliFoundSomeClass(List<String> initClassesName) {
-		URL myClassUrl = StructureHelper.class.getResource("ClassStructureTest.class");
-		myClassUrl.getFile();
+	private static final List<String> dirsToSkip = List.of("jez04", "META-INF");
+	private static final List<String> filesToSkip = List.of("module-info.class");
+
+	public void dynamicalyFoundSomeClass(Set<String> allClassesName) {
+		URL myClassUrl = StructureHelper.class.getResource(this.getClass().getSimpleName() + ".class");
 		try {
-			Path classRoot = Paths.get(myClassUrl.toURI()).getParent().getParent().getParent().getParent();
+			Path classRoot = Paths.get(myClassUrl.toURI());
+			while (!"test-classes".equals(classRoot.getFileName().toString())
+					&& !"classes".equals(classRoot.getFileName().toString())) {
+				classRoot = classRoot.getParent();
+			}
 			if ("test-classes".equals(classRoot.getFileName().toString())) {
 				classRoot = classRoot.getParent().resolve("classes");
 			}
-			System.out.println("class root: " + classRoot);
+//			System.out.println("class root: " + classRoot);
 			final Path classRootFinal = classRoot;
 			Files.walkFileTree(classRoot, new FileVisitor<Path>() {
 
 				@Override
 				public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
-					if (List.of("jez04", "META-INF").contains(dir.getFileName().toString())) {
+					if (dirsToSkip.contains(dir.getFileName().toString())) {
 						return FileVisitResult.SKIP_SUBTREE;
 					}
 					return FileVisitResult.CONTINUE;
@@ -241,18 +325,20 @@ class StructureHelper {
 
 				@Override
 				public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-					System.out.println("VISIT: " + file);
-					if ("module-info.class".equals(file.getFileName().toString())) {
+					if (filesToSkip.contains(file.getFileName().toString())) {
 						return FileVisitResult.CONTINUE;
 					}
 					if (!file.getFileName().toString().endsWith(".class")) {
 						return FileVisitResult.CONTINUE;
 					}
+					if (file.getFileName().toString().contains("$")) {
+						return FileVisitResult.CONTINUE;
+					}
 					String foundClassName = classRootFinal.relativize(file).toString();
 					foundClassName = foundClassName.substring(0, foundClassName.length() - 6)
 							.replace(File.separatorChar, '.');
-					initClassesName.add(foundClassName);
-					return FileVisitResult.TERMINATE;
+					addClassAndAllRef(allClassesName, foundClassName);
+					return FileVisitResult.CONTINUE;
 				}
 
 				@Override
@@ -269,4 +355,34 @@ class StructureHelper {
 			e.printStackTrace();
 		}
 	}
+
+	private void addClassAndAllRef(Set<String> allClassesName, String foundClassName) {
+		allClassesName.add(foundClassName);
+		try {
+			Class<?> foundClass = Class.forName(foundClassName);
+			List.of(foundClass.getInterfaces()).stream().map(Class::getCanonicalName).forEach(allClassesName::add);
+			List.of(foundClass.getDeclaredClasses()).stream().map(Class::getCanonicalName).forEach(allClassesName::add);
+			List.of(foundClass.getDeclaredFields()).stream().map(Field::getType)
+					.map(clazz -> clazz.isArray() ? clazz.componentType() : clazz)
+					.filter(Predicate.not(Class::isPrimitive)).map(Class::getCanonicalName)
+					.forEach(allClassesName::add);
+			List.of(foundClass.getDeclaredMethods()).stream().map(Method::getReturnType)
+					.map(clazz -> clazz.isArray() ? clazz.componentType() : clazz)
+					.filter(Predicate.not(Class::isPrimitive)).map(Class::getCanonicalName)
+					.forEach(allClassesName::add);
+			List.of(foundClass.getDeclaredMethods()).stream().flatMap(m -> List.of(m.getParameters()).stream())
+					.map(Parameter::getType).map(clazz -> clazz.isArray() ? clazz.componentType() : clazz)
+					.filter(Predicate.not(Class::isPrimitive)).map(Class::getCanonicalName)
+					.forEach(allClassesName::add);
+			List.of(foundClass.getDeclaredMethods()).stream().flatMap(m -> List.of(m.getExceptionTypes()).stream())
+					.map(clazz -> clazz.isArray() ? clazz.componentType() : clazz)
+					.filter(Predicate.not(Class::isPrimitive)).map(Class::getCanonicalName)
+					.forEach(allClassesName::add);
+			if (foundClass.getSuperclass() != null) {
+				allClassesName.add(foundClass.getSuperclass().getCanonicalName());
+			}
+		} catch (ClassNotFoundException e) {
+			e.printStackTrace();
+		}
+	}
 }
-- 
GitLab