diff --git a/.gitignore b/.gitignore
index e295b15b15444d4e6e163fb51a2edb17668c3383..e4a159670c17c260446ba6a3b5e10d6ae7adf2b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+/logs/
 /target/
 .settings/
 .project
diff --git a/pom.xml b/pom.xml
index 37f2c2858d4e2b265be88dcdd886625bb171c575..fb407c91e0cc374eb2345fc22527dc22bc34106a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,22 @@
 		<maven.compiler.target>21</maven.compiler.target>
 	</properties>
 	<dependencies>
+		<!--
+		https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
+		<dependency>
+			<groupId>org.apache.logging.log4j</groupId>
+			<artifactId>log4j-core</artifactId>
+			<version>2.24.3</version>
+		</dependency>
+
+		<!--
+		https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
+		<dependency>
+			<groupId>org.apache.logging.log4j</groupId>
+			<artifactId>log4j-api</artifactId>
+			<version>2.24.3</version>
+		</dependency>
+
 		<dependency>
 			<groupId>com.h2database</groupId>
 			<artifactId>h2</artifactId>
diff --git a/src/main/java/lab/Setup.java b/src/main/java/lab/Setup.java
index ab0f461d3436c64bd18610f877ece8cd3e6f3a05..6716305b44ee09e7135304617f15a7bd1fe8f07c 100644
--- a/src/main/java/lab/Setup.java
+++ b/src/main/java/lab/Setup.java
@@ -8,15 +8,30 @@ public class Setup {
 	private static Setup instance;
 
 	private ScoreStorageInterface scoreStorageInterface = new DbConnector();
-	private double lochnessMinXPopsition = 0.5;
-	private double lochnessMinYPopsition = 0.5;
-	private double lochnessMinSpeed = 50;
-	private double lochnessMaxSpeed = 150;
-	private int lochnessMultiplier = 1;
-	private double boatCollisionHeight = 0.25;
-	private double boatHitPulseX = 20;
-	private double boatHitPulseYMin = 50;
-	private double boatHitPulseYMax = 100;
+	private double lochnessMinXPopsition;
+	private double lochnessMinYPopsition;
+	private double lochnessMinSpeed;
+	private double lochnessMaxSpeed;
+	private int lochnessMultiplier;
+	private double boatCollisionHeight;
+	private double boatHitPulseX;
+	private double boatHitPulseYMin;
+	private double boatHitPulseYMax;
+
+	private Setup(ScoreStorageInterface scoreStorageInterface, double lochnessMinXPopsition,
+			double lochnessMinYPopsition, double lochnessMinSpeed, double lochnessMaxSpeed, int lochnessMultiplier,
+			double boatCollisionHeight, double boatHitPulseX, double boatHitPulseYMin, double boatHitPulseYMax) {
+		this.scoreStorageInterface = scoreStorageInterface;
+		this.lochnessMinXPopsition = lochnessMinXPopsition;
+		this.lochnessMinYPopsition = lochnessMinYPopsition;
+		this.lochnessMinSpeed = lochnessMinSpeed;
+		this.lochnessMaxSpeed = lochnessMaxSpeed;
+		this.lochnessMultiplier = lochnessMultiplier;
+		this.boatCollisionHeight = boatCollisionHeight;
+		this.boatHitPulseX = boatHitPulseX;
+		this.boatHitPulseYMin = boatHitPulseYMin;
+		this.boatHitPulseYMax = boatHitPulseYMax;
+	}
 
 	public static void configure(Setup setting) {
 		instance = setting;
@@ -66,4 +81,82 @@ public class Setup {
 		return lochnessMaxSpeed;
 	}
 
+	public static Builder builder() {
+		return new Builder();
+	}
+
+	public static Setup getInstanceForHardcoreGame() {
+		return builder().lochnessMaxSpeed(500).lochnessMinSpeed(200).lochnessMultiplier(10).lochnessMinYPopsition(0.1)
+				.build();
+	}
+
+	public static class Builder {
+		private ScoreStorageInterface scoreStorageInterface = new DbConnector();
+		private double lochnessMinXPopsition = 0.5;
+		private double lochnessMinYPopsition = 0.5;
+		private double lochnessMinSpeed = 50;
+		private double lochnessMaxSpeed = 150;
+		private int lochnessMultiplier = 1;
+		private double boatCollisionHeight = 0.25;
+		private double boatHitPulseX = 20;
+		private double boatHitPulseYMin = 50;
+		private double boatHitPulseYMax = 100;
+
+		public Builder scoreStorageInterface(ScoreStorageInterface scoreStorageInterface) {
+			this.scoreStorageInterface = scoreStorageInterface;
+			return this;
+		}
+
+		public Builder lochnessMinXPopsition(double lochnessMinXPopsition) {
+			this.lochnessMinXPopsition = lochnessMinXPopsition;
+			return this;
+		}
+
+		public Builder lochnessMinYPopsition(double lochnessMinYPopsition) {
+			this.lochnessMinYPopsition = lochnessMinYPopsition;
+			return this;
+		}
+
+		public Builder lochnessMinSpeed(double lochnessMinSpeed) {
+			this.lochnessMinSpeed = lochnessMinSpeed;
+			return this;
+		}
+
+		public Builder lochnessMaxSpeed(double lochnessMaxSpeed) {
+			this.lochnessMaxSpeed = lochnessMaxSpeed;
+			return this;
+		}
+
+		public Builder lochnessMultiplier(int lochnessMultiplier) {
+			this.lochnessMultiplier = lochnessMultiplier;
+			return this;
+		}
+
+		public Builder boatCollisionHeight(double boatCollisionHeight) {
+			this.boatCollisionHeight = boatCollisionHeight;
+			return this;
+		}
+
+		public Builder boatHitPulseX(double boatHitPulseX) {
+			this.boatHitPulseX = boatHitPulseX;
+			return this;
+		}
+
+		public Builder boatHitPulseYMin(double boatHitPulseYMin) {
+			this.boatHitPulseYMin = boatHitPulseYMin;
+			return this;
+		}
+
+		public Builder boatHitPulseYMax(double boatHitPulseYMax) {
+			this.boatHitPulseYMax = boatHitPulseYMax;
+			return this;
+		}
+
+		public Setup build() {
+			return new Setup(scoreStorageInterface, lochnessMinXPopsition, lochnessMinYPopsition, lochnessMinSpeed,
+					lochnessMaxSpeed, lochnessMultiplier, boatCollisionHeight, boatHitPulseX, boatHitPulseYMin,
+					boatHitPulseYMax);
+		}
+	}
+
 }
diff --git a/src/main/java/lab/data/Score.java b/src/main/java/lab/data/Score.java
index 01dc746bf946fa4492b77a46a8f80fb85d50d655..a9993a9c51be53b7870842a1757836b776292340 100644
--- a/src/main/java/lab/data/Score.java
+++ b/src/main/java/lab/data/Score.java
@@ -1,5 +1,6 @@
 package lab.data;
 
+import java.util.Objects;
 import java.util.Random;
 
 public class Score {
@@ -30,6 +31,23 @@ public class Score {
 		this.points = points;
 	}
 
+	@Override
+	public int hashCode() {
+		return Objects.hash(name, points);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		Score other = (Score) obj;
+		return Objects.equals(name, other.name) && points == other.points;
+	}
+
 	@Override
 	public String toString() {
 		return "Score [name=" + name + ", points=" + points + "]";
diff --git a/src/main/java/lab/game/LochNess.java b/src/main/java/lab/game/LochNess.java
index 1329d6a166def995707e7f1f32586e59bde80a90..5fc2f4d2d25f06cfe339cecec0807d7fc6eb94bc 100644
--- a/src/main/java/lab/game/LochNess.java
+++ b/src/main/java/lab/game/LochNess.java
@@ -4,6 +4,9 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
 import javafx.geometry.Dimension2D;
 import javafx.geometry.Point2D;
 import javafx.geometry.Rectangle2D;
@@ -13,6 +16,8 @@ import lab.Setup;
 
 public class LochNess extends WorldEntity implements Collisionable {
 
+	private static Logger log = LogManager.getLogger(LochNess.class);
+
 	private static final Random RANDOM = new Random();
 
 	private Point2D speed;
@@ -48,6 +53,7 @@ public class LochNess extends WorldEntity implements Collisionable {
 			position = new Point2D(scene.getSize().getWidth(), position.getY());
 			speed = speed.multiply(-1);
 		}
+		log.trace("LochNess position: {}", position);
 	}
 
 	@Override
@@ -57,6 +63,7 @@ public class LochNess extends WorldEntity implements Collisionable {
 
 	public void changeDirection() {
 		speed = speed.multiply(-1);
+		log.debug("LochNess chaned direction.");
 	}
 
 	@Override
@@ -66,6 +73,7 @@ public class LochNess extends WorldEntity implements Collisionable {
 
 	@Override
 	public void hitBy(Collisionable another) {
+		log.trace("LochNess hitted by {}.", another);
 		if (another instanceof Boat) {
 			scene.remove(this);
 			scene.add(new Rock(scene, getPosition(), new Dimension2D(10, 10)));
diff --git a/src/main/java/lab/gui/App.java b/src/main/java/lab/gui/App.java
index 1e505dba721037d8c75ece583c9d2b1aa64fa4af..8f4d07db91121a6ed4a820c8337ba27e67b79a41 100644
--- a/src/main/java/lab/gui/App.java
+++ b/src/main/java/lab/gui/App.java
@@ -3,6 +3,9 @@ package lab.gui;
 import java.io.IOException;
 import java.net.URL;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
 import javafx.application.Application;
 import javafx.fxml.FXMLLoader;
 import javafx.scene.Parent;
@@ -19,12 +22,14 @@ import lab.Setup;
  */
 public class App extends Application {
 
+	private static Logger log = LogManager.getLogger(App.class);
 	private GameController gameController;
 	
 	private Stage primaryStage;
 	
 	public static void main(String[] args) {
-		Setup.configure(new Setup());
+		log.info("Application lauched");
+		Setup.configure(Setup.getInstanceForHardcoreGame());
 		launch(args);
 	}
 
@@ -38,7 +43,7 @@ public class App extends Application {
 			// Exit program when main window is closed
 			primaryStage.setOnCloseRequest(this::exitProgram);
 		} catch (Exception e) {
-			e.printStackTrace();
+			log.error("Error during game play.", e);
 		}
 	}
 
@@ -71,9 +76,11 @@ public class App extends Application {
 			gameController.stop();
 		}
 		super.stop();
+		log.info("Gamne stoped");
 	}
 
 	private void exitProgram(WindowEvent evt) {
+		log.info("Exiting game");
 		System.exit(0);
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/lab/gui/GameController.java b/src/main/java/lab/gui/GameController.java
index 3c3ea447bbc2baf7ceb91d8be2050b47d11b3084..d1666cf9c2c855691e4ace4c43ef96e74cb408eb 100644
--- a/src/main/java/lab/gui/GameController.java
+++ b/src/main/java/lab/gui/GameController.java
@@ -2,6 +2,9 @@ package lab.gui;
 
 import java.util.List;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import javafx.event.ActionEvent;
@@ -20,6 +23,8 @@ import lab.game.Scene;
 
 public class GameController {
 
+	private static Logger log = LogManager.getLogger(GameController.class);
+	
 	private Scene scene;
 
     @FXML
@@ -101,19 +106,19 @@ public class GameController {
 		assert boatPosition != null : "fx:id=\"angle\" was not injected: check your FXML file 'gameWindow.fxml'.";
 		assert canvas != null : "fx:id=\"canvas\" was not injected: check your FXML file 'gameWindow.fxml'.";
 		assert speed != null : "fx:id=\"speed\" was not injected: check your FXML file 'gameWindow.fxml'.";
-		boatPosition.valueProperty().addListener(new ChangeListener<Number>() {
+		boatPosition.valueProperty().addListener(new ChangeListener<>() {
 			@Override
 			public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
 				scene.getBoat().setPosInPercentage(newValue.doubleValue());
 			}
 		});
-		speed.valueProperty().addListener(new ChangeListener<Number>() {
+		speed.valueProperty().addListener(new ChangeListener<>() {
 			@Override
 			public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
 				scene.getBackground().setSpeed(newValue.doubleValue());
 			}
 		});
-
+		log.info("Screeen initialized.");
 	}
 
 	public void startGame(String name, int numberOfMonsters) {
diff --git a/src/main/java/lab/gui/MainScreenController.java b/src/main/java/lab/gui/MainScreenController.java
index f5ca94a43ca50b184946b320aa90c33db6085103..8ac4454f1c823e1a32850dffc33bee8d806ed1a9 100644
--- a/src/main/java/lab/gui/MainScreenController.java
+++ b/src/main/java/lab/gui/MainScreenController.java
@@ -3,6 +3,9 @@ package lab.gui;
 import java.io.IOException;
 import java.util.List;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.scene.control.Button;
@@ -21,6 +24,8 @@ import lab.game.Difficult;
  */
 public class MainScreenController {
 
+	private static Logger log = LogManager.getLogger(MainScreenController.class);
+
 	@FXML
 	private Button btnGenerateScore;
 
@@ -100,6 +105,7 @@ public class MainScreenController {
 		pointsColumn.setCellValueFactory(new PropertyValueFactory<>("points"));
 
 		initDB();
+		log.info("Screeen initialized.");
 	}
 
 	private void initDB() {
diff --git a/src/main/java/lab/storage/DbConnector.java b/src/main/java/lab/storage/DbConnector.java
index 73cbefac399e59111cf39d0bb7271dc9423b5a1c..4c30cada3cba160d146286bb60b37d6e4d5f8928 100644
--- a/src/main/java/lab/storage/DbConnector.java
+++ b/src/main/java/lab/storage/DbConnector.java
@@ -9,11 +9,17 @@ import java.sql.Statement;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
 import lab.data.Score;
+import lab.gui.GameController;
 
 
 public class DbConnector implements ScoreStorageInterface {
 
+	private static Logger log = LogManager.getLogger(DbConnector.class);
+
 	private static final String JDBC_CONECTIN_STRING = "jdbc:h2:file:./scoreDB";
 
 	@Override
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 07daa1f118ce972c6e6075932e19752e07953324..fdf26b8382571ab72a45d6f419fd92329a145896 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,8 +1,12 @@
 module lab02_module {
-    requires transitive javafx.controls;
-    requires javafx.fxml;
-    requires javafx.base;
-    requires java.sql;
-    opens lab.gui to javafx.fxml;
-    exports lab.gui to javafx.fxml,javafx.graphics;
+	requires transitive javafx.controls;
+	requires javafx.fxml;
+	requires javafx.base;
+	requires java.sql;
+	requires org.apache.logging.log4j;
+
+	opens lab.gui to javafx.fxml;
+	opens lab.data to javafx.base;
+
+	exports lab.gui to javafx.fxml, javafx.graphics;
 }
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000000000000000000000000000000000..da032d36b62811a46f0edf9bd0e5d3aa4cb66e58
--- /dev/null
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration xmlns="https://logging.apache.org/xml/ns"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="
+                   https://logging.apache.org/xml/ns
+                   https://logging.apache.org/xml/ns/log4j-config-2.xsd">
+	<Appenders>
+		<Console name="Console">
+			<PatternLayout
+				pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
+		</Console>
+		<RollingFile name="File" fileName="logs/app.log"
+			filePattern="logs/app.%d{yyyy-MM-dd}.%i.log.gz">
+			<PatternLayout
+				pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
+			<DefaultRolloverStrategy max="5">
+				<Delete basePath="logs">
+					<IfAccumulatedFileSize  exceeds="2.2M" />
+				</Delete>
+			</DefaultRolloverStrategy>
+			<Policies>
+				<OnStartupTriggeringPolicy />
+				<SizeBasedTriggeringPolicy size="10M" />
+				<TimeBasedTriggeringPolicy interval="1" />
+			</Policies>
+		</RollingFile>
+	</Appenders>
+	<Loggers>
+		<Root level="INFO">
+			<AppenderRef ref="Console" level="INFO"/>
+			<AppenderRef ref="File" />
+		</Root>
+		<Logger name="lab.game" level="TRACE">
+		</Logger>
+	</Loggers>
+</Configuration>
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 0000000000000000000000000000000000000000..8eb250be7bf4933fb898098380bfe3b87cec6ffe
--- /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 0000000000000000000000000000000000000000..7e1f9b78a7f8f5039333470143b91954e77ef9fc
--- /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 6efa52cae44b0468da62712e44556ca3728d9b9d..7eb5fb71569deb6664f436b92e6c17a26bec57ce 100644
--- a/src/test/java/jez04/structure/test/ClassStructureTest.java
+++ b/src/test/java/jez04/structure/test/ClassStructureTest.java
@@ -22,116 +22,6 @@ import org.junit.jupiter.api.Test;
 
 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 personNextBirthdayTest() 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);
-		LocalDate bod =LocalDate.now().plusDays(338).minusYears(10);
-		Object person = parsePerson.invoke(null, "{\"id\":1,\"firstname\":\"Tatum\",\"lastname\":\"Block\",\"email\":\"lonnie.bergstrom@stoltenberg.com\",\"phone\":\"+12397191764\",\"birthday\":\""+ bod.format(DateTimeFormatter.ISO_DATE) +"\",\"gender\":\"male\",\"address\":{");
-		assertThat(person, notNullValue());
-
-		Method daysM = helper.getMethod(p, ".*days.*", false, long.class);
-		long days= (long)daysM.invoke(person);
-		assertEquals(338, days , "Calculate wrong days to next birthday.");
-	}
+	StructureHelper helper = StructureHelper.getInstance();
 
 }
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 0000000000000000000000000000000000000000..5c7952997243af56384183f1ad7b9a784bd82cbb
--- /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 0000000000000000000000000000000000000000..142fec8b77806805f8560a89d83a64d9fb788809
--- /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 0000000000000000000000000000000000000000..0e7098dfaccbfef827f8abe1ea02c5bd09c5d855
--- /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 0000000000000000000000000000000000000000..a202faa8359dd596d8b8ab9fc8dac8426a66967f
--- /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 0000000000000000000000000000000000000000..2abdee29266beb46899bf441eb75b6b8adef861c
--- /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 0000000000000000000000000000000000000000..e7d92772860242e62e7a508e6813cf5d04de06cc
--- /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 0000000000000000000000000000000000000000..4ed7d6c3e0250f75646317233c2f39e74881ec75
--- /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 6c18100e5074708094aab4e9a0a4099e43a13260..6ccabb4d3b2e307664acba8933c2ae35cfb83175 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 0000000000000000000000000000000000000000..6b8bc25eb09606250e61ed18e2955a96c02692d1
--- /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();
+		
+}