diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..98b99a551e28559528e090cbfc676dcff9bd7def
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+# Eclipse
+.classpath
+.project
+.settings/
+ 
+# Intellij
+.idea/
+*.iml
+*.iws
+ 
+# Mac
+.DS_Store
+ 
+# Maven
+log/
+target/
diff --git a/build-and-run-all.sh b/build-and-run-all.sh
new file mode 100755
index 0000000000000000000000000000000000000000..aa5e40b54c52f69a3283e9a60e28d63431cfe6e3
--- /dev/null
+++ b/build-and-run-all.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+mvn clean package
+
+cd target
+tput rmam
+echo "============================================================================================================================="
+echo "==== Run app unisng fat JAR ================================================================================================="
+echo "============================================================================================================================="
+java -jar java2-lab01-v2-1.0-SNAPSHOT-jar-with-dependencies.jar -demo
+java -jar java2-lab01-v2-1.0-SNAPSHOT-jar-with-dependencies.jar -fonts
+java -jar java2-lab01-v2-1.0-SNAPSHOT-jar-with-dependencies.jar
+java -jar java2-lab01-v2-1.0-SNAPSHOT-jar-with-dependencies.jar -text "Hi, Java DEELOPER!"
+java -jar java2-lab01-v2-1.0-SNAPSHOT-jar-with-dependencies.jar -help
+java -jar java2-lab01-v2-1.0-SNAPSHOT-jar-with-dependencies.jar -font "Pagga" -text "Font Pagga with FAT JAR" 
+java -jar java2-lab01-v2-1.0-SNAPSHOT-jar-with-dependencies.jar -cli 
+
+echo "============================================================================================================================="
+echo "==== Run app unisng JAR with LIBS ==========================================================================================="
+echo "============================================================================================================================="
+java -jar java2-lab01-v2-1.0-SNAPSHOT.jar
+java -jar java2-lab01-v2-1.0-SNAPSHOT.jar -cli
+
+echo "============================================================================================================================="
+echo "==== Run app unisng JAR with LIBS using Java Modules ========================================================================"
+echo "============================================================================================================================="
+java --module-path java2-lab01-v2-1.0-SNAPSHOT.jar:libs -m cz.vsb.fei/cz.vsb.fei.App -font "Pagga" -text "Hi, Java DEELOPER!"
+java --module-path java2-lab01-v2-1.0-SNAPSHOT.jar:libs -m cz.vsb.fei/cz.vsb.fei.App -cli
+
+echo "============================================================================================================================="
+echo "==== Run app unisng JLink ==================================================================================================="
+echo "============================================================================================================================="
+echo "Expanding JLink ..."
+unzip -qod jlink java2-lab01-v2-1.0-SNAPSHOT-jlink.zip
+echo "                ... JLink expanded"
+
+jlink/bin/java -m cz.vsb.fei/cz.vsb.fei.App -font "Pagga" -text "Hi, Java DEELOPER!"
+jlink/bin/java -m cz.vsb.fei/cz.vsb.fei.App -cli
+
+echo "Removing expanded Jlink"
+rm -Rf jlink
+
+tput smam
+cd ..
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..506a5e9e077b5282264cc369f2a723d7ca6606cc
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><project xmlns="http://maven.apache.org/POM/4.0.0" 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>cz.vsb.fei</groupId>
+    <artifactId>java2-lab01-v2</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <name>java2-lab01-v2</name>
+
+    <packaging>jar</packaging>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.release>21</maven.compiler.release>
+        <JUnit.version>5.11.0</JUnit.version>
+        <log4j.version>2.23.1</log4j.version>
+        <lombok.version>1.18.34</lombok.version>
+    </properties>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.junit</groupId>
+                <artifactId>junit-bom</artifactId>
+                <version>${JUnit.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <version>${log4j.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-api</artifactId>
+            <version>${log4j.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>${lombok.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>cz.vsb.fei.java2</groupId>
+            <artifactId>lab01-text2asciiart</artifactId>
+            <version>1.0.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <version>0.10.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest</artifactId>
+            <version>3.0</version>
+            <scope>test</scope>
+        </dependency>
+    <dependency><groupId>org.reflections</groupId><artifactId>reflections</artifactId><version>0.10.2</version><scope>test</scope></dependency><dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest</artifactId><version>3.0</version><scope>test</scope></dependency></dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <!--Plugin is used by default, bud we need specify special configuration-->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.13.0</version>
+                <configuration>
+                    <!--Add support for Lombok-->
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>${lombok.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>3.5.0</version>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.6.0</version>
+
+                <configuration>
+                    <!--Set behavior of plugin, which archives should be created see https://maven.apache.org/plugins/maven-assembly-plugin/descriptor-refs.html-->
+                    <descriptorRefs>
+                        <!--Create FAT jar-->
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                    <archive>
+                        <!--Automatically create and add file MANIFEST.MF into jar and set specified properties-->
+                        <manifest>
+                            <mainClass>cz.vsb.fei.App</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+
+                <executions>
+                    <!-- Setup that goal single should be automatically executed during phase package-->
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>3.8.1</version>
+                <executions>
+                    <!-- Setup that goal copy-dependencies should be automatically executed during phase package-->
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <!--Specify where should be dependencies (libraries) copied-->
+                            <outputDirectory>${project.build.directory}/libs</outputDirectory>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>3.4.2</version>
+                <configuration>
+                    <archive>
+                        <!--Automatically create and add file MANIFEST.MF into jar and set specified additional properties-->
+                        <manifestEntries>
+                            <Name>cz/vsb/fei/App</Name>
+                            <Implementation-Build-Date>${maven.build.timestamp}</Implementation-Build-Date>
+                            <Implementation-Title>${project.groupId}.${project.artifactId}</Implementation-Title>
+                            <Implementation-Vendor>VĹ B FEI Departmet of Computer Science</Implementation-Vendor>
+                            <Implementation-Version>${project.version}</Implementation-Version>
+                        </manifestEntries>
+                        <!--Automatically create and add file MANIFEST.MF into jar and set specified properties-->
+                        <manifest>
+                            <addClasspath>true</addClasspath>
+                            <classpathPrefix>libs/</classpathPrefix>
+                            <mainClass>cz.vsb.fei.App</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <!--Plugin create customized version of JDK together with your application which can be
+                distributed to any computer with same platform where you build this Jlink-->
+                <artifactId>maven-jlink-plugin</artifactId>
+                <version>3.2.0</version>
+                <extensions>true</extensions>
+                <executions>
+                    <!-- Setup that goal jlink should be automatically executed during phase package-->
+                    <execution>
+                        <id>create-jlink-image</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>jlink</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <!--Set classifier name to change suffix of generated file, otherwise module finish with error-->
+                    <classifier>jlink</classifier>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <repositories>
+        <repository>
+            <id>vsb-education</id>
+            <url>https://artifactory.cs.vsb.cz/repository/education-releases/</url>
+        </repository>
+        <repository>
+            <id>sci-java-public</id>
+            <url>https://maven.scijava.org/content/groups/public/</url>
+        </repository>
+    </repositories>
+</project>
\ No newline at end of file
diff --git a/run.sh b/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d3d8dae78b416573a92649b40a40906335ac1ebb
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+java --module-path target/java2-lab01-v2-1.0-SNAPSHOT.jar:target/libs -m cz.vsb.fei/cz.vsb.fei.App "$@"
\ No newline at end of file
diff --git a/src/main/java/cz/vsb/fei/App.java b/src/main/java/cz/vsb/fei/App.java
new file mode 100644
index 0000000000000000000000000000000000000000..d947779bdc0fb63df59c5dba9b2b27f2ab08f905
--- /dev/null
+++ b/src/main/java/cz/vsb/fei/App.java
@@ -0,0 +1,104 @@
+package cz.vsb.fei;
+
+import cz.vsb.fei.java2.lab01text2asciiart.ConversionException;
+import cz.vsb.fei.java2.lab01text2asciiart.Text2AsciiArt;
+import lombok.extern.log4j.Log4j2;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.Scanner;
+
+/**
+ * Class <b>App</b> - main class
+ * 
+ * @author Java I
+ */
+@Log4j2
+public class App {
+
+	public static void main(String[] args) {
+		log.info("Launching Java application.");
+		log.info("All args: {}", () -> Arrays.toString(args));
+		List<String> argsList = Arrays.asList(args);
+		String text = null;
+		String fontName = null;
+		boolean cliFlag = false;
+		Text2AsciiArt asciiArt = new Text2AsciiArt();
+		if (argsList.contains("-help")) {
+			printHelp();
+		}
+		if (argsList.contains("-fonts")) {
+			System.out.println("Available font names:");
+			asciiArt.getAllfontNames().forEach(System.out::println);
+		}
+		fontName = getArgValue(argsList, "-font");
+		text = getArgValue(argsList, "-text");
+		if (text == null && argsList.contains("-cli")) {
+			cliFlag = true;
+		}
+		if (argsList.contains("-cli")) {
+			cliFlag = true;
+		}
+		if (argsList.contains("-demo")) {
+			for (String font : asciiArt.getAllfontNames()) {
+				System.out.println("Font name: " + font);
+				System.out.println("======================================================== Font Example ==================================================================");
+				try {
+					System.out.println(asciiArt.convert("Hello: ABCD - abcd _ 1234 +-*/", font));
+				} catch (ConversionException e) {
+					e.printStackTrace();
+				}
+				System.out.println("========================================================================================================================================");
+			}
+		}
+		if(argsList.isEmpty()) {
+			printHelp();
+			System.out.println("No parameter supplied! Using default text and random font.");
+			List<String> fontNames = asciiArt.getAllfontNames();
+			fontName = fontNames.get(new Random().nextInt(fontNames.size()));
+			System.out.println("Choosed font name: " + fontName);
+			text = "Hello user " + System.getProperty("user.name");
+		}
+		if (cliFlag) {
+			System.out.println("Type text to convert:");
+			try (Scanner scanner = new Scanner(System.in)) {
+				text = scanner.nextLine();
+			}
+		}
+		if(text == null || text.isEmpty() || text.isBlank()){
+			text = "Empty text supplied!";
+		}
+		try {
+			if (fontName != null) {
+				System.out.println(asciiArt.convert(text, fontName));
+			} else {
+				System.out.println(asciiArt.convert(text));
+			}
+		} catch (ConversionException e) {
+			e.printStackTrace();
+		}
+
+	}
+
+	private static String getArgValue(List<String> argsList, String argName) {
+		if (argsList.contains(argName)) {
+			int fontIndex = argsList.indexOf(argName) + 1;
+			if (fontIndex < argsList.size()) {
+				return argsList.get(fontIndex);
+			} else {
+				System.out.println("No font name supplied! Use default font.");
+			}
+		}
+		return null;
+	}
+
+	private static void printHelp() {
+		System.out.println("Use params:   -text custom_text [-font font_name]");
+		System.out.println("              -cli [-font font_name]");
+		System.out.println("              -fonts");
+		System.out.println("              -help");
+		System.out.println("              -demo");
+	}
+
+}
\ No newline at end of file
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
new file mode 100644
index 0000000000000000000000000000000000000000..35ec52cc194a58ee96682a5337412edf8785b391
--- /dev/null
+++ b/src/main/java/module-info.java
@@ -0,0 +1,5 @@
+module cz.vsb.fei {
+	requires static lombok;
+	requires org.apache.logging.log4j;
+    requires cz.vsb.fei.java2.lab01text2asciiart;
+}
\ No newline at end of file
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000000000000000000000000000000000..acb3514078f6fb73f4f09ffd7a172b47f184e961
--- /dev/null
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +1,13 @@
+<Configuration>
+	<Appenders>
+		<Console name="Console">
+			<PatternLayout
+				pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
+		</Console>
+	</Appenders>
+	<Loggers>
+		<Root level="info">
+			<AppenderRef ref="Console"></AppenderRef>
+		</Root>
+	</Loggers>
+</Configuration>
diff --git a/src/test/java/cz/vsb/fei/AppTest.java b/src/test/java/cz/vsb/fei/AppTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c03d3463222e3e0033d51845bf8a1c3c18a054c0
--- /dev/null
+++ b/src/test/java/cz/vsb/fei/AppTest.java
@@ -0,0 +1,19 @@
+package cz.vsb.fei;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit test for simple App.
+ */
+class AppTest {
+
+	/**
+	 * Rigorous Test :-)
+	 */
+	@Test
+	void shouldAnswerWithTrue() {
+		assertTrue(true);
+	}
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..684bccfc0313734392b307b3fa3da0c8bba4a61a
--- /dev/null
+++ b/src/test/java/jez04/structure/test/ClassStructureTest.java
@@ -0,0 +1,110 @@
+package jez04.structure.test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URISyntaxException;
+
+import org.junit.jupiter.api.Test;
+
+class ClassStructureTest {
+
+	StructureHelper helper = StructureHelper.getInstance();
+
+	@Test
+	void outputStringTest() throws InvocationTargetException, IllegalAccessException {
+		helper.classExist("App", false);
+		Class<?> app = helper.getClass("App", false);
+		Method main = helper.getMethod(app, "main", void.class, String[].class);
+		PrintStream original = System.out;
+		ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+		System.setOut(new PrintStream(dataStream));
+		try {
+			main.invoke(null, new Object[][]{new String[]{"-text", "Test Text"}});
+			assertNotEquals(0, dataStream.toByteArray().length, "No data written to output consola");
+			assertTrue(new String(dataStream.toByteArray()).split("\n").length > 5, "less then 5 lines writen to output consola");
+        } finally {
+			System.setOut(original);
+			try {
+				System.out.write(dataStream.toByteArray());
+				dataStream.close();
+			} catch (IOException e) {
+				/* ignore it */}
+		}
+	}
+
+	@Test
+	void inputStringTest() throws InvocationTargetException, IllegalAccessException {
+		helper.classExist("App", false);
+		Class<?> app = helper.getClass("App", false);
+		Method main = helper.getMethod(app, "main", void.class, String[].class);
+		PrintStream original = System.out;
+		ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+		System.setOut(new PrintStream(dataStream));
+		System.setIn(new ByteArrayInputStream("----------\na\n\n\n\n\n".getBytes()));
+		try {
+			System.setIn(new ByteArrayInputStream("----------\n".getBytes()));
+			main.invoke(null, new Object[][]{new String[]{"-cli"}});
+			System.setIn(new ByteArrayInputStream("----------\na\n\n\n\n\n".getBytes()));
+			main.invoke(null, new Object[][]{new String[]{}});
+			assertNotEquals(0, dataStream.toByteArray().length, "No data written to output consola");
+			long count = new String(dataStream.toByteArray()).chars().filter(c -> c=='_').count();
+			assertTrue(count > 90, "String from consola (input stream) is not used");
+		} finally {
+			System.setOut(original);
+			try {
+				System.out.write(dataStream.toByteArray());
+				dataStream.close();
+			} catch (IOException e) {
+				/* ignore it */
+			}
+		}
+	}
+
+	@Test
+	void mavenAssemblyPluginTest() throws URISyntaxException, IOException {
+		String pom = helper.getProjectFile("pom.xml");
+		assertTrue(pom.contains("maven-assembly-plugin"), "No jlink used");
+		assertTrue(pom.contains("<manifest>"), "No jlink used");
+	}
+
+	@Test
+	void mavenDependencyPluginTest() throws URISyntaxException, IOException {
+		String pom = helper.getProjectFile("pom.xml");
+		assertTrue(pom.contains("maven-dependency-plugin"), "No jlink used");
+		assertTrue(pom.contains("copy-dependencies"), "No jlink used");
+		assertTrue(pom.contains("${project.build.directory}"), "No jlink used");
+	}
+	@Test
+	void mavenJarPluginTest() throws URISyntaxException, IOException {
+		String pom = helper.getProjectFile("pom.xml");
+		assertTrue(pom.contains("maven-jar-plugin"), "No jlink used");
+		assertTrue(pom.contains("manifestEntries"), "No jlink used");
+		assertTrue(pom.contains("addClasspath"), "No jlink used");
+		assertTrue(pom.contains("classpathPrefix"), "No jlink used");
+		assertFalse(pom.contains("--must be changed"), "Contains --must be changed");
+}
+
+	@Test
+	void mavenJlinkTest() throws URISyntaxException, IOException {
+		String pom = helper.getProjectFile("pom.xml");
+		assertTrue(pom.contains("maven-jlink-plugin"), "No jlink used");
+		assertTrue(pom.contains("create-jlink-image"), "No jlink used");
+	}
+
+	@Test
+	void mavenVsbLibTest() throws URISyntaxException, IOException {
+		String pom = helper.getProjectFile("pom.xml");
+		assertTrue(pom.contains("lab01-text2asciiart"), "No lab01-text2asciiart used");
+		assertTrue(pom.contains("repositories"), "No jlink used");
+		assertTrue(pom.contains("https://artifactory.cs.vsb.cz/repository"), "No jlink used");
+	}
+
+}
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/ProjectContains.java b/src/test/java/jez04/structure/test/ProjectContains.java
new file mode 100644
index 0000000000000000000000000000000000000000..79df7843a8736573f8820c5dd5f5eec085f18edc
--- /dev/null
+++ b/src/test/java/jez04/structure/test/ProjectContains.java
@@ -0,0 +1,105 @@
+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 ProjectContains extends StructureMatcher<String> {
+	private String regexp;
+	private boolean caseInsensitive;
+	private boolean fileFound;
+	private int count = 1;
+	private List<Path> foundFiles;
+
+	public ProjectContains(String regexp, boolean caseInsensitive) {
+		this.regexp = regexp;
+		this.caseInsensitive = caseInsensitive;
+	}
+
+	public ProjectContains count(int count) {
+		this.count = count;
+		return this;
+	}
+
+	@Override
+	public boolean matches(Object actual) {
+		fileFound = true;
+		foundFiles = 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 projectRoot = classRoot.getParent().getParent();
+			Files.walkFileTree(projectRoot, 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()) {
+						foundFiles.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 foundFiles.size() >= count;
+		} catch (URISyntaxException | IOException e) {
+			fileFound = 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 (fileFound) {
+			description.appendText(String.format("Project do not contains %d file match regexp: %s", count, regexp));
+		} else {
+			description.appendText(String.format("source code of class %s was not found"));
+		}
+	}
+
+	public List<Path> getFoundFiles() {
+		return foundFiles;
+	}
+
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..00af2e0d735e939f09d9c03187b04f0e6cecdf9a
--- /dev/null
+++ b/src/test/java/jez04/structure/test/StructureHelper.java
@@ -0,0 +1,599 @@
+package jez04.structure.test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import 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;
+import org.reflections.scanners.Scanners;
+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.");
+	}
+
+	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");
+	}
+
+	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");
+	}
+
+	public Class<?> getClassDirectly(String name) {
+		return loadClass(name, name);
+	}
+
+	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);
+		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);
+		if (className == null) {
+			Assertions.fail("Class " + name + " not found.");
+		}
+		return loadClass(name, className);
+	}
+
+	private Class<?> loadClass(String name, String className) {
+		try {
+			return Class.forName(className);
+		} catch (ClassNotFoundException e) {
+			final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+			try (PrintStream ps = new PrintStream(baos, true)) {
+				e.printStackTrace(ps);
+			} catch (Exception e2) {
+				Assertions.fail(e2.getMessage());
+			}
+			String stackTrace = baos.toString();
+			Assertions.fail("Class " + name + " not found.\n" + stackTrace);
+			return null;
+		}
+	}
+
+	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) {
+		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());
+		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 {
+					return f.getType().equals(type);
+				}
+			}
+			return false;
+		});
+	}
+
+	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) {
+		List<Field> fields = Arrays.asList(classDef.getDeclaredFields());
+		assertTrue(
+				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 "
+						+ classDef.getName());
+	}
+
+	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()))
+						.anyMatch(m -> m.getReturnType().equals(returnType)),
+				"Method " + methodName + " not return " + returnType.getName());
+	}
+
+	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) {
+		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()))
+						.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) {
+		return getMethod(interfaceDef, methodName, true, returnType, 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()))
+				.filter(m -> m.getReturnType().equals(returnType))
+				.filter(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))).toList();
+		if (foundMethods.isEmpty()) {
+			fail("No method " + methodName + " found");
+		}
+		if (foundMethods.size() > 1) {
+			fail("More then one method " + methodName + " found");
+		}
+		return foundMethods.get(0);
+	}
+
+	public long countMethodRegexp(Class<?> interfaceDef, String methodNameRegexp) {
+		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();
+	}
+
+	public long countMethodReference(Class<?> interfaceDef) throws URISyntaxException, IOException {
+		Pattern p = Pattern.compile("::");
+		Matcher m = p.matcher(getSourceCode(interfaceDef));
+		return m.results().count();
+	}
+
+	public long countMethodReferenceOn(Class<?> interfaceDef, String to) {
+		try {
+			Pattern p = Pattern.compile(to + "::");
+			Matcher m = p.matcher(getSourceCode(interfaceDef));
+			return m.results().count();
+		} catch (URISyntaxException | IOException e) {
+			e.printStackTrace();
+			return 0;
+		}
+	}
+
+	public long countClassesRegexp(String classNameRegexp) {
+		return 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();
+	}
+
+	public void hasConstructor(Class<?> classDef, Class<?>... params) {
+		getConstructor(classDef, params);
+	}
+
+	public Constructor<?> getConstructor(Class<?> classDef, Class<?>... params) {
+		List<Constructor<?>> constructors = Arrays.asList(classDef.getConstructors());
+		List<Constructor<?>> foundConstructors = constructors.stream()
+				.filter(m -> m.getParameterCount() == params.length)
+				.filter(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))).toList();
+		if (foundConstructors.isEmpty()) {
+			fail("No constructor found with parameters: "
+					+ Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", ")));
+		}
+		if (foundConstructors.size() > 1) {
+			fail("More then one constructor found with parameters: "
+					+ Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", ")));
+		}
+		return foundConstructors.get(0);
+	}
+
+	public void hasMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, Class<?> returnType,
+			Class<?>... params) {
+		hasMethodRegexp(interfaceDef, methodNameRegexp, true, returnType, 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())),
+				"No method " + methodNameRegexp);
+		assertTrue(
+				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) {
+		List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods());
+		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()))
+				.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) {
+		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()))
+						.filter(m -> m.getReturnType().equals(returnType)
+								&& (Modifier.isAbstract(m.getModifiers()) == abstractTag)
+								&& (Modifier.isFinal(m.getModifiers()) == finalTag))
+						.anyMatch(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))),
+				"Method " + methodName + " has no all params:"
+						+ 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));
+		assertTrue(Arrays.asList(clazz.getInterfaces()).containsAll(interfaces), "Class not implements all interfaces:"
+				+ interfaces.stream().map(Class::getName).collect(Collectors.joining(", ")));
+	}
+
+	public void hasExtends(Class<?> clazz, String parentName) {
+		Class<?> parent = getClass(parentName);
+		assertTrue(clazz.getSuperclass().equals(parent),
+				"Class " + clazz.getName() + " not extends class " + parentName);
+	}
+
+	public void hasExtends(Class<?> clazz, Class<?> parent) {
+		assertTrue(clazz.getSuperclass().equals(parent),
+				"Class " + clazz.getName() + " not extends class " + parent.getCanonicalName());
+	}
+
+	public void hasMethod(Class<?> interfaceDef, String methodName) {
+		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);
+	}
+
+	public String getSourceCode(Class<?> clazz) throws URISyntaxException, IOException {
+		URL myClassUrl = StructureHelper.class.getResource(this.getClass().getSimpleName() + ".class");
+		Path classRoot = Paths.get(myClassUrl.toURI());
+		while (!"test-classes".equals(classRoot.getFileName().toString())
+				&& !"classes".equals(classRoot.getFileName().toString())) {
+			classRoot = classRoot.getParent();
+		}
+		Path srcRoot = classRoot.getParent().getParent().resolve(Paths.get("src", "main", "java"));
+		System.out.println("class root: " + classRoot);
+		Path srcPath = srcRoot.resolve(clazz.getCanonicalName().replace(".", File.separator) + ".java");
+		return Files.readString(srcPath);
+	}
+
+	public String getResourceFile(String resourceFileName) throws URISyntaxException, IOException {
+		URL myClassUrl = StructureHelper.class.getResource(this.getClass().getSimpleName() + ".class");
+		Path classRoot = Paths.get(myClassUrl.toURI());
+		while (!"test-classes".equals(classRoot.getFileName().toString())
+				&& !"classes".equals(classRoot.getFileName().toString())) {
+			classRoot = classRoot.getParent();
+		}
+		Path resourceRoot = classRoot.getParent().getParent().resolve(Paths.get("src", "main", "resources"));
+		System.out.println("class root: " + classRoot);
+		Path srcPath = resourceRoot.resolve(resourceFileName);
+		return Files.readString(srcPath);
+	}
+
+	public String getProjectFile(String resourceFileName) throws URISyntaxException, IOException {
+		URL myClassUrl = StructureHelper.class.getResource(this.getClass().getSimpleName() + ".class");
+		Path classRoot = Paths.get(myClassUrl.toURI());
+		while (!"test-classes".equals(classRoot.getFileName().toString())
+				&& !"classes".equals(classRoot.getFileName().toString())) {
+			classRoot = classRoot.getParent();
+		}
+		Path projectRoot = classRoot.getParent().getParent();
+		System.out.println("project root: " + projectRoot);
+		Path srcPath = projectRoot.resolve(resourceFileName);
+		return Files.readString(srcPath);
+	}
+
+	public Set<String> getNameOfAllClasses() {
+		Set<String> allClassesName = new TreeSet<>();
+		dynamicalyFoundSomeClass(allClassesName);
+//		allClassesName.addAll(List.of("cz.vsb.fei.lab.App", "lab.Routines", "lab.App", "lab.DrawingThread"));
+		for (String className : allClassesName) {
+			try {
+				Class.forName(className);
+				break;
+			} catch (ClassNotFoundException e) {
+				System.out.println(String.format("Class '%s' cannot be loaded: %s", className, e.getMessage()));
+			}
+		}
+		for (Package p : Package.getPackages()) {
+			if (p.getName().startsWith("java.") || p.getName().startsWith("com.") || p.getName().startsWith("jdk.")
+					|| p.getName().startsWith("javafx.") || p.getName().startsWith("org.")
+					|| p.getName().startsWith("sun.") || p.getName().startsWith("javax.")
+					|| p.getName().startsWith("javassist")) {
+				continue;
+			}
+//			System.out.println(p.getName());
+			Configuration conf = new ConfigurationBuilder().addScanners(Scanners.SubTypes.filterResultsBy(pc -> true))
+					.forPackages(p.getName());
+			Reflections reflections = new Reflections(conf);
+			allClassesName.addAll(reflections.getAll(Scanners.SubTypes.filterResultsBy(c -> {
+//				System.out.println(">>> " + c);
+				return true;
+			})));
+		}
+		return allClassesName;
+	}
+
+	private static final List<String> dirsToSkip = List.of("jez04", "META-INF");
+	private static final List<String> filesToSkip = List.of("module-info.class");
+
+	public void dynamicalyFoundSomeClass(Set<String> allClassesName) {
+		URL myClassUrl = StructureHelper.class.getResource(this.getClass().getSimpleName() + ".class");
+		try {
+			Path classRoot = Paths.get(myClassUrl.toURI());
+			while (!"test-classes".equals(classRoot.getFileName().toString())
+					&& !"classes".equals(classRoot.getFileName().toString())) {
+				classRoot = classRoot.getParent();
+			}
+			if ("test-classes".equals(classRoot.getFileName().toString())) {
+				classRoot = classRoot.getParent().resolve("classes");
+			}
+//			System.out.println("class root: " + classRoot);
+			final Path classRootFinal = classRoot;
+			Files.walkFileTree(classRoot, new FileVisitor<Path>() {
+
+				@Override
+				public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+					if (dirsToSkip.contains(dir.getFileName().toString())) {
+						return FileVisitResult.SKIP_SUBTREE;
+					}
+					return FileVisitResult.CONTINUE;
+				}
+
+				@Override
+				public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+					if (filesToSkip.contains(file.getFileName().toString())) {
+						return FileVisitResult.CONTINUE;
+					}
+					if (!file.getFileName().toString().endsWith(".class")) {
+						return FileVisitResult.CONTINUE;
+					}
+					if (file.getFileName().toString().contains("$")) {
+						return FileVisitResult.CONTINUE;
+					}
+					String foundClassName = classRootFinal.relativize(file).toString();
+					foundClassName = foundClassName.substring(0, foundClassName.length() - 6)
+							.replace(File.separatorChar, '.');
+					addClassAndAllRef(allClassesName, foundClassName);
+					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;
+				}
+			});
+		} catch (URISyntaxException | IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	private void addClassAndAllRef(Set<String> allClassesName, String foundClassName) {
+		allClassesName.add(foundClassName);
+		try {
+			Class<?> foundClass = Class.forName(foundClassName);
+			List.of(foundClass.getInterfaces()).stream().map(Class::getCanonicalName).forEach(allClassesName::add);
+			List.of(foundClass.getDeclaredClasses()).stream().map(Class::getCanonicalName).forEach(allClassesName::add);
+			List.of(foundClass.getDeclaredFields()).stream().map(Field::getType)
+					.map(clazz -> clazz.isArray() ? clazz.componentType() : clazz)
+					.filter(Predicate.not(Class::isPrimitive)).map(Class::getCanonicalName)
+					.forEach(allClassesName::add);
+			List.of(foundClass.getDeclaredMethods()).stream().map(Method::getReturnType)
+					.map(clazz -> clazz.isArray() ? clazz.componentType() : clazz)
+					.filter(Predicate.not(Class::isPrimitive)).map(Class::getCanonicalName)
+					.forEach(allClassesName::add);
+			List.of(foundClass.getDeclaredMethods()).stream().flatMap(m -> List.of(m.getParameters()).stream())
+					.map(Parameter::getType).map(clazz -> clazz.isArray() ? clazz.componentType() : clazz)
+					.filter(Predicate.not(Class::isPrimitive)).map(Class::getCanonicalName)
+					.forEach(allClassesName::add);
+			List.of(foundClass.getDeclaredMethods()).stream().flatMap(m -> List.of(m.getExceptionTypes()).stream())
+					.map(clazz -> clazz.isArray() ? clazz.componentType() : clazz)
+					.filter(Predicate.not(Class::isPrimitive)).map(Class::getCanonicalName)
+					.forEach(allClassesName::add);
+			if (foundClass.getSuperclass() != null) {
+				allClassesName.add(foundClass.getSuperclass().getCanonicalName());
+			}
+		} catch (ClassNotFoundException e) {
+			e.printStackTrace();
+		}
+	}
+}
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();
+		
+}