From 7fbe09b7b6363e1a99568bcd80f65c125834ca3f Mon Sep 17 00:00:00 2001
From: jez04 <david.jezek@post.cz>
Date: Wed, 26 Feb 2025 00:37:53 +0100
Subject: [PATCH] feat: init for  java2 lab 02

---
 pom.xml                                       |   5 +-
 src/main/java/lab/App.java                    |   3 +-
 src/main/java/lab/DbConnector.java            |  10 +-
 src/main/java/lab/MainScreenController.java   |  10 +-
 src/main/java/lab/ScoreStorageFactory.java    |  13 ++
 src/main/resources/lab/mainScreen.fxml        |  80 +++----
 .../jez04/structure/test/AllOfContinue.java   |  39 ++++
 .../java/jez04/structure/test/ClassExist.java |  44 ++++
 .../structure/test/ClassStructureTest.java    | 130 +----------
 .../structure/test/ContainsInnerClasses.java  |  70 ++++++
 .../java/jez04/structure/test/HasMethod.java  |  95 ++++++++
 .../jez04/structure/test/HasProperty.java     |  91 ++++++++
 .../jez04/structure/test/IsDescendatOf.java   |  24 ++
 .../jez04/structure/test/IsInterface.java     |  23 ++
 .../structure/test/ResourceContains.java      | 102 ++++++++
 .../jez04/structure/test/SrcContains.java     |  54 +++++
 .../jez04/structure/test/StructureHelper.java | 220 ++++++++++++++----
 .../structure/test/StructureMatcher.java      |   7 +
 18 files changed, 799 insertions(+), 221 deletions(-)
 create mode 100644 src/main/java/lab/ScoreStorageFactory.java
 create mode 100644 src/test/java/jez04/structure/test/AllOfContinue.java
 create mode 100644 src/test/java/jez04/structure/test/ClassExist.java
 create mode 100644 src/test/java/jez04/structure/test/ContainsInnerClasses.java
 create mode 100644 src/test/java/jez04/structure/test/HasMethod.java
 create mode 100644 src/test/java/jez04/structure/test/HasProperty.java
 create mode 100644 src/test/java/jez04/structure/test/IsDescendatOf.java
 create mode 100644 src/test/java/jez04/structure/test/IsInterface.java
 create mode 100644 src/test/java/jez04/structure/test/ResourceContains.java
 create mode 100644 src/test/java/jez04/structure/test/SrcContains.java
 create mode 100644 src/test/java/jez04/structure/test/StructureMatcher.java

diff --git a/pom.xml b/pom.xml
index 13904e2..1781554 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,9 +2,10 @@
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	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>vsb-cs-java1</groupId>
-	<artifactId>lab10v2</artifactId>
+	<groupId>cz.vsb.fei.java2</groupId>
+	<artifactId>java2-lab02-v2</artifactId>
 	<version>0.0.1-SNAPHOST</version>
+    <name>java2-lab02-v2</name>
 	<packaging>jar</packaging>
 	<properties>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/src/main/java/lab/App.java b/src/main/java/lab/App.java
