From 3f72c4a35759bcd337ad8a43d7de27bde321a343 Mon Sep 17 00:00:00 2001 From: jez04 <david.jezek@post.cz> Date: Fri, 28 Feb 2025 14:47:25 +0100 Subject: [PATCH] feat: :tada: all solved --- .gitignore | 16 + build-and-run-all.sh | 44 ++ pom.xml | 200 ++++++ run.sh | 3 + src/main/java/cz/vsb/fei/App.java | 104 +++ src/main/java/module-info.java | 5 + src/main/resources/log4j2.xml | 13 + src/test/java/cz/vsb/fei/AppTest.java | 19 + .../jez04/structure/test/AllOfContinue.java | 39 ++ .../java/jez04/structure/test/ClassExist.java | 44 ++ .../structure/test/ClassStructureTest.java | 110 ++++ .../structure/test/ContainsInnerClasses.java | 70 ++ .../java/jez04/structure/test/HasMethod.java | 95 +++ .../jez04/structure/test/HasProperty.java | 91 +++ .../jez04/structure/test/IsDescendatOf.java | 24 + .../jez04/structure/test/IsInterface.java | 23 + .../jez04/structure/test/ProjectContains.java | 105 +++ .../structure/test/ResourceContains.java | 102 +++ .../jez04/structure/test/SrcContains.java | 54 ++ .../jez04/structure/test/StructureHelper.java | 599 ++++++++++++++++++ .../structure/test/StructureMatcher.java | 7 + 21 files changed, 1767 insertions(+) create mode 100644 .gitignore create mode 100755 build-and-run-all.sh create mode 100644 pom.xml create mode 100755 run.sh create mode 100644 src/main/java/cz/vsb/fei/App.java create mode 100644 src/main/java/module-info.java create mode 100644 src/main/resources/log4j2.xml create mode 100644 src/test/java/cz/vsb/fei/AppTest.java create mode 100644 src/test/java/jez04/structure/test/AllOfContinue.java create mode 100644 src/test/java/jez04/structure/test/ClassExist.java create mode 100644 src/test/java/jez04/structure/test/ClassStructureTest.java create mode 100644 src/test/java/jez04/structure/test/ContainsInnerClasses.java create mode 100644 src/test/java/jez04/structure/test/HasMethod.java create mode 100644 src/test/java/jez04/structure/test/HasProperty.java create mode 100644 src/test/java/jez04/structure/test/IsDescendatOf.java create mode 100644 src/test/java/jez04/structure/test/IsInterface.java create mode 100644 src/test/java/jez04/structure/test/ProjectContains.java create mode 100644 src/test/java/jez04/structure/test/ResourceContains.java create mode 100644 src/test/java/jez04/structure/test/SrcContains.java create mode 100644 src/test/java/jez04/structure/test/StructureHelper.java create mode 100644 src/test/java/jez04/structure/test/StructureMatcher.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98b99a5 --- /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 0000000..aa5e40b --- /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 0000000..506a5e9 --- /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 0000000..d3d8dae --- /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 0000000..d947779 --- /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 0000000..35ec52c --- /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 0000000..acb3514 --- /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 0000000..c03d346 --- /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 0000000..8eb250b --- /dev/null +++ b/src/test/java/jez04/structure/test/AllOfContinue.java @@ -0,0 +1,39 @@ +package jez04.structure.test; + +import java.util.Arrays; +import java.util.List; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +public class AllOfContinue<T> extends BaseMatcher<T> { + + private final List<Matcher<? super T>> matchers; + + @SafeVarargs + public AllOfContinue(Matcher<? super T> ... matchers) { + this(Arrays.asList(matchers)); + } + + public AllOfContinue(List<Matcher<? super T>> matchers) { + this.matchers = matchers; + } + + @Override + public boolean matches(Object o) { + for (Matcher<? super T> matcher : matchers) { + if (!matcher.matches(o)) { +// matcher.describeMismatch(o, mismatch); + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendList("(", " " + "and" + " ", ")", matchers); + } + +} diff --git a/src/test/java/jez04/structure/test/ClassExist.java b/src/test/java/jez04/structure/test/ClassExist.java new file mode 100644 index 0000000..7e1f9b7 --- /dev/null +++ b/src/test/java/jez04/structure/test/ClassExist.java @@ -0,0 +1,44 @@ +package jez04.structure.test; + +import org.hamcrest.Description; + +public class ClassExist extends StructureMatcher<String> { + + private String className; + private boolean useRegExp; + private boolean caseSensitive; + + public ClassExist(String className) { + this(className, true, false); + } + + public ClassExist(String className, boolean caseSensitive, boolean useRegExp) { + this.className = className; + this.useRegExp = useRegExp; + this.caseSensitive = caseSensitive; + } + + @Override + public boolean matches(Object actual) { + if (useRegExp) { + return structureHelper.getAllClasses().stream().anyMatch( + c -> caseSensitive ? c.matches(className) : c.toLowerCase().matches(className.toLowerCase())); + } else { + return structureHelper.getAllClasses().stream().anyMatch( + c -> caseSensitive ? c.endsWith(className) : c.toLowerCase().endsWith(className.toLowerCase())); + } + + } + + @Override + public void describeTo(Description description) { + description.appendText(String.format("class/interface with name '%s' comparsion params(%s %s) exists", className, + caseSensitive ? "" : "no case sensitive", useRegExp ? "using regexp" : "")); + } + + @Override + public void describeMismatch(Object item, Description description) { + description.appendValueList("no class match from:\n ", "\n ", "", structureHelper.getAllClasses()); + } + +} diff --git a/src/test/java/jez04/structure/test/ClassStructureTest.java b/src/test/java/jez04/structure/test/ClassStructureTest.java new file mode 100644 index 0000000..684bccf --- /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 0000000..5c79529 --- /dev/null +++ b/src/test/java/jez04/structure/test/ContainsInnerClasses.java @@ -0,0 +1,70 @@ +package jez04.structure.test; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.List; +import java.util.stream.Collectors; + +import org.hamcrest.Description; + +public class ContainsInnerClasses extends StructureMatcher<Class<?>> { + + private String methodNameRegexp; + private boolean caseSensitive = true; + private boolean useRegExp = false; + private int count = 1; + private List<Class<?>> params; + + public ContainsInnerClasses(String methodNameRegexp) { + this.methodNameRegexp = methodNameRegexp; + } + + public ContainsInnerClasses caseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + public ContainsInnerClasses count(int count) { + this.count = count; + return this; + } + + public ContainsInnerClasses useRegExp(boolean useRegExp) { + this.useRegExp = useRegExp; + return this; + } + + @Override + public boolean matches(Object actual) { + if (actual instanceof Class c) { + long lamdaCount = structureHelper.countMethodRegexp(c, "lambda\\$.*"); + long innerClassCount = structureHelper.countClassesRegexp(c.getCanonicalName()+"\\$.*"); + long methodRefCount = 0; + try { + methodRefCount = structureHelper.countMethodReference(c); + } catch (URISyntaxException | IOException e) { + System.out.println("Cannot count method references"); + e.printStackTrace(); + } + return lamdaCount + innerClassCount+methodRefCount >= count; + } + return false; + } + + @Override + public void describeTo(Description description) { + params.stream().map(Class::getName).collect(Collectors.joining(", ")); + description.appendText( + String.format("Class should have inner classses or lambdas name (regexp) of type %s %s %s with params types %s", + methodNameRegexp, caseSensitive ? "" : "ignore case", "")); + } + + @Override + public void describeMismatch(Object item, Description description) { + if (item instanceof Class c) { + description.appendValueList("no method match from:\n ", "\n ", "", c.getDeclaredMethods()); + } else { + description.appendText("mismatched item is not class type"); + } + } +} diff --git a/src/test/java/jez04/structure/test/HasMethod.java b/src/test/java/jez04/structure/test/HasMethod.java new file mode 100644 index 0000000..142fec8 --- /dev/null +++ b/src/test/java/jez04/structure/test/HasMethod.java @@ -0,0 +1,95 @@ +package jez04.structure.test; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.hamcrest.Description; + +public class HasMethod extends StructureMatcher<Class<?>> { + + private String methodNameRegexp; + private Class<?> returnType; + private boolean caseSensitive = true; + private boolean useRegExp = false; + private Boolean abstractTag = null; + private Boolean finalTag = null; + private int count = 1; + private List<Class<?>> params; + + public HasMethod(String methodNameRegexp, Class<?> returnType, Class<?>... params) { + this.methodNameRegexp = methodNameRegexp; + this.returnType = returnType; + this.params = List.of(params); + } + + public HasMethod caseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + public HasMethod abstractTag(Boolean abstractTag) { + this.abstractTag = abstractTag; + return this; + } + + public HasMethod finalTag(Boolean finalTag) { + this.finalTag = finalTag; + return this; + } + + public HasMethod count(int count) { + this.count = count; + return this; + } + + public HasMethod useRegExp(boolean useRegExp) { + this.useRegExp = useRegExp; + return this; + } + + @Override + public boolean matches(Object actual) { + if (actual instanceof Class c) { + List<Method> methods = Arrays.asList(c.getDeclaredMethods()); + Stream<Method> streamOfMethods; + if (useRegExp) { + streamOfMethods = methods.stream().filter(m -> caseSensitive ? m.getName().matches(methodNameRegexp) + : m.getName().toLowerCase().matches(methodNameRegexp.toLowerCase())); + + } else { + streamOfMethods = methods.stream().filter(m -> caseSensitive ? m.getName().endsWith(methodNameRegexp) + : m.getName().toLowerCase().endsWith(methodNameRegexp.toLowerCase())); + } + streamOfMethods = streamOfMethods + .filter(m -> returnType != null ? returnType.equals(m.getReturnType()) : true) + .filter(m -> finalTag != null ? Modifier.isAbstract(m.getModifiers()) == abstractTag.booleanValue() + : true) + .filter(m -> abstractTag != null ? Modifier.isFinal(m.getModifiers()) == finalTag.booleanValue() + : true); + long co = streamOfMethods.count(); + return co >= count; + } + return false; + } + + @Override + public void describeTo(Description description) { + params.stream().map(Class::getName).collect(Collectors.joining(", ")); + description.appendText( + String.format("Class should have method name (regexp) of type %s %s %s with params types %s", + returnType, methodNameRegexp, caseSensitive ? "" : "ignore case", "")); + } + + @Override + public void describeMismatch(Object item, Description description) { + if (item instanceof Class c) { + description.appendValueList("no method match from:\n ", "\n ", "", c.getDeclaredMethods()); + } else { + description.appendText("mismatched item is not class type"); + } + } +} diff --git a/src/test/java/jez04/structure/test/HasProperty.java b/src/test/java/jez04/structure/test/HasProperty.java new file mode 100644 index 0000000..0e7098d --- /dev/null +++ b/src/test/java/jez04/structure/test/HasProperty.java @@ -0,0 +1,91 @@ +package jez04.structure.test; + +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.hamcrest.Description; + +public class HasProperty extends StructureMatcher<Class<?>> { + + private String propertyNameRegexp; + private Class<?> type; + private Predicate<Type> genericTypeFilter; + private Predicate<Class<?>> typeFilter; + private Boolean array = false; + private boolean caseSensitive; + private Class<?> annotation = null; + private int count = 1; + + public HasProperty(String propertyNameRegexp, Class<?> type, Boolean array) { + this(propertyNameRegexp, type, array, true); + } + + public HasProperty(String propertyNameRegexp, Class<?> type, Boolean array, boolean caseSensitive) { + this.propertyNameRegexp = propertyNameRegexp; + this.type = type; + this.array = array; + this.caseSensitive = caseSensitive; + } + + public HasProperty annotation(Class<?> annotation) { + this.annotation = annotation; + return this; + } + + public HasProperty typeFilter(Predicate<Class<?>> typeFilter) { + this.typeFilter = typeFilter; + return this; + } + + public HasProperty genericTypeFilter(Predicate<Type> genericTypeFilter) { + this.genericTypeFilter = genericTypeFilter; + return this; + } + + public HasProperty count(int count) { + this.count = count; + return this; + } + + @Override + public boolean matches(Object actual) { + if (actual instanceof Class c) { + Stream<?> streamOfResults; + List<Field> fields = Arrays.asList(c.getDeclaredFields()); + Stream<Field> streamOfFields = fields.stream() + .filter(f -> caseSensitive ? f.getName().matches(propertyNameRegexp) + : f.getName().toLowerCase().matches(propertyNameRegexp.toLowerCase())) + .filter(f -> type != null ? f.getType().equals(type) : true) + .filter(f -> array != null ? f.getType().isAnnotation() == array.booleanValue() : true) + .filter(f -> genericTypeFilter != null ? genericTypeFilter.test(f.getGenericType()) : true) + .filter(f -> typeFilter != null ? typeFilter.test(f.getType()) : true); + streamOfResults = streamOfFields; + if (annotation != null) { + streamOfResults = streamOfFields.flatMap(f -> Arrays.asList(f.getAnnotations()).stream()) + .map(a -> a.annotationType()).filter(a -> a.equals(annotation)); + } + long actualCount = streamOfResults.count(); + return this.count <= actualCount; + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText(String.format("Class should have field of type %s%s with name match regexp '%s'%s", type, + array != null && array ? "[]" : "", propertyNameRegexp, caseSensitive ? "" : "ignore case")); + } + + @Override + public void describeMismatch(Object item, Description description) { + if (item instanceof Class c) { + description.appendValueList("none of", ", ", "match", c.getDeclaredFields()); + } else { + description.appendText("mismatched item is not class type"); + } + } +} diff --git a/src/test/java/jez04/structure/test/IsDescendatOf.java b/src/test/java/jez04/structure/test/IsDescendatOf.java new file mode 100644 index 0000000..a202faa --- /dev/null +++ b/src/test/java/jez04/structure/test/IsDescendatOf.java @@ -0,0 +1,24 @@ +package jez04.structure.test; + +import org.hamcrest.Description; + +public class IsDescendatOf extends StructureMatcher<Class<?>> { + + private String className; + public IsDescendatOf(String className) { + this.className = className; + } + @Override + public boolean matches(Object actual) { + if(actual instanceof Class c) { + return structureHelper.getClass(className).isAssignableFrom(c); + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText(String.format("cass shoud be descendant of %s", className)); + } + +} diff --git a/src/test/java/jez04/structure/test/IsInterface.java b/src/test/java/jez04/structure/test/IsInterface.java new file mode 100644 index 0000000..2abdee2 --- /dev/null +++ b/src/test/java/jez04/structure/test/IsInterface.java @@ -0,0 +1,23 @@ +package jez04.structure.test; + +import org.hamcrest.Description; + +public class IsInterface extends StructureMatcher<Class<?>> { + + public IsInterface() { + } + + @Override + public boolean matches(Object actual) { + if (actual instanceof Class c) { + return c.isInterface(); + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText(String.format("value should be interface")); + } + +} diff --git a/src/test/java/jez04/structure/test/ProjectContains.java b/src/test/java/jez04/structure/test/ProjectContains.java new file mode 100644 index 0000000..79df784 --- /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 0000000..e7d9277 --- /dev/null +++ b/src/test/java/jez04/structure/test/ResourceContains.java @@ -0,0 +1,102 @@ +package jez04.structure.test; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +import org.hamcrest.Description; + +public class ResourceContains extends StructureMatcher<String> { + private String regexp; + private boolean caseInsensitive; + private boolean srcFound; + private int count = 1; + + public ResourceContains(String regexp, boolean caseInsensitive) { + this.regexp = regexp; + this.caseInsensitive = caseInsensitive; + } + + public ResourceContains count(int count) { + this.count = count; + return this; + } + + @Override + public boolean matches(Object actual) { + srcFound = true; + List<Path> foundResources = new LinkedList<>(); + Pattern p; + if (caseInsensitive) { + p = Pattern.compile(regexp, Pattern.CASE_INSENSITIVE); + } else { + p = Pattern.compile(regexp); + } + try { + URL myClassUrl = StructureHelper.class.getResource(this.getClass().getSimpleName() + ".class"); + Path classRoot = Paths.get(myClassUrl.toURI()); + while (!"test-classes".equals(classRoot.getFileName().toString()) + && !"classes".equals(classRoot.getFileName().toString())) { + classRoot = classRoot.getParent(); + } + Path resourcesRoot = classRoot.getParent().getParent().resolve(Paths.get("src", "main", "resources")); + System.out.println("resources root: " + resourcesRoot); + Files.walkFileTree(resourcesRoot, new FileVisitor<Path>() { + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (p.matcher(file.getFileName().toString()).matches()) { + foundResources.add(file); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + }); + return foundResources.size() >= count; + } catch (URISyntaxException | IOException e) { + srcFound = false; + e.printStackTrace(); + return false; + } + } + + @Override + public void describeTo(Description description) { + description.appendText(String.format("Source code of class shoud contains regexp '%s'%s", regexp, + caseInsensitive ? " in case insensitive mode" : "")); + } + + @Override + public void describeMismatch(Object item, Description description) { + if (srcFound) { + description + .appendText(String.format("source code of class %s do not contains substring that match reg exp")); + } else { + description.appendText(String.format("source code of class %s was not found")); + } + } + +} diff --git a/src/test/java/jez04/structure/test/SrcContains.java b/src/test/java/jez04/structure/test/SrcContains.java new file mode 100644 index 0000000..4ed7d6c --- /dev/null +++ b/src/test/java/jez04/structure/test/SrcContains.java @@ -0,0 +1,54 @@ +package jez04.structure.test; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.regex.Pattern; + +import org.hamcrest.Description; + +public class SrcContains extends StructureMatcher<Class<?>> { + private String regexp; + private boolean caseInsensitive; + private boolean srcFound; + + public SrcContains(String regexp, boolean caseInsensitive) { + this.regexp = regexp; + this.caseInsensitive = caseInsensitive; + } + + @Override + public boolean matches(Object actual) { + srcFound = true; + if (actual instanceof Class c) { + Pattern p = Pattern.compile(regexp); + if (caseInsensitive) { + p = Pattern.compile(regexp, Pattern.CASE_INSENSITIVE); + } + try { + return p.matcher(structureHelper.getSourceCode(c)).find(); + } catch (URISyntaxException | IOException e) { + srcFound = false; + e.printStackTrace(); + return false; + } + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText(String.format("Source code of class shoud contains regexp '%s'%s", regexp, + caseInsensitive ? " in case insensitive mode" : "")); + } + + @Override + public void describeMismatch(Object item, Description description) { + if (srcFound) { + description + .appendText(String.format("source code of class %s do not contains substring that match reg exp", item)); + } else { + description.appendText(String.format("source code of class %s was not found")); + } + } + +} diff --git a/src/test/java/jez04/structure/test/StructureHelper.java b/src/test/java/jez04/structure/test/StructureHelper.java new file mode 100644 index 0000000..00af2e0 --- /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 0000000..6b8bc25 --- /dev/null +++ b/src/test/java/jez04/structure/test/StructureMatcher.java @@ -0,0 +1,7 @@ +package jez04.structure.test; + +public abstract class StructureMatcher<T> extends org.hamcrest.BaseMatcher<T>{ + + protected StructureHelper structureHelper = StructureHelper.getInstance(); + +} -- GitLab