index 9a2c2db..d9b2609 100644
--- a/src/main/java/lab/App.java
+++ b/src/main/java/lab/App.java
@@ -31,8 +31,7 @@ public class App extends Application {
 		try {
 			this.primaryStage = primaryStage;
 			switchToMenu();
-			primaryStage.resizableProperty().set(false);
-			primaryStage.setTitle("Java 1 - 1th laboratory");
+			primaryStage.setTitle("Java 2 - 2th laboratory");
 			primaryStage.show();
 			// Exit program when main window is closed
 			primaryStage.setOnCloseRequest(this::exitProgram);
diff --git a/src/main/java/lab/DbConnector.java b/src/main/java/lab/DbConnector.java
index 9aba4e9..fda38fd 100644
--- a/src/main/java/lab/DbConnector.java
+++ b/src/main/java/lab/DbConnector.java
@@ -13,15 +13,15 @@ public class DbConnector {
 
 	private static final String JDBC_CONECTIN_STRING = "jdbc:h2:file:./scoreDB";
 
-	public static List<Score> getAll() {
+	public List<Score> getAll() {
 		return queryScore("select * from scores;");
 	}
 
-	public static List<Score> getFirstTen() {
+	public List<Score> getFirstTen() {
 		return queryScore("select * from scores order by points  desc limit 10;");
 	}
 
-	private static List<Score> queryScore(String query) {
+	private List<Score> queryScore(String query) {
 		List<Score> result = new ArrayList<>();
 		try (Connection con = DriverManager.getConnection(JDBC_CONECTIN_STRING);
 				Statement stm = con.createStatement();
@@ -35,7 +35,7 @@ public class DbConnector {
 		return result;
 	}
 
-	public static void createTable() {
+	public 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);");
@@ -44,7 +44,7 @@ public class DbConnector {
 		}
 	}
 
-	public static void insertScore(Score score) {
+	public 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());
diff --git a/src/main/java/lab/MainScreenController.java b/src/main/java/lab/MainScreenController.java
index 3e6c812..43f8f94 100644
--- a/src/main/java/lab/MainScreenController.java
+++ b/src/main/java/lab/MainScreenController.java
@@ -67,17 +67,17 @@ public class MainScreenController {
     void btnGenerateScoreAction(ActionEvent event) {
     	Score score = Score.generate();
     	this.scores.getItems().add(score);
-    	DbConnector.insertScore(score);
+    	ScoreStorageFactory.getInstance().insertScore(score);
     }
 
     @FXML
     void btnLoadAllAction(ActionEvent event) {
-    	updateScoreTable(DbConnector.getAll());
+    	updateScoreTable(ScoreStorageFactory.getInstance().getAll());
     }
 
     @FXML
     void btnLoadFirstTenAction(ActionEvent event) {
-    	updateScoreTable(DbConnector.getFirstTen());
+    	updateScoreTable(ScoreStorageFactory.getInstance().getFirstTen());
     }
     
     private void updateScoreTable(List<Score> scores) {
@@ -101,8 +101,8 @@ public class MainScreenController {
 	
 	private void initDB() {
 		//Stream.generate(Score::generate).limit(10).toList();
-		DbConnector.createTable();
-		scores.getItems().addAll(DbConnector.getAll());
+		ScoreStorageFactory.getInstance().createTable();
+		scores.getItems().addAll(ScoreStorageFactory.getInstance().getAll());
 	}
 
 }
diff --git a/src/main/java/lab/ScoreStorageFactory.java b/src/main/java/lab/ScoreStorageFactory.java
new file mode 100644
index 0000000..3346de6
--- /dev/null
+++ b/src/main/java/lab/ScoreStorageFactory.java
@@ -0,0 +1,13 @@
+package lab;
+
+public class ScoreStorageFactory {
+
+	private static DbConnector instance;
+	
+	public static DbConnector getInstance() {
+		if(instance	== null) {
+			instance = new DbConnector();
+		}
+		return instance;
+	}
+}
diff --git a/src/main/resources/lab/mainScreen.fxml b/src/main/resources/lab/mainScreen.fxml
index 8a2cec8..3229f44 100644
--- a/src/main/resources/lab/mainScreen.fxml
+++ b/src/main/resources/lab/mainScreen.fxml
@@ -13,7 +13,7 @@
 <?import javafx.scene.layout.VBox?>
 <?import javafx.scene.text.Font?>
 
-<BorderPane fx:id="menuPanel" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="456.0" prefWidth="711.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="lab.MainScreenController">
+<BorderPane fx:id="menuPanel" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="547.0" prefWidth="804.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="lab.MainScreenController">
    <top>
       <HBox prefWidth="200.0" BorderPane.alignment="CENTER">
          <children>
@@ -29,45 +29,47 @@
          </font></Button>
    </bottom>
    <center>
-      <HBox prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
+      <VBox prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
          <children>
-            <RadioButton fx:id="easy" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="difficultButton" text="Easy" HBox.hgrow="ALWAYS">
-               <toggleGroup>
-                  <ToggleGroup fx:id="difficult" />
-               </toggleGroup>
-               <HBox.margin>
-                  <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
-               </HBox.margin>
-            </RadioButton>
-            <RadioButton fx:id="medium" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" selected="true" styleClass="difficultButton" text="Medium" toggleGroup="$difficult" HBox.hgrow="ALWAYS">
-               <HBox.margin>
-                  <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
-               </HBox.margin>
-            </RadioButton>
-            <RadioButton fx:id="hard" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="difficultButton" text="Hard" toggleGroup="$difficult" HBox.hgrow="ALWAYS">
-               <HBox.margin>
-                  <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
-               </HBox.margin>
-            </RadioButton>
-         </children>
-      </HBox>
-   </center>
-   <right>
-      <VBox prefHeight="287.0" prefWidth="159.0" 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" />
+            <HBox>
+               <children>
+                  <RadioButton fx:id="easy" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="difficultButton" text="Easy" HBox.hgrow="ALWAYS">
+                     <toggleGroup>
+                        <ToggleGroup fx:id="difficult" />
+                     </toggleGroup>
+                     <HBox.margin>
+                        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
+                     </HBox.margin>
+                  </RadioButton>
+                  <RadioButton fx:id="medium" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" selected="true" styleClass="difficultButton" text="Medium" toggleGroup="$difficult" HBox.hgrow="ALWAYS">
+                     <HBox.margin>
+                        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
+                     </HBox.margin>
+                  </RadioButton>
+                  <RadioButton fx:id="hard" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="difficultButton" text="Hard" toggleGroup="$difficult" HBox.hgrow="ALWAYS">
+                     <HBox.margin>
+                        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
+                     </HBox.margin>
+                  </RadioButton>
+               </children>
+            </HBox>
+            <VBox>
+               <children>
+                  <TableView fx:id="scores" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="2000.0">
+                    <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>
          </children>
       </VBox>
-   </right>
+   </center>
 </BorderPane>
diff --git a/src/test/java/jez04/structure/test/AllOfContinue.java b/src/test/java/jez04/structure/test/AllOfContinue.java
new file mode 100644
index 0000000..8eb250b
--- /dev/null
+++ b/src/test/java/jez04/structure/test/AllOfContinue.java
@@ -0,0 +1,39 @@
+package jez04.structure.test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+
+public class AllOfContinue<T> extends BaseMatcher<T> {
+
+	private final List<Matcher<? super T>> matchers;
+
+    @SafeVarargs
+	public AllOfContinue(Matcher<? super T> ... matchers) {
+        this(Arrays.asList(matchers));
+    }
+
+    public AllOfContinue(List<Matcher<? super T>> matchers) {
+        this.matchers = matchers;
+    }
+
+    @Override
+    public boolean matches(Object o) {
+        for (Matcher<? super T> matcher : matchers) {
+            if (!matcher.matches(o)) {
+//                matcher.describeMismatch(o, mismatch);
+              return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendList("(", " " + "and" + " ", ")", matchers);
+    }
+
+}
diff --git a/src/test/java/jez04/structure/test/ClassExist.java b/src/test/java/jez04/structure/test/ClassExist.java
new file mode 100644
index 0000000..7e1f9b7
--- /dev/null
+++ b/src/test/java/jez04/structure/test/ClassExist.java
@@ -0,0 +1,44 @@
+package jez04.structure.test;
+
+import org.hamcrest.Description;
+
+public class ClassExist extends StructureMatcher<String> {
+
+	private String className;
+	private boolean useRegExp;
+	private boolean caseSensitive;
+
+	public ClassExist(String className) {
+		this(className, true, false);
+	}
+
+	public ClassExist(String className, boolean caseSensitive, boolean useRegExp) {
+		this.className = className;
+		this.useRegExp = useRegExp;
+		this.caseSensitive = caseSensitive;
+	}
+
+	@Override
+	public boolean matches(Object actual) {
+		if (useRegExp) {
+			return structureHelper.getAllClasses().stream().anyMatch(
+					c -> caseSensitive ? c.matches(className) : c.toLowerCase().matches(className.toLowerCase()));
+		} else {
+			return structureHelper.getAllClasses().stream().anyMatch(
+					c -> caseSensitive ? c.endsWith(className) : c.toLowerCase().endsWith(className.toLowerCase()));
+		}
+
+	}
+
+	@Override
+	public void describeTo(Description description) {
+		description.appendText(String.format("class/interface with name '%s' comparsion params(%s %s)  exists", className,
+				caseSensitive ? "" : "no case sensitive", useRegExp ? "using regexp" : ""));
+	}
+
+	@Override
+	public void describeMismatch(Object item, Description description) {
+		description.appendValueList("no class match from:\n      ", "\n      ", "", structureHelper.getAllClasses());
+	}
+
+}
diff --git a/src/test/java/jez04/structure/test/ClassStructureTest.java b/src/test/java/jez04/structure/test/ClassStructureTest.java
index aa54460..74691a5 100644
--- a/src/test/java/jez04/structure/test/ClassStructureTest.java
+++ b/src/test/java/jez04/structure/test/ClassStructureTest.java
@@ -1,138 +1,28 @@
 package jez04.structure.test;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.startsWith;
+import static org.hamcrest.Matchers.stringContainsInOrder;
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.URISyntaxException;
 import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 import java.util.List;
 
-import org.hamcrest.Matcher;
-import org.hamcrest.MatcherAssert;
-import org.hamcrest.Matchers;
 import org.junit.jupiter.api.Test;
 
-import javafx.event.ActionEvent;
-import javafx.fxml.FXML;
-import javafx.scene.control.TableColumn;
-import javafx.scene.control.TableView;
-import static org.hamcrest.MatcherAssert.assertThat; 
-import static org.hamcrest.Matchers.*;
-
 class ClassStructureTest {
 
-	StructureHelper helper = new StructureHelper();
-
-	@Test
-	void dataImporterTest() throws URISyntaxException, IOException, IllegalAccessException, InvocationTargetException {
-		helper.classExist("DataImporter", false);
-		Class<?> d = helper.getClass("DataImporter", false);
-	}
-
-	@Test
-	void dataImporterDownloadTest() throws URISyntaxException, IOException, IllegalAccessException, InvocationTargetException {
-		helper.classExist("DataImporter", false);
-		Class<?> d = helper.getClass("DataImporter", false);
-		String src = helper.getSourceCode(d);
-		helper.hasMethod(d, ".*down.*", false, String.class);
-		Method download = helper.getMethod(d, ".*down.*", false, String.class);
-		String downloadedText = (String) download.invoke(null);
-		assertThat(downloadedText, allOf(
-				endsWith("}]}"),
-				startsWith("{\"status\":\"OK\""),
-				stringContainsInOrder("firstname"),
-				stringContainsInOrder("lastname"),
-				stringContainsInOrder("birthday")
-				));
-		
-	}
-
-	@Test
-	void dataImporterParseAllTest() throws URISyntaxException, IOException, IllegalAccessException, InvocationTargetException {
-		helper.classExist("DataImporter", false);
-		Class<?> d = helper.getClass("DataImporter", false);
-		String src = helper.getSourceCode(d);
-		helper.hasMethod(d, ".*parse.*", false, List.class, String.class);
-		Method parse = helper.getMethod(d, ".*parse.*", false, List.class, String.class);
-		List<String> texts = (List<String>) parse.invoke(null, "{\"id\":1,\"firstname\":\"Tatum\",\"lastname\":\"Block\",\"email\":\"lonnie.bergstrom@stoltenberg.com\",\"phone\":\"+12397191764\",\"birthday\":\"1946-11-06\",\"gender\":\"male\",\"address\":{");
-		assertThat(texts, not(empty()));
-	}
-
-	@Test
-	void personTest() throws URISyntaxException, IOException, IllegalAccessException, InvocationTargetException {
-		helper.classExist("Person", false);
-		Class<?> p = helper.getClass("Person", false);
-	}
-
-	@Test
-	void dataImporterParsePersonTest() throws URISyntaxException, IOException, IllegalAccessException, InvocationTargetException {
-		helper.classExist("DataImporter", false);
-		Class<?> d = helper.getClass("DataImporter", false);
-		String src = helper.getSourceCode(d);
-
-		helper.classExist("Person", false);
-		Class<?> p = helper.getClass("Person", false);
-		helper.hasMethod(d, ".*parse.*", false, p, String.class);
-		Method parsePerson = helper.getMethod(d, ".*parse.*", false, p, String.class);
-		Object person = parsePerson.invoke(null, "{\"id\":1,\"firstname\":\"Tatum\",\"lastname\":\"Block\",\"email\":\"lonnie.bergstrom@stoltenberg.com\",\"phone\":\"+12397191764\",\"birthday\":\"1946-11-06\",\"gender\":\"male\",\"address\":{");
-		assertThat(person, notNullValue());
-	}
-
-	@Test
-	void personAgeTest() throws URISyntaxException, IOException, IllegalAccessException, InvocationTargetException {
-		helper.classExist("DataImporter", false);
-		Class<?> d = helper.getClass("DataImporter", false);
-
-		helper.classExist("Person", false);
-		Class<?> p = helper.getClass("Person", false);
-		helper.hasMethod(d, ".*parse.*", false, p, String.class);
-		Method parsePerson = helper.getMethod(d, ".*parse.*", false, p, String.class);
-		Object person = parsePerson.invoke(null, "{\"id\":1,\"firstname\":\"Tatum\",\"lastname\":\"Block\",\"email\":\"lonnie.bergstrom@stoltenberg.com\",\"phone\":\"+12397191764\",\"birthday\":\"1946-11-06\",\"gender\":\"male\",\"address\":{");
-		assertThat(person, notNullValue());
-
-		Method age = helper.getMethod(p, ".*age.*", false, int.class);
-		int result = (int)age.invoke(person);
-		assertEquals(78, result , "Calculate wrong age.");
-	}
-
-	@Test
-	void person50birthdayTest() throws URISyntaxException, IOException, IllegalAccessException, InvocationTargetException {
-		helper.classExist("DataImporter", false);
-		Class<?> d = helper.getClass("DataImporter", false);
-		String src = helper.getSourceCode(d);
-
-		helper.classExist("Person", false);
-		Class<?> p = helper.getClass("Person", false);
-		helper.hasMethod(d, ".*parse.*", false, p, String.class);
-		Method parsePerson = helper.getMethod(d, ".*parse.*", false, p, String.class);
-		Object person = parsePerson.invoke(null, "{\"id\":1,\"firstname\":\"Tatum\",\"lastname\":\"Block\",\"email\":\"lonnie.bergstrom@stoltenberg.com\",\"phone\":\"+12397191764\",\"birthday\":\"1946-11-06\",\"gender\":\"male\",\"address\":{");
-		assertThat(person, notNullValue());
-		
-		Method birth50 = helper.getMethod(p, ".*50.*", false, LocalDate.class);
-		LocalDate ldBirth50 = (LocalDate)birth50.invoke(person);
-		assertEquals(LocalDate.of(1996, 11, 06), ldBirth50 , "Calculate wrong 50th birthday.");
-		
-	}
-
-	@Test
-	void peronNextBirthdayTest() throws URISyntaxException, IOException, IllegalAccessException, InvocationTargetException {
-		helper.classExist("DataImporter", false);
-		Class<?> d = helper.getClass("DataImporter", false);
-		String src = helper.getSourceCode(d);
-		
-		helper.classExist("Person", false);
-		Class<?> p = helper.getClass("Person", false);
-		helper.hasMethod(d, ".*parse.*", false, p, String.class);
-		Method parsePerson = helper.getMethod(d, ".*parse.*", false, p, String.class);
-		Object person = parsePerson.invoke(null, "{\"id\":1,\"firstname\":\"Tatum\",\"lastname\":\"Block\",\"email\":\"lonnie.bergstrom@stoltenberg.com\",\"phone\":\"+12397191764\",\"birthday\":\"1946-11-06\",\"gender\":\"male\",\"address\":{");
-		assertThat(person, notNullValue());
+	StructureHelper helper = StructureHelper.getInstance();
 
-		Method daysM = helper.getMethod(p, ".*days.*", false, long.class);
-		long days= (long)daysM.invoke(person);
-		assertEquals(338, days , "Calculate wrong days to next birthday.");
-	}
 
 }
diff --git a/src/test/java/jez04/structure/test/ContainsInnerClasses.java b/src/test/java/jez04/structure/test/ContainsInnerClasses.java
new file mode 100644
index 0000000..5c79529
--- /dev/null
+++ b/src/test/java/jez04/structure/test/ContainsInnerClasses.java
@@ -0,0 +1,70 @@
+package jez04.structure.test;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.hamcrest.Description;
+
+public class ContainsInnerClasses extends StructureMatcher<Class<?>> {
+
+	private String methodNameRegexp;
+	private boolean caseSensitive = true;
+	private boolean useRegExp = false;
+	private int count = 1;
+	private List<Class<?>> params;
+
+	public ContainsInnerClasses(String methodNameRegexp) {
+		this.methodNameRegexp = methodNameRegexp;
+	}
+
+	public ContainsInnerClasses caseSensitive(boolean caseSensitive) {
+		this.caseSensitive = caseSensitive;
+		return this;
+	}
+
+	public ContainsInnerClasses count(int count) {
+		this.count = count;
+		return this;
+	}
+
+	public ContainsInnerClasses useRegExp(boolean useRegExp) {
+		this.useRegExp = useRegExp;
+		return this;
+	}
+
+	@Override
+	public boolean matches(Object actual) {
+		if (actual instanceof Class c) {
+			long lamdaCount = structureHelper.countMethodRegexp(c, "lambda\\$.*");
+			long innerClassCount = structureHelper.countClassesRegexp(c.getCanonicalName()+"\\$.*");
+			long methodRefCount = 0;
+			try {
+				methodRefCount = structureHelper.countMethodReference(c);
+			} catch (URISyntaxException  | IOException e) {
+				System.out.println("Cannot count method references");
+				e.printStackTrace();
+			}
+			return lamdaCount + innerClassCount+methodRefCount >= count;
+		}
+		return false;
+	}
+
+	@Override
+	public void describeTo(Description description) {
+		params.stream().map(Class::getName).collect(Collectors.joining(", "));
+		description.appendText(
+				String.format("Class should have inner classses or lambdas name (regexp) of type %s %s %s with params types %s",
+						methodNameRegexp, caseSensitive ? "" : "ignore case", ""));
+	}
+
+	@Override
+	public void describeMismatch(Object item, Description description) {
+		if (item instanceof Class c) {
+			description.appendValueList("no method match from:\n      ", "\n      ", "", c.getDeclaredMethods());
+		} else {
+			description.appendText("mismatched item is not class type");
+		}
+	}
+}
diff --git a/src/test/java/jez04/structure/test/HasMethod.java b/src/test/java/jez04/structure/test/HasMethod.java
new file mode 100644
index 0000000..142fec8
--- /dev/null
+++ b/src/test/java/jez04/structure/test/HasMethod.java
@@ -0,0 +1,95 @@
+package jez04.structure.test;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.hamcrest.Description;
+
+public class HasMethod extends StructureMatcher<Class<?>> {
+
+	private String methodNameRegexp;
+	private Class<?> returnType;
+	private boolean caseSensitive = true;
+	private boolean useRegExp = false;
+	private Boolean abstractTag = null;
+	private Boolean finalTag = null;
+	private int count = 1;
+	private List<Class<?>> params;
+
+	public HasMethod(String methodNameRegexp, Class<?> returnType, Class<?>... params) {
+		this.methodNameRegexp = methodNameRegexp;
+		this.returnType = returnType;
+		this.params = List.of(params);
+	}
+
+	public HasMethod caseSensitive(boolean caseSensitive) {
+		this.caseSensitive = caseSensitive;
+		return this;
+	}
+
+	public HasMethod abstractTag(Boolean abstractTag) {
+		this.abstractTag = abstractTag;
+		return this;
+	}
+
+	public HasMethod finalTag(Boolean finalTag) {
+		this.finalTag = finalTag;
+		return this;
+	}
+
+	public HasMethod count(int count) {
+		this.count = count;
+		return this;
+	}
+
+	public HasMethod useRegExp(boolean useRegExp) {
+		this.useRegExp = useRegExp;
+		return this;
+	}
+
+	@Override
+	public boolean matches(Object actual) {
+		if (actual instanceof Class c) {
+			List<Method> methods = Arrays.asList(c.getDeclaredMethods());
+			Stream<Method> streamOfMethods;
+			if (useRegExp) {
+				streamOfMethods = methods.stream().filter(m -> caseSensitive ? m.getName().matches(methodNameRegexp)
+						: m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase()));
+
+			} else {
+				streamOfMethods = methods.stream().filter(m -> caseSensitive ? m.getName().endsWith(methodNameRegexp)
+						: m.getName().toLowerCase().endsWith(methodNameRegexp.toLowerCase()));
+			}
+			streamOfMethods = streamOfMethods
+					.filter(m -> returnType != null ? returnType.equals(m.getReturnType()) : true)
+					.filter(m -> finalTag != null ? Modifier.isAbstract(m.getModifiers()) == abstractTag.booleanValue()
+							: true)
+					.filter(m -> abstractTag != null ? Modifier.isFinal(m.getModifiers()) == finalTag.booleanValue()
+							: true);
+			long co = streamOfMethods.count(); 
+			return co >= count;
+		}
+		return false;
+	}
+
+	@Override
+	public void describeTo(Description description) {
+		params.stream().map(Class::getName).collect(Collectors.joining(", "));
+		description.appendText(
+				String.format("Class should have method name (regexp) of type %s %s %s with params types %s",
+						returnType, methodNameRegexp, caseSensitive ? "" : "ignore case", ""));
+	}
+
+	@Override
+	public void describeMismatch(Object item, Description description) {
+		if (item instanceof Class c) {
+			description.appendValueList("no method match from:\n      ", "\n      ", "", c.getDeclaredMethods());
+		} else {
+			description.appendText("mismatched item is not class type");
+		}
+	}
+}
diff --git a/src/test/java/jez04/structure/test/HasProperty.java b/src/test/java/jez04/structure/test/HasProperty.java
new file mode 100644
index 0000000..0e7098d
--- /dev/null
+++ b/src/test/java/jez04/structure/test/HasProperty.java
@@ -0,0 +1,91 @@
+package jez04.structure.test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import org.hamcrest.Description;
+
+public class HasProperty extends StructureMatcher<Class<?>> {
+
+	private String propertyNameRegexp;
+	private Class<?> type;
+	private Predicate<Type> genericTypeFilter;
+	private Predicate<Class<?>> typeFilter;
+	private Boolean array = false;
+	private boolean caseSensitive;
+	private Class<?> annotation = null;
+	private int count = 1;
+
+	public HasProperty(String propertyNameRegexp, Class<?> type, Boolean array) {
+		this(propertyNameRegexp, type, array, true);
+	}
+
+	public HasProperty(String propertyNameRegexp, Class<?> type, Boolean array, boolean caseSensitive) {
+		this.propertyNameRegexp = propertyNameRegexp;
+		this.type = type;
+		this.array = array;
+		this.caseSensitive = caseSensitive;
+	}
+
+	public HasProperty annotation(Class<?> annotation) {
+		this.annotation = annotation;
+		return this;
+	}
+
+	public HasProperty typeFilter(Predicate<Class<?>> typeFilter) {
+		this.typeFilter = typeFilter;
+		return this;
+	}
+
+	public HasProperty genericTypeFilter(Predicate<Type> genericTypeFilter) {
+		this.genericTypeFilter = genericTypeFilter;
+		return this;
+	}
+
+	public HasProperty count(int count) {
+		this.count = count;
+		return this;
+	}
+
+	@Override
+	public boolean matches(Object actual) {
+		if (actual instanceof Class c) {
+			Stream<?> streamOfResults;
+			List<Field> fields = Arrays.asList(c.getDeclaredFields());
+			Stream<Field> streamOfFields = fields.stream()
+					.filter(f -> caseSensitive ? f.getName().matches(propertyNameRegexp)
+							: f.getName().toLowerCase().matches(propertyNameRegexp.toLowerCase()))
+					.filter(f -> type != null ? f.getType().equals(type) : true)
+					.filter(f -> array != null ? f.getType().isAnnotation() == array.booleanValue() : true)
+					.filter(f -> genericTypeFilter != null ? genericTypeFilter.test(f.getGenericType()) : true)
+					.filter(f -> typeFilter != null ? typeFilter.test(f.getType()) : true);
+			streamOfResults = streamOfFields;
+			if (annotation != null) {
+				streamOfResults = streamOfFields.flatMap(f -> Arrays.asList(f.getAnnotations()).stream())
+						.map(a -> a.annotationType()).filter(a -> a.equals(annotation));
+			}
+			long actualCount = streamOfResults.count();
+			return this.count <= actualCount;
+		}
+		return false;
+	}
+
+	@Override
+	public void describeTo(Description description) {
+		description.appendText(String.format("Class should have field of type %s%s with name match regexp '%s'%s", type,
+				array != null && array ? "[]" : "", propertyNameRegexp, caseSensitive ? "" : "ignore case"));
+	}
+
+	@Override
+	public void describeMismatch(Object item, Description description) {
+		if (item instanceof Class c) {
+			description.appendValueList("none of", ", ", "match", c.getDeclaredFields());
+		} else {
+			description.appendText("mismatched item is not class type");
+		}
+	}
+}
diff --git a/src/test/java/jez04/structure/test/IsDescendatOf.java b/src/test/java/jez04/structure/test/IsDescendatOf.java
new file mode 100644
index 0000000..a202faa
--- /dev/null
+++ b/src/test/java/jez04/structure/test/IsDescendatOf.java
@@ -0,0 +1,24 @@
+package jez04.structure.test;
+
+import org.hamcrest.Description;
+
+public class IsDescendatOf extends StructureMatcher<Class<?>> {
+
+	private String className;
+	public IsDescendatOf(String className) {
+		this.className = className;
+	}
+	@Override
+	public boolean matches(Object actual) {
+		if(actual instanceof Class c) {
+			return structureHelper.getClass(className).isAssignableFrom(c);
+		}
+		return false;
+	}
+
+	@Override
+	public void describeTo(Description description) {
+		description.appendText(String.format("cass shoud be descendant of %s", className));
+	}
+	
+}
diff --git a/src/test/java/jez04/structure/test/IsInterface.java b/src/test/java/jez04/structure/test/IsInterface.java
new file mode 100644
index 0000000..2abdee2
--- /dev/null
+++ b/src/test/java/jez04/structure/test/IsInterface.java
@@ -0,0 +1,23 @@
+package jez04.structure.test;
+
+import org.hamcrest.Description;
+
+public class IsInterface extends StructureMatcher<Class<?>> {
+
+	public IsInterface() {
+	}
+
+	@Override
+	public boolean matches(Object actual) {
+		if (actual instanceof Class c) {
+			return c.isInterface();
+		}
+		return false;
+	}
+
+	@Override
+	public void describeTo(Description description) {
+		description.appendText(String.format("value should be interface"));
+	}
+
+}
diff --git a/src/test/java/jez04/structure/test/ResourceContains.java b/src/test/java/jez04/structure/test/ResourceContains.java
new file mode 100644
index 0000000..e7d9277
--- /dev/null
+++ b/src/test/java/jez04/structure/test/ResourceContains.java
@@ -0,0 +1,102 @@
+package jez04.structure.test;
+
+import java.io.IOException;
+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.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.hamcrest.Description;
+
+public class ResourceContains extends StructureMatcher<String> {
+	private String regexp;
+	private boolean caseInsensitive;
+	private boolean srcFound;
+	private int count = 1;
+
+	public ResourceContains(String regexp, boolean caseInsensitive) {
+		this.regexp = regexp;
+		this.caseInsensitive = caseInsensitive;
+	}
+
+	public ResourceContains count(int count) {
+		this.count = count;
+		return this;
+	}
+
+	@Override
+	public boolean matches(Object actual) {
+		srcFound = true;
+		List<Path> foundResources = new LinkedList<>();
+		Pattern p;
+		if (caseInsensitive) {
+			p = Pattern.compile(regexp, Pattern.CASE_INSENSITIVE);
+		} else {
+			p = Pattern.compile(regexp);
+		}
+		try {
+			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 resourcesRoot = classRoot.getParent().getParent().resolve(Paths.get("src", "main", "resources"));
+			System.out.println("resources root: " + resourcesRoot);
+			Files.walkFileTree(resourcesRoot, new FileVisitor<Path>() {
+
+				@Override
+				public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+					return FileVisitResult.CONTINUE;
+				}
+
+				@Override
+				public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+					if (p.matcher(file.getFileName().toString()).matches()) {
+						foundResources.add(file);
+					}
+					return FileVisitResult.CONTINUE;
+				}
+
+				@Override
+				public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+					return FileVisitResult.CONTINUE;
+				}
+
+				@Override
+				public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+					return FileVisitResult.CONTINUE;
+				}
+			});
+			return foundResources.size() >= count;
+		} catch (URISyntaxException | IOException e) {
+			srcFound = false;
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	@Override
+	public void describeTo(Description description) {
+		description.appendText(String.format("Source code of class shoud contains regexp '%s'%s", regexp,
+				caseInsensitive ? " in case insensitive mode" : ""));
+	}
+
+	@Override
+	public void describeMismatch(Object item, Description description) {
+		if (srcFound) {
+			description
+					.appendText(String.format("source code of class %s do not contains substring that match reg exp"));
+		} else {
+			description.appendText(String.format("source code of class %s was not found"));
+		}
+	}
+
+}
diff --git a/src/test/java/jez04/structure/test/SrcContains.java b/src/test/java/jez04/structure/test/SrcContains.java
new file mode 100644
index 0000000..4ed7d6c
--- /dev/null
+++ b/src/test/java/jez04/structure/test/SrcContains.java
@@ -0,0 +1,54 @@
+package jez04.structure.test;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.regex.Pattern;
+
+import org.hamcrest.Description;
+
+public class SrcContains extends StructureMatcher<Class<?>> {
+	private String regexp;
+	private boolean caseInsensitive;
+	private boolean srcFound;
+
+	public SrcContains(String regexp, boolean caseInsensitive) {
+		this.regexp = regexp;
+		this.caseInsensitive = caseInsensitive;
+	}
+
+	@Override
+	public boolean matches(Object actual) {
+		srcFound = true;
+		if (actual instanceof Class c) {
+			Pattern p = Pattern.compile(regexp);
+			if (caseInsensitive) {
+				p = Pattern.compile(regexp, Pattern.CASE_INSENSITIVE);
+			}
+			try {
+				return p.matcher(structureHelper.getSourceCode(c)).find();
+			} catch (URISyntaxException | IOException e) {
+				srcFound = false;
+				e.printStackTrace();
+				return false;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public void describeTo(Description description) {
+		description.appendText(String.format("Source code of class shoud contains regexp '%s'%s", regexp,
+				caseInsensitive ? " in case insensitive mode" : ""));
+	}
+
+	@Override
+	public void describeMismatch(Object item, Description description) {
+		if (srcFound) {
+			description
+					.appendText(String.format("source code of class %s do not contains substring that match reg exp", item));
+		} else {
+			description.appendText(String.format("source code of class %s was not found"));
+		}
+	}
+
+}
diff --git a/src/test/java/jez04/structure/test/StructureHelper.java b/src/test/java/jez04/structure/test/StructureHelper.java
index 6c18100..6ccabb4 100644
--- a/src/test/java/jez04/structure/test/StructureHelper.java
+++ b/src/test/java/jez04/structure/test/StructureHelper.java
@@ -29,7 +29,10 @@ import java.util.function.Predicate;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
 import org.junit.jupiter.api.Assertions;
 import org.reflections.Configuration;
 import org.reflections.Reflections;
@@ -38,8 +41,25 @@ import org.reflections.util.ConfigurationBuilder;
 
 class StructureHelper {
 
+	private static StructureHelper singeltonInstance;
+
+	public static StructureHelper getInstance() {
+		if (singeltonInstance == null) {
+			singeltonInstance = new StructureHelper();
+		}
+		return singeltonInstance;
+	}
+
 	Set<String> allClasses = getNameOfAllClasses();
 
+	private StructureHelper() {
+		/* hide public one */
+	}
+
+	public Set<String> getAllClasses() {
+		return allClasses;
+	}
+
 	public void isInterface(Class<?> c) {
 		assertTrue(c.isInterface(), c.getName() + " have to be interface.");
 	}
@@ -47,16 +67,23 @@ class StructureHelper {
 	public void classExist(String name) {
 		classExist(name, true);
 	}
+
 	public void classExist(String name, boolean caseSensitive) {
-		assertTrue(allClasses.stream().anyMatch(c -> caseSensitive?c.endsWith(name):c.toLowerCase().endsWith(name.toLowerCase())), "Class/Interface " + name + " not found");
+		assertTrue(
+				allClasses.stream()
+						.anyMatch(c -> caseSensitive ? c.endsWith(name) : c.toLowerCase().endsWith(name.toLowerCase())),
+				"Class/Interface " + name + " not found");
 	}
 
 	public void classExistRegexp(String name) {
 		classExistRegexp(name, true);
 	}
-	
+
 	public void classExistRegexp(String name, boolean caseSensitive) {
-		assertTrue(allClasses.stream().anyMatch(c -> caseSensitive?c.matches(name):c.toLowerCase().matches(name.toLowerCase())), "Class/Interface " + name + " not found");
+		assertTrue(
+				allClasses.stream()
+						.anyMatch(c -> caseSensitive ? c.matches(name) : c.toLowerCase().matches(name.toLowerCase())),
+				"Class/Interface " + name + " not found");
 	}
 
 	public Class<?> getClassDirectly(String name) {
@@ -66,23 +93,25 @@ class StructureHelper {
 	public Class<?> getClassRegexp(String name) {
 		return getClassRegexp(name, true);
 	}
-	
+
 	public Class<?> getClassRegexp(String name, boolean caseSensitive) {
-		String className = allClasses.stream().filter(c -> 
-		caseSensitive?
-				c.matches(name):
-					c.toLowerCase().matches(name.toLowerCase())
-					).findAny().orElse(null);
+		String className = allClasses.stream()
+				.filter(c -> caseSensitive ? c.matches(name) : c.toLowerCase().matches(name.toLowerCase())).findAny()
+				.orElse(null);
 		if (className == null) {
 			Assertions.fail("Class " + name + " not found.");
 		}
 		return loadClass(name, className);
 	}
+
 	public Class<?> getClass(String name) {
 		return getClass(name, true);
 	}
+
 	public Class<?> getClass(String name, boolean caseSensitive) {
-		String className = allClasses.stream().filter(c -> caseSensitive?c.endsWith(name):c.toLowerCase().endsWith(name.toLowerCase())).findAny().orElse(null);
+		String className = allClasses.stream()
+				.filter(c -> caseSensitive ? c.endsWith(name) : c.toLowerCase().endsWith(name.toLowerCase())).findAny()
+				.orElse(null);
 		if (className == null) {
 			Assertions.fail("Class " + name + " not found.");
 		}
@@ -105,13 +134,27 @@ class StructureHelper {
 		}
 	}
 
+	public org.hamcrest.Matcher<Class<?>> hasProperty(String propertyNameRegexp, Class<?> type, boolean array) {
+		return new HasProperty(propertyNameRegexp, type, array);
+	}
+
 	public void hasProperty(Class<?> classDef, String propertyNameRegexp, Class<?> type, boolean array) {
 		hasProperty(classDef, propertyNameRegexp, type, array, true);
 	}
-	public void hasProperty(Class<?> classDef, String propertyNameRegexp, Class<?> type, boolean array, boolean caseSensitive) {
+
+	public void hasProperty(Class<?> classDef, String propertyNameRegexp, Class<?> type, boolean array,
+			boolean caseSensitive) {
+		assertTrue(hasPropertyB(classDef, propertyNameRegexp, type, array, caseSensitive),
+				"No field " + propertyNameRegexp + " of type " + type.getName() + " (is array " + array + ") in class "
+						+ classDef.getName());
+	}
+
+	public boolean hasPropertyB(Class<?> classDef, String propertyNameRegexp, Class<?> type, boolean array,
+			boolean caseSensitive) {
 		List<Field> fields = Arrays.asList(classDef.getDeclaredFields());
-		assertTrue(fields.stream().anyMatch(f -> {
-			if (caseSensitive?f.getName().matches(propertyNameRegexp):f.getName().toLowerCase().matches(propertyNameRegexp.toLowerCase())) {
+		return fields.stream().anyMatch(f -> {
+			if (caseSensitive ? f.getName().matches(propertyNameRegexp)
+					: f.getName().toLowerCase().matches(propertyNameRegexp.toLowerCase())) {
 				if (array) {
 					return f.getType().isArray() && f.getType().getComponentType().equals(type);
 				} else {
@@ -119,18 +162,20 @@ class StructureHelper {
 				}
 			}
 			return false;
-		}), "No field " + propertyNameRegexp + " of type " + type.getName() + " (is array " + array + ") in class "
-				+ classDef.getName());
+		});
 	}
 
 	public void hasPropertyWithAnnotation(Class<?> classDef, String propertyNameRegexp, Class<?> annotation) {
 		hasPropertyWithAnnotation(classDef, propertyNameRegexp, annotation, true);
 	}
-	
-	public void hasPropertyWithAnnotation(Class<?> classDef, String propertyNameRegexp, Class<?> annotation, boolean caseSensitive) {
+
+	public void hasPropertyWithAnnotation(Class<?> classDef, String propertyNameRegexp, Class<?> annotation,
+			boolean caseSensitive) {
 		List<Field> fields = Arrays.asList(classDef.getDeclaredFields());
 		assertTrue(
-				fields.stream().filter(f -> caseSensitive?f.getName().matches(propertyNameRegexp):f.getName().toLowerCase().matches(propertyNameRegexp.toLowerCase()))
+				fields.stream()
+						.filter(f -> caseSensitive ? f.getName().matches(propertyNameRegexp)
+								: f.getName().toLowerCase().matches(propertyNameRegexp.toLowerCase()))
 						.flatMap(f -> Arrays.asList(f.getAnnotations()).stream()).map(a -> a.annotationType())
 						.anyMatch(a -> a.equals(annotation)),
 				"No field " + propertyNameRegexp + " with annotation " + annotation.getName() + " in class "
@@ -140,11 +185,14 @@ class StructureHelper {
 	public void hasMethod(Class<?> interfaceDef, String methodName, Class<?> returnType) {
 		hasMethod(interfaceDef, methodName, returnType, true);
 	}
+
 	public void hasMethod(Class<?> interfaceDef, String methodName, Class<?> returnType, boolean caseSensitive) {
 		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
 		assertTrue(methods.stream().anyMatch(m -> m.getName().contains(methodName)), "No method " + methodName);
 		assertTrue(
-				methods.stream().filter(m -> caseSensitive?m.getName().matches(methodName):m.getName().toLowerCase().matches(methodName.toLowerCase()))
+				methods.stream()
+						.filter(m -> caseSensitive ? m.getName().matches(methodName)
+								: m.getName().toLowerCase().matches(methodName.toLowerCase()))
 						.anyMatch(m -> m.getReturnType().equals(returnType)),
 				"Method " + methodName + " not return " + returnType.getName());
 	}
@@ -152,25 +200,35 @@ class StructureHelper {
 	public void hasMethod(Class<?> interfaceDef, String methodName, Class<?> returnType, Class<?>... params) {
 		hasMethod(interfaceDef, methodName, true, returnType, params);
 	}
-	
-	public void hasMethod(Class<?> interfaceDef, String methodName, boolean caseSensitive, Class<?> returnType, Class<?>... params) {
+
+	public void hasMethod(Class<?> interfaceDef, String methodName, boolean caseSensitive, Class<?> returnType,
+			Class<?>... params) {
 		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
-		assertTrue(methods.stream().anyMatch(m -> caseSensitive?m.getName().matches(methodName):m.getName().toLowerCase().matches(methodName.toLowerCase())), "No method " + methodName);
 		assertTrue(
-				methods.stream().filter(m -> caseSensitive?m.getName().matches(methodName):m.getName().toLowerCase().matches(methodName.toLowerCase()))
+				methods.stream()
+						.anyMatch(m -> caseSensitive ? m.getName().matches(methodName)
+								: m.getName().toLowerCase().matches(methodName.toLowerCase())),
+				"No method " + methodName);
+		assertTrue(
+				methods.stream()
+						.filter(m -> caseSensitive ? m.getName().matches(methodName)
+								: m.getName().toLowerCase().matches(methodName.toLowerCase()))
 						.filter(m -> m.getReturnType().equals(returnType))
 						.anyMatch(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))),
 				"Method " + methodName + " has no all parrams:"
 						+ Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", ")));
 	}
 
-	public Method getMethod(Class<?> interfaceDef, String methodName,Class<?> returnType, Class<?>... params) {
+	public Method getMethod(Class<?> interfaceDef, String methodName, Class<?> returnType, Class<?>... params) {
 		return getMethod(interfaceDef, methodName, true, returnType, params);
 	}
 
-	public Method getMethod(Class<?> interfaceDef, String methodName, boolean caseSensitive, Class<?> returnType, Class<?>... params) {
+	public Method getMethod(Class<?> interfaceDef, String methodName, boolean caseSensitive, Class<?> returnType,
+			Class<?>... params) {
 		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
-		List<Method> foundMethods = methods.stream().filter(m -> caseSensitive?m.getName().matches(methodName):m.getName().toLowerCase().matches(methodName.toLowerCase()))
+		List<Method> foundMethods = methods.stream()
+				.filter(m -> caseSensitive ? m.getName().matches(methodName)
+						: m.getName().toLowerCase().matches(methodName.toLowerCase()))
 				.filter(m -> m.getReturnType().equals(returnType))
 				.filter(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))).toList();
 		if (foundMethods.isEmpty()) {
@@ -185,10 +243,11 @@ class StructureHelper {
 	public long countMethodRegexp(Class<?> interfaceDef, String methodNameRegexp) {
 		return countMethodRegexp(interfaceDef, methodNameRegexp, true);
 	}
-	
+
 	public long countMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, boolean caseSensitive) {
 		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
-		return methods.stream().filter(m -> caseSensitive?m.getName().matches(methodNameRegexp):m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase())).count();
+		return methods.stream().filter(m -> caseSensitive ? m.getName().matches(methodNameRegexp)
+				: m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase())).count();
 	}
 
 	public long countMethodReference(Class<?> interfaceDef) throws URISyntaxException, IOException {
@@ -211,8 +270,10 @@ class StructureHelper {
 	public long countClassesRegexp(String classNameRegexp) {
 		return countClassesRegexp(classNameRegexp, true);
 	}
+
 	public long countClassesRegexp(String classNameRegexp, boolean caseSensitive) {
-		return getNameOfAllClasses().stream().filter(className -> caseSensitive?className.matches(classNameRegexp):className.toLowerCase().matches(classNameRegexp.toLowerCase())).count();
+		return getNameOfAllClasses().stream().filter(className -> caseSensitive ? className.matches(classNameRegexp)
+				: className.toLowerCase().matches(classNameRegexp.toLowerCase())).count();
 	}
 
 	public void hasConstructor(Class<?> classDef, Class<?>... params) {
@@ -234,51 +295,106 @@ class StructureHelper {
 		}
 		return foundConstructors.get(0);
 	}
-	
+
 	public void hasMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, Class<?> returnType,
 			Class<?>... params) {
-		hasMethodRegexp(interfaceDef, methodNameRegexp, true, returnType, params);	
+		hasMethodRegexp(interfaceDef, methodNameRegexp, true, returnType, params);
 	}
-	
-	public void hasMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, boolean caseSensitive, Class<?> returnType,
-			Class<?>... params) {
+
+	public void hasMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, boolean caseSensitive,
+			Class<?> returnType, Class<?>... params) {
 		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
-		assertTrue(methods.stream().anyMatch(m -> caseSensitive?m.getName().matches(methodNameRegexp):m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase())),
+		assertTrue(
+				methods.stream()
+						.anyMatch(m -> caseSensitive ? m.getName().matches(methodNameRegexp)
+								: m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase())),
 				"No method " + methodNameRegexp);
 		assertTrue(
-				methods.stream().filter(m -> caseSensitive?m.getName().matches(methodNameRegexp):m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase()))
+				methods.stream()
+						.filter(m -> caseSensitive ? m.getName().matches(methodNameRegexp)
+								: m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase()))
 						.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 boolean hasMethodRegexpTest(Class<?> interfaceDef, String methodNameRegexp, boolean caseSensitive,
+			Class<?> returnType, Class<?>... params) {
+		return hasMethodRegexpTest(interfaceDef, methodNameRegexp, caseSensitive, returnType, List.of(params));
+	}
+
+	public boolean hasMethodRegexpTest(Class<?> interfaceDef, String methodNameRegexp, boolean caseSensitive,
+			Class<?> returnType, List<Class<?>> params) {
+		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
+		if (!methods.stream().anyMatch(m -> caseSensitive ? m.getName().matches(methodNameRegexp)
+				: m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase()))) {
+			return false;
+		}
+		return methods.stream()
+				.filter(m -> caseSensitive ? m.getName().matches(methodNameRegexp)
+						: m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase()))
+				.filter(m -> m.getReturnType().equals(returnType))
+				.anyMatch(m -> Arrays.asList(m.getParameterTypes()).containsAll(params));
+	}
+
 	public long countMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, Class<?> returnType,
 			Class<?>... params) {
 		return countMethodRegexp(interfaceDef, methodNameRegexp, true, returnType, params);
 	}
-	
-	public long countMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, boolean caseSensitive, Class<?> returnType,
-			Class<?>... params) {
+
+	public long countMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, boolean caseSensitive,
+			Class<?> returnType, Class<?>... params) {
 		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
-		assertTrue(methods.stream().anyMatch(m -> caseSensitive?m.getName().matches(methodNameRegexp):m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase())),
+		assertTrue(
+				methods.stream()
+						.anyMatch(m -> caseSensitive ? m.getName().matches(methodNameRegexp)
+								: m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase())),
 				"No method " + methodNameRegexp);
-		return methods.stream().filter(m -> caseSensitive?m.getName().matches(methodNameRegexp):m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase()))
+		return methods.stream()
+				.filter(m -> caseSensitive ? m.getName().matches(methodNameRegexp)
+						: m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase()))
 				.filter(m -> m.getReturnType().equals(returnType))
 				.filter(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))).count();
 	}
 
+	public boolean hasMethodTest(Class<?> interfaceDef, boolean finalTag, boolean abstractTag, String methodName,
+			boolean caseSensitive, Class<?> returnType, Class<?>... params) {
+		return hasMethodTest(interfaceDef, finalTag, abstractTag, methodName, caseSensitive, returnType, List.of(params));
+	}
+	public boolean hasMethodTest(Class<?> interfaceDef, boolean finalTag, boolean abstractTag, String methodName,
+			boolean caseSensitive, Class<?> returnType, List<Class<?>> params) {
+		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
+		if (!methods.stream().anyMatch(m -> caseSensitive ? m.getName().matches(methodName)
+				: m.getName().toLowerCase().matches(methodName.toLowerCase()))) {
+			return false;
+		}
+		return methods.stream()
+				.filter(m -> caseSensitive ? m.getName().matches(methodName)
+						: m.getName().toLowerCase().matches(methodName.toLowerCase()))
+				.filter(m -> m.getReturnType().equals(returnType)
+						&& (Modifier.isAbstract(m.getModifiers()) == abstractTag)
+						&& (Modifier.isFinal(m.getModifiers()) == finalTag))
+				.anyMatch(m -> Arrays.asList(m.getParameterTypes()).containsAll(params));
+	}
+
 	public void hasMethod(Class<?> interfaceDef, boolean finalTag, boolean abstractTag, String methodName,
 			Class<?> returnType, Class<?>... params) {
 		hasMethod(interfaceDef, finalTag, abstractTag, methodName, true, returnType, params);
 	}
-	
-	public void hasMethod(Class<?> interfaceDef, boolean finalTag, boolean abstractTag, String methodName, boolean caseSensitive,
-			Class<?> returnType, Class<?>... params) {
+
+	public void hasMethod(Class<?> interfaceDef, boolean finalTag, boolean abstractTag, String methodName,
+			boolean caseSensitive, Class<?> returnType, Class<?>... params) {
 		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
-		assertTrue(methods.stream().anyMatch(m -> caseSensitive?m.getName().matches(methodName):m.getName().toLowerCase().matches(methodName.toLowerCase())), "No method " + methodName);
 		assertTrue(
-				methods.stream().filter(m -> caseSensitive?m.getName().matches(methodName):m.getName().toLowerCase().matches(methodName.toLowerCase()))
+				methods.stream()
+						.anyMatch(m -> caseSensitive ? m.getName().matches(methodName)
+								: m.getName().toLowerCase().matches(methodName.toLowerCase())),
+				"No method " + methodName);
+		assertTrue(
+				methods.stream()
+						.filter(m -> caseSensitive ? m.getName().matches(methodName)
+								: m.getName().toLowerCase().matches(methodName.toLowerCase()))
 						.filter(m -> m.getReturnType().equals(returnType)
 								&& (Modifier.isAbstract(m.getModifiers()) == abstractTag)
 								&& (Modifier.isFinal(m.getModifiers()) == finalTag))
@@ -287,6 +403,10 @@ class StructureHelper {
 						+ Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", ")));
 	}
 
+	public boolean isDescendatOf(Class<?> clazz, String interfaceName) {
+		return getClass(interfaceName).isAssignableFrom(clazz);
+	}
+
 	public void hasImplements(Class<?> clazz, String... interfaceNames) {
 		List<Class<?>> interfaces = new ArrayList<>();
 		Arrays.asList(interfaceNames).stream().map(name -> getClass(name)).forEach(c -> interfaces.add(c));
@@ -308,10 +428,14 @@ class StructureHelper {
 	public void hasMethod(Class<?> interfaceDef, String methodName) {
 		hasMethod(interfaceDef, methodName, true);
 	}
-	
+
 	public void hasMethod(Class<?> interfaceDef, String methodName, boolean caseSensitive) {
 		List<Method> methods = Arrays.asList(interfaceDef.getMethods());
-		assertTrue(methods.stream().anyMatch(m -> caseSensitive?m.getName().matches(methodName):m.getName().toLowerCase().matches(methodName.toLowerCase())), "No method " + methodName);
+		assertTrue(
+				methods.stream()
+						.anyMatch(m -> caseSensitive ? m.getName().matches(methodName)
+								: m.getName().toLowerCase().matches(methodName.toLowerCase())),
+				"No method " + methodName);
 	}
 
 	public String getSourceCode(Class<?> clazz) throws URISyntaxException, IOException {
diff --git a/src/test/java/jez04/structure/test/StructureMatcher.java b/src/test/java/jez04/structure/test/StructureMatcher.java
new file mode 100644
index 0000000..6b8bc25
--- /dev/null
+++ b/src/test/java/jez04/structure/test/StructureMatcher.java
@@ -0,0 +1,7 @@
+package jez04.structure.test;
+
+public abstract class StructureMatcher<T> extends org.hamcrest.BaseMatcher<T>{
+		
+		protected StructureHelper  structureHelper = StructureHelper.getInstance();
+		
+}
-- 
GitLab