diff --git a/.gitignore b/.gitignore index 6eeab52..9af2926 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,5 @@ hs_err_pid* # Project +cds/ recommendations.json diff --git a/cds.sh b/cds.sh new file mode 100755 index 0000000..90b81af --- /dev/null +++ b/cds.sh @@ -0,0 +1,23 @@ +rm -rf cds/ +mkdir cds + +printf "\n--- NO CDS ---\n\n" +printf ">> deactivating CDS\n" +./stats-time.sh java --enable-preview -Xlog:class+load:file=cds/off.log \ + -Xshare:off \ + -cp jars/genealogy.jar:jars/genealogists.jar org.codefx.java_after_eight.Main + +printf "\n--- JDK CDS ---\n\n" +printf ">> using default archive\n" +./stats-time.sh java --enable-preview -Xlog:class+load:file=cds/jdk.log \ + -cp jars/genealogy.jar:jars/genealogists.jar org.codefx.java_after_eight.Main + + +printf "\n--- APP CDS ---\n\n" +printf ">> building archive...\n" +java --enable-preview -XX:ArchiveClassesAtExit=cds/app.jsa \ + -cp jars/genealogy.jar:jars/genealogists.jar org.codefx.java_after_eight.Main +printf "\n>> using archive...\n" +./stats-time.sh java --enable-preview -Xlog:class+load:file=cds/app.log \ + -XX:SharedArchiveFile=cds/app.jsa \ + -cp jars/genealogy.jar:jars/genealogists.jar org.codefx.java_after_eight.Main diff --git a/genealogists/src/main/java/module-info.java b/genealogists/src/main/java/module-info.java new file mode 100644 index 0000000..b19a8ee --- /dev/null +++ b/genealogists/src/main/java/module-info.java @@ -0,0 +1,8 @@ +import org.codefx.java_after_eight.genealogist.GenealogistService; + +module org.codefx.java_after_eight.genealogists { + requires org.codefx.java_after_eight.genealogy; + + provides GenealogistService + with org.codefx.java_after_eight.genealogists.tags.TagGenealogistService; +} \ No newline at end of file diff --git a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/random/RandomGenealogist.java b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/random/RandomGenealogist.java index f315c81..b826f39 100644 --- a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/random/RandomGenealogist.java +++ b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/random/RandomGenealogist.java @@ -5,7 +5,7 @@ import org.codefx.java_after_eight.genealogist.TypedRelation; import org.codefx.java_after_eight.post.Post; -import java.util.Random; +import java.util.random.RandomGenerator; import static java.util.Objects.requireNonNull; @@ -13,16 +13,16 @@ public class RandomGenealogist implements Genealogist { private static final RelationType TYPE = new RelationType("random"); - private final Random random; + private final RandomGenerator random; - public RandomGenealogist(Random random) { + public RandomGenealogist(RandomGenerator random) { this.random = requireNonNull(random); } @Override public TypedRelation infer(Post post1, Post post2) { - long score = random.nextLong(); + long score = random.nextLong(101); return new TypedRelation(post1, post2, TYPE, score); } -} +} \ No newline at end of file diff --git a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/random/RandomGenealogistService.java b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/random/RandomGenealogistService.java index b796368..a5361eb 100644 --- a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/random/RandomGenealogistService.java +++ b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/random/RandomGenealogistService.java @@ -5,13 +5,20 @@ import org.codefx.java_after_eight.post.Post; import java.util.Collection; -import java.util.Random; +import java.util.random.RandomGenerator; public class RandomGenealogistService implements GenealogistService { @Override public Genealogist procure(Collection posts) { - Random random = new Random(); + var random = RandomGenerator.getDefault(); + // for something fancier: +// var random = RandomGeneratorFactory.all() +// .filter(RandomGeneratorFactory::isJumpable) +// .filter(factory -> factory.stateBits() > 64) +// .map(RandomGeneratorFactory::create) +// .findAny() +// .orElseThrow(); return new RandomGenealogist(random); } diff --git a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/repo/RepoGenealogist.java b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/repo/RepoGenealogist.java index dae9fd1..1ae53e8 100644 --- a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/repo/RepoGenealogist.java +++ b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/repo/RepoGenealogist.java @@ -22,23 +22,23 @@ public TypedRelation infer(Post post1, Post post2) { } private long determineScore(Post post1, Post post2) { - Optional repo1 = getRepository(post1); - Optional repo2 = getRepository(post2); + var repo1 = getRepository(post1); + var repo2 = getRepository(post2); if (repo1.isPresent() != repo2.isPresent()) return 0; // at this point, either both are empty or both are non-empty - if (!repo1.isPresent()) + if (repo1.isEmpty()) return 20; return Objects.equals(repo1, repo2) ? 100 : 50; } private Optional getRepository(Post post) { - if (post instanceof Article) - return ((Article) post).repository(); - if (post instanceof Video) - return ((Video) post).repository(); - return Optional.empty(); + return switch (post) { + case Article(var t, var ts, var d, var desc, var s, var repository, var c) -> repository; + case Video(var t, var ts, var d, var desc, var s, var v, var repository) -> repository; + default -> Optional.empty(); + }; } } diff --git a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/silly/SillyGenealogist.java b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/silly/SillyGenealogist.java index b37a2f4..d54554e 100644 --- a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/silly/SillyGenealogist.java +++ b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/silly/SillyGenealogist.java @@ -9,7 +9,7 @@ import java.util.Set; import static java.lang.Math.round; -import static java.util.stream.Collectors.toSet; +import static java.util.stream.Collectors.toUnmodifiableSet; public class SillyGenealogist implements Genealogist { @@ -17,9 +17,9 @@ public class SillyGenealogist implements Genealogist { @Override public TypedRelation infer(Post post1, Post post2) { - Set post1Letters = titleLetters(post1); - Set post2Letters = titleLetters(post2); - Set intersection = new HashSet<>(post1Letters); + var post1Letters = titleLetters(post1); + var post2Letters = titleLetters(post2); + var intersection = new HashSet<>(post1Letters); intersection.retainAll(post2Letters); long score = round((100.0 * intersection.size()) / post1Letters.size()); @@ -32,7 +32,7 @@ private static Set titleLetters(Post post) { .text() .toLowerCase() .chars().boxed() - .collect(toSet()); + .collect(toUnmodifiableSet()); } } diff --git a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/tags/TagGenealogist.java b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/tags/TagGenealogist.java index d88f715..bfc213d 100644 --- a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/tags/TagGenealogist.java +++ b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/tags/TagGenealogist.java @@ -4,12 +4,8 @@ import org.codefx.java_after_eight.genealogist.RelationType; import org.codefx.java_after_eight.genealogist.TypedRelation; import org.codefx.java_after_eight.post.Post; -import org.codefx.java_after_eight.post.Tag; - -import java.util.Set; import static java.lang.Math.round; -import static java.util.stream.Collectors.toSet; public class TagGenealogist implements Genealogist { @@ -17,12 +13,12 @@ public class TagGenealogist implements Genealogist { @Override public TypedRelation infer(Post post1, Post post2) { - Set post2Tags = post2.tags().collect(toSet()); + var post2Tags = post2.tags(); long numberOfSharedTags = post1 - .tags() + .tags().stream() .filter(post2Tags::contains) .count(); - long numberOfPost1Tags = post1.tags().count(); + long numberOfPost1Tags = post1.tags().size(); long score = round((100.0 * 2 * numberOfSharedTags) / (numberOfPost1Tags + post2Tags.size())); return new TypedRelation(post1, post2, TYPE, score); } diff --git a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/type/TypeGenealogist.java b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/type/TypeGenealogist.java index 47d1396..4a8cb5e 100644 --- a/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/type/TypeGenealogist.java +++ b/genealogists/src/main/java/org/codefx/java_after_eight/genealogists/type/TypeGenealogist.java @@ -3,7 +3,10 @@ import org.codefx.java_after_eight.genealogist.Genealogist; import org.codefx.java_after_eight.genealogist.RelationType; import org.codefx.java_after_eight.genealogist.TypedRelation; +import org.codefx.java_after_eight.post.Article; import org.codefx.java_after_eight.post.Post; +import org.codefx.java_after_eight.post.Talk; +import org.codefx.java_after_eight.post.Video; public class TypeGenealogist implements Genealogist { @@ -11,18 +14,11 @@ public class TypeGenealogist implements Genealogist { @Override public TypedRelation infer(Post post1, Post post2) { - long score = 0; - switch (post2.getClass().getSimpleName()) { - case "Article": - score = 50; - break; - case "Video": - score = 90; - break; - case "Talk": - score = 20; - break; - } + long score = switch (post2) { + case Article __ -> 50; + case Video __ -> 90; + case Talk __ -> 20; + }; return new TypedRelation(post1, post2, TYPE, score); } diff --git a/genealogy/pom.xml b/genealogy/pom.xml index dabe1c2..b10e50c 100644 --- a/genealogy/pom.xml +++ b/genealogy/pom.xml @@ -15,6 +15,19 @@ + + maven-surefire-plugin + + + --enable-preview + --add-opens=org.codefx.java_after_eight.genealogy/org.codefx.java_after_eight=ALL-UNNAMED + --add-opens=org.codefx.java_after_eight.genealogy/org.codefx.java_after_eight.post=ALL-UNNAMED + --add-opens=org.codefx.java_after_eight.genealogy/org.codefx.java_after_eight.post.factories=ALL-UNNAMED + --add-opens=org.codefx.java_after_eight.genealogy/org.codefx.java_after_eight.genealogy=ALL-UNNAMED + --add-opens=org.codefx.java_after_eight.genealogy/org.codefx.java_after_eight.recommendation=ALL-UNNAMED + + + maven-jar-plugin diff --git a/genealogy/src/demo/java/GenealogistServiceDemo.java b/genealogy/src/demo/java/GenealogistServiceDemo.java new file mode 100644 index 0000000..803548b --- /dev/null +++ b/genealogy/src/demo/java/GenealogistServiceDemo.java @@ -0,0 +1,27 @@ +import org.codefx.java_after_eight.genealogist.Genealogist; +import org.codefx.java_after_eight.genealogist.GenealogistService; +import org.codefx.java_after_eight.post.Post; + +import java.util.Collection; +import java.util.List; + +class Demo { + + public static void main(String[] args) { + // @start region="creation" + Collection posts = List.of(); + GenealogistService service = new SpecificGenealogistService(); + Genealogist genealogist = service.procure(posts); + // @end + } + + private static class SpecificGenealogistService implements GenealogistService { + + @Override + public Genealogist procure(Collection posts) { + throw new UnsupportedOperationException(); + } + + } + +} \ No newline at end of file diff --git a/genealogy/src/main/java/module-info.java b/genealogy/src/main/java/module-info.java new file mode 100644 index 0000000..96206d1 --- /dev/null +++ b/genealogy/src/main/java/module-info.java @@ -0,0 +1,10 @@ +import org.codefx.java_after_eight.genealogist.GenealogistService; + +module org.codefx.java_after_eight.genealogy { + requires jdk.incubator.concurrent; + + exports org.codefx.java_after_eight.post; + exports org.codefx.java_after_eight.genealogist; + + uses GenealogistService; +} \ No newline at end of file diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/Config.java b/genealogy/src/main/java/org/codefx/java_after_eight/Config.java index 6497191..30db2cb 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/Config.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/Config.java @@ -4,41 +4,45 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Optional; import java.util.concurrent.CompletableFuture; -public class Config { +public record Config( + Path articleFolder, + Path talkFolder, + Path videoFolder, + Optional outputFile) { private static final String CONFIG_FILE_NAME = "recommendations.config"; - private final Path articleFolder; - private final Path talkFolder; - private final Path videoFolder; - private final Optional outputFile; + // use static factory method(s) + @Deprecated + public Config { } - private Config(String[] raw) { + private static Config fromRawConfig(String[] raw) { if (raw.length == 0) throw new IllegalArgumentException("No article path defined."); - this.articleFolder = readFolder(raw[0]); - this.talkFolder = readFolder(raw[1]); - this.videoFolder = readFolder(raw[2]); + var articleFolder = readFolder(raw[0]); + var talkFolder = readFolder(raw[1]); + var videoFolder = readFolder(raw[2]); - Optional outputFile = raw.length >= 4 + Optional outputFileName = raw.length >= 4 ? Optional.of(raw[3]) : Optional.empty(); - this.outputFile = outputFile - .map(file -> Paths.get(System.getProperty("user.dir")).resolve(file)); - this.outputFile.ifPresent(file -> { + var outputFile = outputFileName + .map(file -> Path.of(System.getProperty("user.dir")).resolve(file)); + outputFile.ifPresent(file -> { boolean notWritable = Files.exists(file) && !Files.isWritable(file); if (notWritable) - throw new IllegalArgumentException("Output path is not writable: " + this.outputFile.get()); + throw new IllegalArgumentException("Output path is not writable: " + outputFile.get()); }); + + return new Config(articleFolder, talkFolder, videoFolder, outputFile); } private static Path readFolder(String raw) { - Path folder = Paths.get(raw); + var folder = Path.of(raw); if (!Files.exists(folder)) throw new IllegalArgumentException("Path doesn't exist: " + folder); if (!Files.isDirectory(folder)) @@ -46,47 +50,31 @@ private static Path readFolder(String raw) { return folder; } - public Path articleFolder() { - return articleFolder; - } - - public Path talkFolder() { - return talkFolder; - } - - public Path videoFolder() { - return videoFolder; - } - - public Optional outputFile() { - return outputFile; - } - public static CompletableFuture create(String[] args) { CompletableFuture rawConfig = args.length > 0 ? CompletableFuture.completedFuture(args) : readProjectConfig() - .exceptionally(__ -> readUserConfig().join()) - .exceptionally(__ -> new String[0]); + .exceptionallyComposeAsync(__ -> readUserConfig()) + .exceptionallyAsync(__ -> new String[0]); return rawConfig - .thenApply(Config::new); + .thenApply(Config::fromRawConfig); } private static CompletableFuture readProjectConfig() { - Path workingDir = Paths.get(System.getProperty("user.dir")).resolve(CONFIG_FILE_NAME); + var workingDir = Path.of(System.getProperty("user.dir")).resolve(CONFIG_FILE_NAME); return readConfig(workingDir); } private static CompletableFuture readUserConfig() { - Path workingDir = Paths.get(System.getProperty("user.home")).resolve(CONFIG_FILE_NAME); + var workingDir = Path.of(System.getProperty("user.home")).resolve(CONFIG_FILE_NAME); return readConfig(workingDir); } private static CompletableFuture readConfig(Path workingDir) { return CompletableFuture.supplyAsync(() -> { try { - return Files.readAllLines(workingDir).toArray(new String[0]); + return Files.readAllLines(workingDir).toArray(String[]::new); } catch (IOException ex) { throw new UncheckedIOException(ex); } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/Main.java b/genealogy/src/main/java/org/codefx/java_after_eight/Main.java index e9357f5..4611701 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/Main.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/Main.java @@ -1,9 +1,9 @@ package org.codefx.java_after_eight; +import jdk.incubator.concurrent.StructuredTaskScope; import org.codefx.java_after_eight.genealogist.Genealogist; import org.codefx.java_after_eight.genealogist.GenealogistService; import org.codefx.java_after_eight.genealogy.Genealogy; -import org.codefx.java_after_eight.genealogy.Relation; import org.codefx.java_after_eight.genealogy.Weights; import org.codefx.java_after_eight.post.Post; import org.codefx.java_after_eight.post.factories.ArticleFactory; @@ -18,37 +18,54 @@ import java.util.Collection; import java.util.List; import java.util.ServiceLoader; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.stream.Stream; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; -import static org.codefx.java_after_eight.Utils.concat; public class Main { public static void main(String[] args) { System.out.println(ProcessDetails.details()); - Config config = Config.create(args).join(); - Genealogy genealogy = createGenealogy(config.articleFolder(), config.talkFolder(), config.videoFolder()); - Recommender recommender = new Recommender(); + var config = Config.create(args).join(); + var genealogy = createGenealogy(config.articleFolder(), config.talkFolder(), config.videoFolder()); + var recommender = new Recommender(); - Stream relations = genealogy.inferRelations(); - Stream recommendations = recommender.recommend(relations, 3); - String recommendationsAsJson = recommendationsToJson(recommendations); + var relations = genealogy.inferRelations(); + var recommendations = recommender.recommend(relations, 3); + var recommendationsAsJson = recommendationsToJson(recommendations); - if (config.outputFile().isPresent()) - Utils.uncheckedFilesWrite(config.outputFile().get(), recommendationsAsJson); - else - System.out.println(recommendationsAsJson); + config.outputFile().ifPresentOrElse( + outputFile -> Utils.uncheckedFilesWrite(outputFile, recommendationsAsJson), + () -> System.out.println(recommendationsAsJson)); } private static Genealogy createGenealogy(Path articleFolder, Path talkFolder, Path videoFolder) { - List posts = concat( - markdownFilesIn(articleFolder).map(ArticleFactory::createArticle), - markdownFilesIn(talkFolder).map(TalkFactory::createTalk), - markdownFilesIn(videoFolder).map(VideoFactory::createVideo) - ).collect(toList()); + List> futurePosts = new ArrayList<>(); + + try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { + markdownFilesIn(articleFolder) + .map(file -> scope.fork(() -> ArticleFactory.createArticle(file))) + .forEach(futurePosts::add); + markdownFilesIn(talkFolder) + .map(file -> scope.fork(() -> TalkFactory.createTalk(file))) + .forEach(futurePosts::add); + markdownFilesIn(videoFolder) + .map(file -> scope.fork(() -> VideoFactory.createVideo(file))) + .forEach(futurePosts::add); + + scope.join(); + scope.throwIfFailed(); + } catch (ExecutionException | InterruptedException ex) { + // this is horrible error handling + throw new RuntimeException(ex); + } + + List posts = futurePosts.stream() + .map(Future::resultNow) + .toList(); Collection genealogists = getGenealogists(posts); return new Genealogy(posts, genealogists, Weights.allEqual()); } @@ -60,31 +77,37 @@ private static Stream markdownFilesIn(Path folder) { } private static Collection getGenealogists(Collection posts) { - List genealogists = new ArrayList<>(); - ServiceLoader - .load(GenealogistService.class) - .forEach(service -> genealogists.add(service.procure(posts))); + var genealogists = ServiceLoader + .load(GenealogistService.class).stream() + .map(ServiceLoader.Provider::get) + .map(service -> service.procure(posts)) + .toList(); if (genealogists.isEmpty()) throw new IllegalArgumentException("No genealogists found."); return genealogists; } private static String recommendationsToJson(Stream recommendations) { - String frame = "[\n$RECOMMENDATIONS\n]"; - String recommendation = "" + - "\t{" + - "\n\t\t\"title\": \"$TITLE\",\n" + - "\t\t\"recommendations\": [\n" + - "$RECOMMENDED_POSTS\n" + - "\t\t]\n" + - "\t}"; - String recommendedPost = "" + - "\t\t\t{ \"title\": \"$TITLE\" }"; - - String recs = recommendations + var frame = """ + [ + $RECOMMENDATIONS + ] + """; + var recommendation = """ + { + "title": "$TITLE", + "recommendations": [ + $RECOMMENDED_POSTS + ] + } + """; + var recommendedPost = """ + \t\t\t{ "title": "$TITLE" }"""; + + var recs = recommendations .map(rec -> { String posts = rec - .recommendedPosts() + .recommendedPosts().stream() .map(recArt -> recArt.title().text()) .map(recTitle -> recommendedPost.replace("$TITLE", recTitle)) .collect(joining(",\n")); diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/ProcessDetails.java b/genealogy/src/main/java/org/codefx/java_after_eight/ProcessDetails.java index a1b73ac..7323244 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/ProcessDetails.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/ProcessDetails.java @@ -1,107 +1,11 @@ package org.codefx.java_after_eight; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.management.ManagementFactory; -import java.lang.management.RuntimeMXBean; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Optional; - -import static java.lang.String.format; - public class ProcessDetails { public static String details() { - return format( - "Process ID: %s | Major Java version: %s", - getPid().map(Object::toString).orElse("unknown"), - getMajorJavaVersion().map(Object::toString).orElse("unknown")); - } - - public static Optional getPid() { - Optional pid = getPidFromMxBeanName(); - if (pid.isPresent()) - return pid; - pid = getPidFromMxBeanInternal(); - if (pid.isPresent()) - return pid; - pid = getPidFromProcSelfSymlink(); - if (pid.isPresent()) - return pid; - pid = getPidFromBashPid(); - if (pid.isPresent()) - return pid; - - return Optional.empty(); - } - - private static Optional getPidFromMxBeanName() { - // on many VMs `ManagementFactory.getRuntimeMXBean().getName()` - // returns something like 1234@localhost - String[] pidAndHost = ManagementFactory.getRuntimeMXBean().getName().split("@"); - try { - return Optional.of(Long.parseLong(pidAndHost[0])); - } catch (NumberFormatException ex) { - return Optional.empty(); - } - } - - private static Optional getPidFromMxBeanInternal() { - // crosses fingers - try { - RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); - Field jvm_field = runtime.getClass().getDeclaredField("jvm"); - jvm_field.setAccessible(true); - Object mgmt = jvm_field.get(runtime); - - Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId"); - pid_method.setAccessible(true); - int pid = (int) pid_method.invoke(mgmt); - - return Optional.of((long) pid); - } catch (ClassCastException | ReflectiveOperationException ex) { - return Optional.empty(); - } - } - - private static Optional getPidFromProcSelfSymlink() { - try { - String pid = new File("/proc/self").getCanonicalFile().getName(); - return Optional.of(Long.parseLong(pid)); - } catch (IOException | NumberFormatException ex) { - return Optional.empty(); - } - } - - private static Optional getPidFromBashPid() { - // if we're on Linux, this should work on all POSIX shells - try { - InputStream echoPid = new ProcessBuilder("sh", "-c", "echo $PPID").start().getInputStream(); - String pid = new BufferedReader(new InputStreamReader(echoPid)).readLine(); - return Optional.of(Long.parseLong(pid)); - } catch (IOException | NumberFormatException ex) { - return Optional.empty(); - } - } - - public static Optional getMajorJavaVersion() { - try { - String version = System.getProperty("java.version"); - if (version.startsWith("1.")) - return Optional.of(Integer.parseInt(version.substring(2, 3))); - - if (version.contains(".")) - return Optional.of(Integer.parseInt(version.split("\\.")[0])); - - // hail mary - return Optional.of(Integer.parseInt(version)); - } catch (NumberFormatException ex) { - return Optional.empty(); - } + return "Process ID: %s | Major Java version: %s".formatted( + ProcessHandle.current().pid(), + Runtime.version().major()); } } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/Utils.java b/genealogy/src/main/java/org/codefx/java_after_eight/Utils.java index 22f81d7..5908f0e 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/Utils.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/Utils.java @@ -2,10 +2,8 @@ import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -14,8 +12,6 @@ import java.util.stream.Collector; import java.util.stream.Stream; -import static java.lang.String.format; - public final class Utils { private Utils() { @@ -36,7 +32,7 @@ public static Stream uncheckedFilesList(Path dir) { public static void uncheckedFilesWrite(Path path, String content) { try { - Files.write(path, Arrays.asList(content), StandardCharsets.UTF_8); + Files.write(path, List.of(content)); } catch (IOException ex) { throw new UncheckedIOException(ex); } @@ -44,7 +40,7 @@ public static void uncheckedFilesWrite(Path path, String content) { public static List uncheckedFilesReadAllLines(Path file) { try { - return Files.readAllLines(file, StandardCharsets.UTF_8); + return Files.readAllLines(file); } catch (IOException ex) { throw new UncheckedIOException(ex); } @@ -52,7 +48,7 @@ public static List uncheckedFilesReadAllLines(Path file) { public static Stream uncheckedFilesLines(Path file) { try { - return Files.lines(file, StandardCharsets.UTF_8); + return Files.lines(file); } catch (IOException ex) { throw new UncheckedIOException(ex); } @@ -74,13 +70,13 @@ public static Stream concat(Stream... stre (AtomicReference left, ELEMENT right) -> { if (left.get() != null && !equals.test(left.get(), right)) throw new IllegalArgumentException( - format("Unequal elements in stream: %s vs %s", left.get(), right)); + "Unequal elements in stream: %s vs %s".formatted(left.get(), right)); left.set(right); }, (AtomicReference left, AtomicReference right) -> { if (left.get() != null && right.get() != null && !equals.test(left.get(), right.get())) throw new IllegalArgumentException( - format("Unequal elements in stream: %s vs %s", left.get(), right.get())); + "Unequal elements in stream: %s vs %s".formatted(left.get(), right.get())); return left.get() != null ? left : right; }, reference -> Optional.ofNullable(reference.get()) diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/GenealogistService.java b/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/GenealogistService.java index 7b8037f..2b4afb6 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/GenealogistService.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/GenealogistService.java @@ -8,13 +8,7 @@ * Used as a service to create {@link Genealogist}s - must have a public parameterless constructor. * *

Intended use: - *

- * {@code
- * Collection posts = // ... ;
- * GenealogistService service = new SpecificGenealogistService();
- * Genealogist genealogist = service.create(posts);
- * }
- * 
+ * {@snippet file="GenealogistServiceDemo.java" region="creation"} */ public interface GenealogistService { diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/RelationType.java b/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/RelationType.java index f1275b5..7efa809 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/RelationType.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/RelationType.java @@ -1,46 +1,16 @@ package org.codefx.java_after_eight.genealogist; -import java.util.Objects; - import static java.util.Objects.requireNonNull; -public class RelationType { +public record RelationType(String value) { // `RelationType` is a string (and not an enum) because {@code Genealogist} implementations // can be plugged in via services, which means their type is unknown at runtime. - private final String value; - - public RelationType(String value) { - this.value = requireNonNull(value); - if (value.isEmpty()) + public RelationType { + requireNonNull(value); + if (value.isBlank()) throw new IllegalArgumentException("Relation types can't have an empty value."); } - public String value() { - return value; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - RelationType slug = (RelationType) o; - return value.equals(slug.value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public String toString() { - return "RelationType{" + - "value='" + value + '\'' + - '}'; - } - } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/TypedRelation.java b/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/TypedRelation.java index daac09d..241f3e0 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/TypedRelation.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/genealogist/TypedRelation.java @@ -2,68 +2,20 @@ import org.codefx.java_after_eight.post.Post; -import java.util.Objects; - import static java.util.Objects.requireNonNull; -public class TypedRelation { - - private final Post post1; - private final Post post2; - private final RelationType type; - private final long score; +public record TypedRelation( + Post post1, + Post post2, + RelationType type, + long score) { - public TypedRelation(Post post1, Post post2, RelationType type, long score) { - this.post1 = requireNonNull(post1); - this.post2 = requireNonNull(post2); - this.type = requireNonNull(type); - this.score = score; + public TypedRelation { + requireNonNull(post1); + requireNonNull(post2); + requireNonNull(type); if (score < 0 || 100 < score) throw new IllegalArgumentException("Score should be in interval [0; 100]: " + score); } - public Post post1() { - return post1; - } - - public Post post2() { - return post2; - } - - public RelationType type() { - return type; - } - - public long score() { - return score; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - TypedRelation that = (TypedRelation) o; - return score == that.score && - post1.equals(that.post1) && - post2.equals(that.post2) && - type.equals(that.type); - } - - @Override - public int hashCode() { - return Objects.hash(post1, post2, type, score); - } - - @Override - public String toString() { - return "Relation{" + - "post1=" + post1.slug().value() + - ", post2=" + post2.slug().value() + - ", type='" + type + '\'' + - ", score=" + score + - '}'; - } - } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Genealogy.java b/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Genealogy.java index 1ed44f9..c9883bd 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Genealogy.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Genealogy.java @@ -41,6 +41,8 @@ private Stream aggregateTypedRelations(Stream typedRela } private Stream inferTypedRelations() { + record Posts(Post post1, Post post2) { } + record PostResearch(Genealogist genealogist, Posts posts) { } return posts.stream() .flatMap(post1 -> posts.stream() .map(post2 -> new Posts(post1, post2))) @@ -48,35 +50,8 @@ private Stream inferTypedRelations() { .filter(posts -> posts.post1 != posts.post2) .flatMap(posts -> genealogists.stream() .map(genealogist -> new PostResearch(genealogist, posts))) - .map(PostResearch::infer); - } - - private static class Posts { - - final Post post1; - final Post post2; - - Posts(Post post1, Post post2) { - this.post1 = post1; - this.post2 = post2; - } - - } - - private static class PostResearch { - - final Genealogist genealogist; - final Posts posts; - - PostResearch(Genealogist genealogist, Posts posts) { - this.genealogist = genealogist; - this.posts = posts; - } - - TypedRelation infer() { - return genealogist.infer(posts.post1, posts.post2); - } - + .map(research -> research.genealogist() + .infer(research.posts().post1(), research.posts().post2())); } } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Relation.java b/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Relation.java index 15ed9bd..baa336e 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Relation.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Relation.java @@ -3,104 +3,38 @@ import org.codefx.java_after_eight.genealogist.TypedRelation; import org.codefx.java_after_eight.post.Post; -import java.util.Objects; import java.util.stream.Stream; import static java.lang.Math.round; -import static java.lang.String.format; import static java.util.Objects.requireNonNull; - -public class Relation { - - private final Post post1; - private final Post post2; - private final long score; - - Relation(Post post1, Post post2, long score) { - this.post1 = requireNonNull(post1); - this.post2 = requireNonNull(post2); - this.score = score; - +import static java.util.stream.Collectors.averagingDouble; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.teeing; +import static org.codefx.java_after_eight.Utils.collectEqualElement; + +public record Relation( + Post post1, + Post post2, + long score) { + + public Relation { + requireNonNull(post1); + requireNonNull(post2); if (score < 0 || 100 < score) throw new IllegalArgumentException("Score should be in interval [0; 100]: " + toString()); } static Relation aggregate(Stream typedRelations, Weights weights) { - return typedRelations - .map(relation -> new UnfinishedRelation(relation, weights.weightOf(relation.type()))) - .reduce(UnfinishedRelation::fold) - .map(UnfinishedRelation::finish) + record Posts(Post post1, Post post2) { } + return typedRelations.collect( + teeing( + mapping( + rel -> new Posts(rel.post1(), rel.post2()), + collectEqualElement()), + averagingDouble(rel -> rel.score() * weights.weightOf(rel.type())), + (posts, score) -> posts.map(ps -> new Relation(ps.post1(), ps.post2(), round(score))) + )) .orElseThrow(() -> new IllegalArgumentException("Can't create relation from zero typed relations.")); } - public Post post1() { - return post1; - } - - public Post post2() { - return post2; - } - - public long score() { - return score; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Relation relation = (Relation) o; - return score == relation.score && - post1.equals(relation.post1) && - post2.equals(relation.post2); - } - - @Override - public int hashCode() { - return Objects.hash(post1, post2, score); - } - - @Override - public String toString() { - return "Relation{" + - "post1=" + post1.slug().value() + - ", post2=" + post2.slug().value() + - ", score=" + score + - '}'; - } - - private static class UnfinishedRelation { - - private final Post post1; - private final Post post2; - private double scoreTotal; - private long scoreCount; - - public UnfinishedRelation(TypedRelation relation, double weight) { - this.post1 = relation.post1(); - this.post2 = relation.post2(); - this.scoreTotal = relation.score() * weight; - this.scoreCount = 1; - } - - public UnfinishedRelation fold(UnfinishedRelation other) { - if (post1 != other.post1) - throw new IllegalArgumentException(format( - "All typed relations must belong to the same post: %s vs %s", post1, other.post1)); - if (post2 != other.post2) - throw new IllegalArgumentException(format( - "All typed relations must belong to the same post: %s vs %s", post2, other.post2)); - scoreTotal += other.scoreTotal; - scoreCount += other.scoreCount; - return this; - } - - public Relation finish() { - return new Relation(post1, post2, round(scoreTotal / scoreCount)); - } - - } - } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Weights.java b/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Weights.java index 17d5197..f5d433e 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Weights.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Weights.java @@ -2,7 +2,6 @@ import org.codefx.java_after_eight.genealogist.RelationType; -import java.util.HashMap; import java.util.Map; public class Weights { @@ -11,14 +10,12 @@ public class Weights { private final double defaultWeight; public Weights(Map weights, double defaultWeight) { - this.weights = new HashMap<>(weights); - if (this.weights.entrySet().stream().anyMatch(entry -> entry.getKey() == null || entry.getValue() == null)) - throw new NullPointerException("Neither relation type nor weight can be null."); + this.weights = Map.copyOf(weights); this.defaultWeight = defaultWeight; } public static Weights allEqual() { - return new Weights(new HashMap<>(), 1); + return new Weights(Map.of(), 1); } public double weightOf(RelationType genealogistType) { diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/post/Article.java b/genealogy/src/main/java/org/codefx/java_after_eight/post/Article.java index d7797b8..33f1ddf 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/post/Article.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/post/Article.java @@ -4,62 +4,31 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import static java.util.Objects.requireNonNull; -public class Article implements Post { +public record Article( + Title title, + Set tags, + LocalDate date, + Description description, + Slug slug, + Optional repository, + Content content) implements Post { - private final Title title; - private final Set tags; - private final LocalDate date; - private final Description description; - private final Slug slug; - - private final Optional repository; - private final Content content; - - public Article(Title title, Set tags, LocalDate date, Description description, Slug slug, Optional repository, Content content) { - this.title = requireNonNull(title); - this.tags = requireNonNull(tags); - this.date = requireNonNull(date); - this.description = requireNonNull(description); - this.slug = requireNonNull(slug); - this.repository = requireNonNull(repository); - this.content = requireNonNull(content); - } - - @Override - public Title title() { - return title; - } - - @Override - public Stream tags() { - return tags.stream(); - } - - @Override - public LocalDate date() { - return date; + public Article { + requireNonNull(title); + requireNonNull(tags); + requireNonNull(date); + requireNonNull(description); + requireNonNull(slug); + requireNonNull(repository); + requireNonNull(content); } @Override - public Description description() { - return description; - } - - @Override - public Slug slug() { - return slug; - } - - public Optional repository() { - return repository; - } - - public Content content() { - return content; + public Set tags() { + return Set.copyOf(tags); } @Override @@ -77,16 +46,4 @@ public int hashCode() { return Objects.hash(slug); } - @Override - public String toString() { - return "Article{" + - "title=" + title + - ", tags=" + tags + - ", date=" + date + - ", description=" + description + - ", slug=" + slug + - ", repository=" + repository + - '}'; - } - } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/post/Description.java b/genealogy/src/main/java/org/codefx/java_after_eight/post/Description.java index 791cc82..02d3be5 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/post/Description.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/post/Description.java @@ -2,46 +2,15 @@ import org.codefx.java_after_eight.Utils; -import java.util.Objects; - import static java.util.Objects.requireNonNull; -public class Description { - - private final String text; +public record Description(String text) { - public Description(String text) { + public Description { requireNonNull(text); - String unquotedText = Utils.removeOuterQuotationMarks(text).trim(); - if (unquotedText.isEmpty()) + text = Utils.removeOuterQuotationMarks(text).strip(); + if (text.isBlank()) throw new IllegalArgumentException("Description can't have an empty text."); - this.text = unquotedText; - } - - public String text() { - return text; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Description that = (Description) o; - return text.equals(that.text); - } - - @Override - public int hashCode() { - return Objects.hash(text); - } - - @Override - public String toString() { - return "Description{" + - "text='" + text + '\'' + - '}'; } } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/post/Post.java b/genealogy/src/main/java/org/codefx/java_after_eight/post/Post.java index 548490e..2c37fb5 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/post/Post.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/post/Post.java @@ -1,13 +1,13 @@ package org.codefx.java_after_eight.post; import java.time.LocalDate; -import java.util.stream.Stream; +import java.util.Set; -public interface Post { +public sealed interface Post permits Article, Talk, Video { Title title(); - Stream tags(); + Set tags(); LocalDate date(); diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/post/Repository.java b/genealogy/src/main/java/org/codefx/java_after_eight/post/Repository.java index 6e406a5..aabe6da 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/post/Repository.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/post/Repository.java @@ -1,43 +1,13 @@ package org.codefx.java_after_eight.post; -import java.util.Objects; - import static java.util.Objects.requireNonNull; -public class Repository { - - private final String identifier; +public record Repository(String identifier) { - public Repository(String identifier) { - this.identifier = requireNonNull(identifier); - if (identifier.isEmpty()) + public Repository { + identifier = requireNonNull(identifier); + if (identifier.isBlank()) throw new IllegalArgumentException("Repositories can't have an empty identifier."); } - public String identifier() { - return identifier; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Repository that = (Repository) o; - return identifier.equals(that.identifier); - } - - @Override - public int hashCode() { - return Objects.hash(identifier); - } - - @Override - public String toString() { - return "Repository{" + - "identifier='" + identifier + '\'' + - '}'; - } - } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/post/Slug.java b/genealogy/src/main/java/org/codefx/java_after_eight/post/Slug.java index a104da8..cdda80d 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/post/Slug.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/post/Slug.java @@ -1,48 +1,18 @@ package org.codefx.java_after_eight.post; -import java.util.Objects; - import static java.util.Objects.requireNonNull; -public class Slug implements Comparable { - - private final String value; +public record Slug(String value) implements Comparable { - public Slug(String value) { - this.value = requireNonNull(value); - if (value.isEmpty()) + public Slug { + value = requireNonNull(value); + if (value.isBlank()) throw new IllegalArgumentException("Slugs can't have an empty value."); } - public String value() { - return value; - } - @Override public int compareTo(Slug right) { return this.value.compareTo(right.value); } - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Slug slug = (Slug) o; - return value.equals(slug.value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public String toString() { - return "Slug{" + - "value='" + value + '\'' + - '}'; - } - } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/post/Tag.java b/genealogy/src/main/java/org/codefx/java_after_eight/post/Tag.java index 12794bf..634c590 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/post/Tag.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/post/Tag.java @@ -1,58 +1,26 @@ package org.codefx.java_after_eight.post; -import java.util.Collections; -import java.util.Objects; import java.util.Set; import java.util.stream.Stream; import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toSet; +import static java.util.function.Predicate.not; +import static java.util.stream.Collectors.toUnmodifiableSet; -public class Tag { +public record Tag(String text) { - private final String text; - - private Tag(String text) { - this.text = requireNonNull(text); - if (text.isEmpty()) - throw new IllegalArgumentException("Tags can't have an empty text."); + public Tag { + requireNonNull(text); } public static Set from(String tagsText) { - Set tags = Stream.of(tagsText + return Stream.of(tagsText .replaceAll("^\\[|\\]$", "") .split(",")) - .map(String::trim) - .filter(tag -> !tag.isEmpty()) + .map(String::strip) + .filter(not(String::isBlank)) .map(Tag::new) - .collect(toSet()); - return Collections.unmodifiableSet(tags); - } - - public String text() { - return text; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Tag tag = (Tag) o; - return text.equals(tag.text); - } - - @Override - public int hashCode() { - return Objects.hash(text); - } - - @Override - public String toString() { - return "Tag{" + - "text='" + text + '\'' + - '}'; + .collect(toUnmodifiableSet()); } } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/post/Talk.java b/genealogy/src/main/java/org/codefx/java_after_eight/post/Talk.java index 1957434..21a237c 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/post/Talk.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/post/Talk.java @@ -5,62 +5,31 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import static java.util.Objects.requireNonNull; -public class Talk implements Post { +public record Talk( + Title title, + Set tags, + LocalDate date, + Description description, + Slug slug, + URI slides, + Optional video) implements Post { - private final Title title; - private final Set tags; - private final LocalDate date; - private final Description description; - private final Slug slug; - - private final URI slides; - private final Optional video; - - public Talk(Title title, Set tags, LocalDate date, Description description, Slug slug, URI slides, Optional video) { - this.title = requireNonNull(title); - this.tags = requireNonNull(tags); - this.date = requireNonNull(date); - this.description = requireNonNull(description); - this.slug = requireNonNull(slug); - this.slides = requireNonNull(slides); - this.video = requireNonNull(video); - } - - @Override - public Title title() { - return title; - } - - @Override - public Stream tags() { - return tags.stream(); - } - - @Override - public LocalDate date() { - return date; + public Talk { + requireNonNull(title); + requireNonNull(tags); + requireNonNull(date); + requireNonNull(description); + requireNonNull(slug); + requireNonNull(slides); + requireNonNull(video); } @Override - public Description description() { - return description; - } - - @Override - public Slug slug() { - return slug; - } - - public URI slides() { - return slides; - } - - public Optional video() { - return video; + public Set tags() { + return Set.copyOf(tags); } @Override @@ -69,8 +38,8 @@ public boolean equals(Object o) { return true; if (o == null || getClass() != o.getClass()) return false; - Talk video = (Talk) o; - return slug.equals(video.slug); + Talk talk = (Talk) o; + return slug.equals(talk.slug); } @Override @@ -78,17 +47,4 @@ public int hashCode() { return Objects.hash(slug); } - @Override - public String toString() { - return "Video{" + - "title=" + title + - ", tags=" + tags + - ", date=" + date + - ", description=" + description + - ", slug=" + slug + - ", slides=" + slides + - ", video=" + video + - '}'; - } - } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/post/Title.java b/genealogy/src/main/java/org/codefx/java_after_eight/post/Title.java index 157c5ca..1a788b1 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/post/Title.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/post/Title.java @@ -2,46 +2,16 @@ import org.codefx.java_after_eight.Utils; -import java.util.Objects; - import static java.util.Objects.requireNonNull; -public class Title { - - private final String text; +public record Title(String text) { - public Title(String text) { + public Title { requireNonNull(text); - String unquotedText = Utils.removeOuterQuotationMarks(text).trim(); - if (unquotedText.isEmpty()) + var unquotedText = Utils.removeOuterQuotationMarks(text); + if (unquotedText.isBlank()) throw new IllegalArgumentException("Titles can't have an empty text."); - this.text = unquotedText; - } - - public String text() { - return text; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Title title = (Title) o; - return text.equals(title.text); - } - - @Override - public int hashCode() { - return Objects.hash(text); - } - - @Override - public String toString() { - return "Title{" + - "value='" + text + '\'' + - '}'; + text = unquotedText; } } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/post/Video.java b/genealogy/src/main/java/org/codefx/java_after_eight/post/Video.java index 200eb9c..4a66bb0 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/post/Video.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/post/Video.java @@ -4,62 +4,31 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import static java.util.Objects.requireNonNull; -public class Video implements Post { +public record Video( + Title title, + Set tags, + LocalDate date, + Description description, + Slug slug, + VideoSlug video, + Optional repository) implements Post { - private final Title title; - private final Set tags; - private final LocalDate date; - private final Description description; - private final Slug slug; - - private final VideoSlug video; - private final Optional repository; - - public Video(Title title, Set tags, LocalDate date, Description description, Slug slug, VideoSlug video, Optional repository) { - this.title = requireNonNull(title); - this.tags = requireNonNull(tags); - this.date = requireNonNull(date); - this.description = requireNonNull(description); - this.slug = requireNonNull(slug); - this.video = requireNonNull(video); - this.repository = requireNonNull(repository); - } - - @Override - public Title title() { - return title; - } - - @Override - public Stream tags() { - return tags.stream(); - } - - @Override - public LocalDate date() { - return date; + public Video { + requireNonNull(title); + requireNonNull(tags); + requireNonNull(date); + requireNonNull(description); + requireNonNull(slug); + requireNonNull(video); + requireNonNull(repository); } @Override - public Description description() { - return description; - } - - @Override - public Slug slug() { - return slug; - } - - public VideoSlug video() { - return video; - } - - public Optional repository() { - return repository; + public Set tags() { + return Set.copyOf(tags); } @Override @@ -77,17 +46,4 @@ public int hashCode() { return Objects.hash(slug); } - @Override - public String toString() { - return "Video{" + - "title=" + title + - ", tags=" + tags + - ", date=" + date + - ", description=" + description + - ", slug=" + slug + - ", url=" + video + - ", repository=" + repository + - '}'; - } - } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/post/VideoSlug.java b/genealogy/src/main/java/org/codefx/java_after_eight/post/VideoSlug.java index c20c07b..1121b6c 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/post/VideoSlug.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/post/VideoSlug.java @@ -1,48 +1,18 @@ package org.codefx.java_after_eight.post; -import java.util.Objects; - import static java.util.Objects.requireNonNull; -public class VideoSlug implements Comparable { - - private final String value; +public record VideoSlug(String value) implements Comparable { - public VideoSlug(String value) { - this.value = requireNonNull(value); - if (value.isEmpty()) + public VideoSlug { + requireNonNull(value); + if (value.isBlank()) throw new IllegalArgumentException("Slugs can't have an empty value."); } - public String value() { - return value; - } - @Override public int compareTo(VideoSlug right) { return this.value.compareTo(right.value); } - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - VideoSlug slug = (VideoSlug) o; - return value.equals(slug.value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public String toString() { - return "VideoSlug{" + - "value='" + value + '\'' + - '}'; - } - } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/post/factories/PostFactory.java b/genealogy/src/main/java/org/codefx/java_after_eight/post/factories/PostFactory.java index 69a7990..84ffe59 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/post/factories/PostFactory.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/post/factories/PostFactory.java @@ -4,11 +4,11 @@ import org.codefx.java_after_eight.post.Content; import java.nio.file.Path; -import java.util.AbstractMap; -import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Stream; +import static java.util.function.Predicate.not; import static java.util.stream.Collectors.toMap; final class PostFactory { @@ -39,60 +39,46 @@ public static RawPost readPost(Path file) { public static RawPost readPost(List fileLines) { RawFrontMatter frontMatter = extractFrontMatter(fileLines); - Content content = () -> extractContent(fileLines).stream(); + Content content = () -> extractContent(fileLines); return new RawPost(frontMatter, content); } private static RawFrontMatter extractFrontMatter(List fileLines) { - List frontMatterLines = readFrontMatter(fileLines); - Map frontMatter = frontMatterLines.stream() + Map frontMatter = readFrontMatter(fileLines) .filter(line -> !line.startsWith("#")) .map(PostFactory::keyValuePairFrom) - .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + .collect(toMap(FrontMatterLine::key, FrontMatterLine::value)); return new RawFrontMatter(frontMatter); } - private static List readFrontMatter(List markdownFile) { - List frontMatter = new ArrayList<>(); - boolean frontMatterStarted = false; - for (String line : markdownFile) { - if (line.trim().equals(FRONT_MATTER_SEPARATOR)) { - if (frontMatterStarted) - return frontMatter; - else - frontMatterStarted = true; - } else if (frontMatterStarted) - frontMatter.add(line); - } - return frontMatter; + private static Stream readFrontMatter(List markdownFile) { + return markdownFile.stream() + .map(String::strip) + .dropWhile(not(FRONT_MATTER_SEPARATOR::equals)) + .skip(1) + .takeWhile(not(FRONT_MATTER_SEPARATOR::equals)); } - private static Map.Entry keyValuePairFrom(String line) { + private static FrontMatterLine keyValuePairFrom(String line) { String[] pair = line.split(":", 2); if (pair.length < 2) throw new IllegalArgumentException("Line doesn't seem to be a key/value pair (no colon): " + line); - String key = pair[0].trim(); - if (key.isEmpty()) + String key = pair[0].strip(); + if (key.isBlank()) throw new IllegalArgumentException("Line \"" + line + "\" has no key."); - String value = pair[1].trim(); - return new AbstractMap.SimpleImmutableEntry<>(key, value); + String value = pair[1].strip(); + return new FrontMatterLine(key, value); } - private static List extractContent(List markdownFile) { - List content = new ArrayList<>(); - boolean frontMatterStarted = false; - boolean contentStarted = false; - for (String line : markdownFile) { - if (line.trim().equals(FRONT_MATTER_SEPARATOR)) { - if (frontMatterStarted) - contentStarted = true; - else - frontMatterStarted = true; - } else if (contentStarted) - content.add(line); - } - return content; + private static Stream extractContent(List markdownFile) { + return markdownFile.stream() + .dropWhile(line -> !line.strip().equals(FRONT_MATTER_SEPARATOR)) + .skip(1) + .dropWhile(line -> !line.strip().equals(FRONT_MATTER_SEPARATOR)) + .skip(1); } + private record FrontMatterLine(String key, String value) { } + } diff --git a/genealogy/src/main/java/org/codefx/java_after_eight/recommendation/Recommendation.java b/genealogy/src/main/java/org/codefx/java_after_eight/recommendation/Recommendation.java index b1ef1d9..6cc7b0e 100644 --- a/genealogy/src/main/java/org/codefx/java_after_eight/recommendation/Recommendation.java +++ b/genealogy/src/main/java/org/codefx/java_after_eight/recommendation/Recommendation.java @@ -1,64 +1,29 @@ package org.codefx.java_after_eight.recommendation; import org.codefx.java_after_eight.post.Post; -import org.codefx.java_after_eight.post.Slug; import java.util.List; -import java.util.Objects; import java.util.stream.Stream; import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toUnmodifiableList; -public class Recommendation { +public record Recommendation( + Post post, + List recommendedPosts) { - private final Post post; - private final List recommendedPosts; - - Recommendation(Post post, List recommendedPosts) { - this.post = requireNonNull(post); - this.recommendedPosts = requireNonNull(recommendedPosts); + public Recommendation { + requireNonNull(post); + requireNonNull(recommendedPosts); } static Recommendation from(Post post, Stream sortedRecommendations, int perPost) { - List recommendations = sortedRecommendations.limit(perPost).collect(toList()); + var recommendations = sortedRecommendations.limit(perPost).toList(); return new Recommendation(requireNonNull(post), recommendations); } - public Post post() { - return post; - } - - public Stream recommendedPosts() { - return recommendedPosts.stream(); - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Recommendation that = (Recommendation) o; - return post.equals(that.post) && - recommendedPosts.equals(that.recommendedPosts); - } - - @Override - public int hashCode() { - return Objects.hash(post); - } - - @Override - public String toString() { - return "Recommendation{" + - "post=" + post.slug().value() + - ", recommendedPosts=" + recommendedPosts.stream() - .map(Post::slug) - .map(Slug::value) - .collect(joining(", ")) + - '}'; + public List recommendedPosts() { + return List.copyOf(recommendedPosts); } } diff --git a/genealogy/src/test/java/org/codefx/java_after_eight/TextParserTests.java b/genealogy/src/test/java/org/codefx/java_after_eight/TextParserTests.java index 741b712..e07ee96 100644 --- a/genealogy/src/test/java/org/codefx/java_after_eight/TextParserTests.java +++ b/genealogy/src/test/java/org/codefx/java_after_eight/TextParserTests.java @@ -12,30 +12,30 @@ public interface QuotationTests { @Test default void createFromStringWithoutQuotationMarks_noChange() { - String text = "A cool blog post"; - String expected = text; + var text = "A cool blog post"; + var expected = text; - String actual = parseCreateExtract(text); + var actual = parseCreateExtract(text); assertThat(actual).isEqualTo(expected); } @Test default void createFromStringWithQuotationMarks_quotationMarksRemoved() { - String text = "\"A cool blog post\""; - String expected = "A cool blog post"; + var text = "\"A cool blog post\""; + var expected = "A cool blog post"; - String actual = parseCreateExtract(text); + var actual = parseCreateExtract(text); assertThat(actual).isEqualTo(expected); } @Test default void createFromStringWithInnerQuotationMarks_onlyOuterQuotationMarksRemoved() { - String text = "\"\"A cool blog post\" he said\""; - String expected = "\"A cool blog post\" he said"; + var text = "\"\"A cool blog post\" he said\""; + var expected = "\"A cool blog post\" he said"; - String actual = parseCreateExtract(text); + var actual = parseCreateExtract(text); assertThat(actual).isEqualTo(expected); } diff --git a/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/GenealogyTests.java b/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/GenealogyTests.java index e395f1c..99bd165 100644 --- a/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/GenealogyTests.java +++ b/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/GenealogyTests.java @@ -7,8 +7,7 @@ import org.codefx.java_after_eight.genealogist.TypedRelation; import org.junit.jupiter.api.Test; -import java.util.Arrays; -import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -46,14 +45,11 @@ class GenealogyTests { private final Genealogist linkGenealogist = (Post1, Post2) -> new TypedRelation(Post1, Post2, linkRelation, linkScore(Post1, Post2)); - private final Weights weights; - - GenealogyTests() { - Map weights = new HashMap<>(); - weights.put(tagRelation, TAG_WEIGHT); - weights.put(linkRelation, LINK_WEIGHT); - this.weights = new Weights(weights, 0.5); - } + private final Weights weights = new Weights( + Map.of( + tagRelation, TAG_WEIGHT, + linkRelation, LINK_WEIGHT), + 0.5); private int tagScore(Post post1, Post post2) { if (post1 == post2) @@ -93,12 +89,12 @@ private int linkScore(Post post1, Post post2) { @Test void oneGenealogist_twoPosts() { - Genealogy genealogy = new Genealogy( - Arrays.asList(postA, postB), - Arrays.asList(tagGenealogist), + var genealogy = new Genealogy( + List.of(postA, postB), + List.of(tagGenealogist), weights); - Stream relations = genealogy.inferRelations(); + var relations = genealogy.inferRelations(); assertThat(relations).containsExactlyInAnyOrder( new Relation(postA, postB, round(TAG_SCORE_A_B * TAG_WEIGHT)), @@ -108,12 +104,12 @@ void oneGenealogist_twoPosts() { @Test void otherGenealogist_twoPosts() { - Genealogy genealogy = new Genealogy( - Arrays.asList(postA, postB), - Arrays.asList(linkGenealogist), + var genealogy = new Genealogy( + List.of(postA, postB), + List.of(linkGenealogist), weights); - Stream relations = genealogy.inferRelations(); + var relations = genealogy.inferRelations(); assertThat(relations).containsExactlyInAnyOrder( new Relation(postA, postB, round(LINK_SCORE_A_B * LINK_WEIGHT)), @@ -123,12 +119,12 @@ void otherGenealogist_twoPosts() { @Test void oneGenealogist_threePosts() { - Genealogy genealogy = new Genealogy( - Arrays.asList(postA, postB, postC), - Arrays.asList(tagGenealogist), + var genealogy = new Genealogy( + List.of(postA, postB, postC), + List.of(tagGenealogist), weights); - Stream relations = genealogy.inferRelations(); + var relations = genealogy.inferRelations(); assertThat(relations).containsExactlyInAnyOrder( new Relation(postA, postB, round(TAG_SCORE_A_B * TAG_WEIGHT)), @@ -142,12 +138,12 @@ void oneGenealogist_threePosts() { @Test void twoGenealogists_threePosts() { - Genealogy genealogy = new Genealogy( - Arrays.asList(postA, postB, postC), - Arrays.asList(tagGenealogist, linkGenealogist), + var genealogy = new Genealogy( + List.of(postA, postB, postC), + List.of(tagGenealogist, linkGenealogist), weights); - Stream relations = genealogy.inferRelations(); + var relations = genealogy.inferRelations(); assertThat(relations).containsExactlyInAnyOrder( new Relation(postA, postB, round((TAG_SCORE_A_B * TAG_WEIGHT + LINK_SCORE_A_B * LINK_WEIGHT) / 2)), diff --git a/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/RelationTests.java b/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/RelationTests.java index ac46aa6..154f574 100644 --- a/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/RelationTests.java +++ b/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/RelationTests.java @@ -6,7 +6,6 @@ import org.codefx.java_after_eight.genealogist.TypedRelation; import org.junit.jupiter.api.Test; -import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; @@ -24,14 +23,11 @@ class RelationTests { private final RelationType tagRelation = new RelationType("tag"); private final RelationType linkRelation = new RelationType("link"); - private final Weights weights; - - RelationTests() { - Map weights = new HashMap<>(); - weights.put(tagRelation, TAG_WEIGHT); - weights.put(linkRelation, LINK_WEIGHT); - this.weights = new Weights(weights, 0.5); - } + private final Weights weights = new Weights( + Map.of( + tagRelation, TAG_WEIGHT, + linkRelation, LINK_WEIGHT), + 0.5); /* * TODO: various failure states @@ -40,11 +36,11 @@ class RelationTests { @Test void singleTypedRelation_weightOne_samePostsAndScore() { int score = 60; - Stream typedRelations = Stream.of( + var typedRelations = Stream.of( new TypedRelation(postA, postB, tagRelation, score) ); - Relation relation = Relation.aggregate(typedRelations, weights); + var relation = Relation.aggregate(typedRelations, weights); assertThat(relation.post1()).isEqualTo(postA); assertThat(relation.post2()).isEqualTo(postB); @@ -53,24 +49,24 @@ void singleTypedRelation_weightOne_samePostsAndScore() { @Test void twoTypedRelation_weightOne_averagedScore() { - Stream typedRelations = Stream.of( + var typedRelations = Stream.of( new TypedRelation(postA, postB, tagRelation, 40), new TypedRelation(postA, postB, tagRelation, 80) ); - Relation relation = Relation.aggregate(typedRelations, weights); + var relation = Relation.aggregate(typedRelations, weights); assertThat(relation.score()).isEqualTo((40 + 80) / 2); } @Test void twoTypedRelation_differingWeight_weightedScore() { - Stream typedRelations = Stream.of( + var typedRelations = Stream.of( new TypedRelation(postA, postB, tagRelation, 40), new TypedRelation(postA, postB, linkRelation, 80) ); - Relation relation = Relation.aggregate(typedRelations, weights); + var relation = Relation.aggregate(typedRelations, weights); double expectedScore = (40 * TAG_WEIGHT + 80 * LINK_WEIGHT) / 2; assertThat(relation.score()).isEqualTo(round(expectedScore)); diff --git a/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/WeightsTests.java b/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/WeightsTests.java index b9ef9c1..6ff1ca4 100644 --- a/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/WeightsTests.java +++ b/genealogy/src/test/java/org/codefx/java_after_eight/genealogy/WeightsTests.java @@ -16,32 +16,28 @@ class WeightsTests { @Test void nullRelationType_throwsException() { - Map weightMap = new HashMap<>(); + var weightMap = new HashMap(); weightMap.put(null, 1.0); assertThatThrownBy(() -> new Weights(weightMap, 0.5)).isInstanceOf(NullPointerException.class); } @Test void nullWeight_throwsException() { - Map weightMap = new HashMap<>(); + var weightMap = new HashMap(); weightMap.put(TAG_TYPE, null); assertThatThrownBy(() -> new Weights(weightMap, 0.5)).isInstanceOf(NullPointerException.class); } @Test void knownRelationType_returnsWeight() { - Map weightMap = new HashMap<>(); - weightMap.put(TAG_TYPE, 0.42); - Weights weights = new Weights(weightMap, 0.5); + var weights = new Weights(Map.of(TAG_TYPE, 0.42), 0.5); assertThat(weights.weightOf(TAG_TYPE)).isEqualTo(0.42); } @Test void unknownRelationType_returnsDefaultWeight() { - Map weightMap = new HashMap<>(); - weightMap.put(TAG_TYPE, 0.42); - Weights weights = new Weights(weightMap, 0.5); + var weights = new Weights(Map.of(TAG_TYPE, 0.42), 0.5); assertThat(weights.weightOf(LIST_TYPE)).isEqualTo(0.5); } diff --git a/genealogy/src/test/java/org/codefx/java_after_eight/post/TagTests.java b/genealogy/src/test/java/org/codefx/java_after_eight/post/TagTests.java index 639af30..63cba35 100644 --- a/genealogy/src/test/java/org/codefx/java_after_eight/post/TagTests.java +++ b/genealogy/src/test/java/org/codefx/java_after_eight/post/TagTests.java @@ -1,21 +1,16 @@ package org.codefx.java_after_eight.post; import org.assertj.core.api.Assertions; -import org.codefx.java_after_eight.post.Tag; import org.junit.jupiter.api.Test; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - class TagTests { @Test void emptyElementArray_emptyTag() { - String tagsText = "[ ]"; - String[] expectedTags = { }; + var tagsText = "[ ]"; + var expectedTags = new String[] { }; - Set tags = Tag.from(tagsText); + var tags = Tag.from(tagsText); Assertions.assertThat(tags) .extracting(Tag::text) @@ -24,10 +19,10 @@ void emptyElementArray_emptyTag() { @Test void singleElementArray_singleTag() { - String tagsText = "[$TAG]"; - String[] expectedTags = { "$TAG" }; + var tagsText = "[$TAG]"; + var expectedTags = new String[] { "$TAG" }; - Set tags = Tag.from(tagsText); + var tags = Tag.from(tagsText); Assertions.assertThat(tags) .extracting(Tag::text) @@ -36,10 +31,10 @@ void singleElementArray_singleTag() { @Test void multipleElementsArray_multipleTags() { - String tagsText = "[$TAG,$TOG,$TUG]"; - String[] expectedTags = { "$TAG", "$TOG", "$TUG" }; + var tagsText = "[$TAG,$TOG,$TUG]"; + var expectedTags = new String[]{ "$TAG", "$TOG", "$TUG" }; - Set tags = Tag.from(tagsText); + var tags = Tag.from(tagsText); Assertions.assertThat(tags) .extracting(Tag::text) @@ -48,10 +43,10 @@ void multipleElementsArray_multipleTags() { @Test void multipleElementsArrayWithSpaces_multipleTagsWithoutSpaces() { - String tagsText = "[$TAG , $TOG , $TUG ]"; - String[] expectedTags = { "$TAG", "$TOG", "$TUG" }; + var tagsText = "[$TAG , $TOG , $TUG ]"; + var expectedTags = new String[]{ "$TAG", "$TOG", "$TUG" }; - Set tags = Tag.from(tagsText); + var tags = Tag.from(tagsText); Assertions.assertThat(tags) .extracting(Tag::text) @@ -60,10 +55,10 @@ void multipleElementsArrayWithSpaces_multipleTagsWithoutSpaces() { @Test void multipleElementsArrayWithJustSpacesTag_emptyTagIsIgnored() { - String tagsText = "[$TAG , , $TUG ]"; - String[] expectedTags = { "$TAG", "$TUG" }; + var tagsText = "[$TAG , , $TUG ]"; + var expectedTags = new String[]{ "$TAG", "$TUG" }; - Set tags = Tag.from(tagsText); + var tags = Tag.from(tagsText); Assertions.assertThat(tags) .extracting(Tag::text) @@ -72,10 +67,10 @@ void multipleElementsArrayWithJustSpacesTag_emptyTagIsIgnored() { @Test void multipleElementsArrayWithEmptyTag_emptyTagIsIgnored() { - String tagsText = "[$TAG ,, $TUG ]"; - String[] expectedTags = { "$TAG", "$TUG" }; + var tagsText = "[$TAG ,, $TUG ]"; + var expectedTags = new String[]{ "$TAG", "$TUG" }; - Set tags = Tag.from(tagsText); + var tags = Tag.from(tagsText); Assertions.assertThat(tags) .extracting(Tag::text) @@ -84,10 +79,10 @@ void multipleElementsArrayWithEmptyTag_emptyTagIsIgnored() { @Test void multipleElementsArrayDuplicateTags_duplicateTagIsIgnored() { - String tagsText = "[$TAG, $TAG]"; - String[] expectedTags = { "$TAG" }; + var tagsText = "[$TAG, $TAG]"; + var expectedTags = new String[]{ "$TAG" }; - Set tags = Tag.from(tagsText); + var tags = Tag.from(tagsText); Assertions.assertThat(tags) .extracting(Tag::text) diff --git a/genealogy/src/test/java/org/codefx/java_after_eight/post/factories/ArticleFactoryTests.java b/genealogy/src/test/java/org/codefx/java_after_eight/post/factories/ArticleFactoryTests.java index 4c9024e..41a60ec 100644 --- a/genealogy/src/test/java/org/codefx/java_after_eight/post/factories/ArticleFactoryTests.java +++ b/genealogy/src/test/java/org/codefx/java_after_eight/post/factories/ArticleFactoryTests.java @@ -1,12 +1,9 @@ package org.codefx.java_after_eight.post.factories; -import org.codefx.java_after_eight.post.Article; -import org.codefx.java_after_eight.post.Post; import org.codefx.java_after_eight.post.Tag; import org.junit.jupiter.api.Test; import java.time.LocalDate; -import java.util.Arrays; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -15,7 +12,7 @@ class ArticleFactoryTests { @Test void createFromFrontMatter_multipleColons_getValidArticle() { - List file = Arrays.asList( + var file = List.of( "---", "title: Cool: A blog post", "tags: [$TAG, $TOG]", @@ -26,7 +23,7 @@ void createFromFrontMatter_multipleColons_getValidArticle() { "" ); - Post post = ArticleFactory.createArticle(file); + var post = ArticleFactory.createArticle(file); assertThat(post.title().text()).isEqualTo("Cool: A blog post"); assertThat(post.tags()).extracting(Tag::text).containsExactlyInAnyOrder("$TAG", "$TOG"); @@ -37,7 +34,7 @@ void createFromFrontMatter_multipleColons_getValidArticle() { @Test void createFromFrontMatter_allTagsCorrect_getValidArticle() { - List file = Arrays.asList( + var file = List.of( "---", "title: A cool blog post", "tags: [$TAG, $TOG]", @@ -48,7 +45,7 @@ void createFromFrontMatter_allTagsCorrect_getValidArticle() { "" ); - Post article = ArticleFactory.createArticle(file); + var article = ArticleFactory.createArticle(file); assertThat(article.title().text()).isEqualTo("A cool blog post"); assertThat(article.tags()).extracting(Tag::text).containsExactlyInAnyOrder("$TAG", "$TOG"); @@ -59,7 +56,7 @@ void createFromFrontMatter_allTagsCorrect_getValidArticle() { @Test void createFromFile_allTagsCorrect_getValidArticle() { - List file = Arrays.asList( + var file = List.of( "---", "title: A cool blog post", "tags: [$TAG, $TOG]", @@ -73,7 +70,7 @@ void createFromFile_allTagsCorrect_getValidArticle() { "Duis aute irure dolor in reprehenderit.", "Excepteur sint occaecat cupidatat non proident."); - Article article = ArticleFactory.createArticle(file); + var article = ArticleFactory.createArticle(file); assertThat(article.title().text()).isEqualTo("A cool blog post"); assertThat(article.tags()).extracting(Tag::text).containsExactlyInAnyOrder("$TAG", "$TOG"); diff --git a/genealogy/src/test/java/org/codefx/java_after_eight/recommendation/RecommenderTests.java b/genealogy/src/test/java/org/codefx/java_after_eight/recommendation/RecommenderTests.java index 1d1e34d..24f9c3b 100644 --- a/genealogy/src/test/java/org/codefx/java_after_eight/recommendation/RecommenderTests.java +++ b/genealogy/src/test/java/org/codefx/java_after_eight/recommendation/RecommenderTests.java @@ -6,7 +6,7 @@ import org.codefx.java_after_eight.genealogy.RelationTestHelper; import org.junit.jupiter.api.Test; -import java.util.Arrays; +import java.util.List; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -28,47 +28,47 @@ class RecommenderTests { @Test void forOnePost_oneRelation() { - Stream recommendations = recommender.recommend( + var recommendations = recommender.recommend( Stream.of(relation_AC), 1); assertThat(recommendations).containsExactlyInAnyOrder( - new Recommendation(postA, Arrays.asList(postC))); + new Recommendation(postA, List.of(postC))); } @Test void forOnePost_twoRelations() { - Stream recommendations = recommender.recommend( + var recommendations = recommender.recommend( Stream.of(relation_AB, relation_AC), 1); assertThat(recommendations).containsExactlyInAnyOrder( - new Recommendation(postA, Arrays.asList(postB))); + new Recommendation(postA, List.of(postB))); } @Test void forManyPosts_oneRelationEach() { - Stream recommendations = recommender.recommend( + var recommendations = recommender.recommend( Stream.of(relation_AC, relation_BC, relation_CB), 1); assertThat(recommendations).containsExactlyInAnyOrder( - new Recommendation(postA, Arrays.asList(postC)), - new Recommendation(postB, Arrays.asList(postC)), - new Recommendation(postC, Arrays.asList(postB)) + new Recommendation(postA, List.of(postC)), + new Recommendation(postB, List.of(postC)), + new Recommendation(postC, List.of(postB)) ); } @Test void forManyPosts_twoRelationsEach() { - Stream recommendations = recommender.recommend( + var recommendations = recommender.recommend( Stream.of(relation_AB, relation_AC, relation_BA, relation_BC, relation_CA, relation_CB), 1); assertThat(recommendations).containsExactlyInAnyOrder( - new Recommendation(postA, Arrays.asList(postB)), - new Recommendation(postB, Arrays.asList(postC)), - new Recommendation(postC, Arrays.asList(postA)) + new Recommendation(postA, List.of(postB)), + new Recommendation(postB, List.of(postC)), + new Recommendation(postC, List.of(postA)) ); } diff --git a/pom.xml b/pom.xml index 1cba71c..370dad0 100644 --- a/pom.xml +++ b/pom.xml @@ -12,8 +12,7 @@ UTF-8 - 8 - 8 + 19 5.7.0 3.7.7 @@ -28,10 +27,16 @@ maven-compiler-plugin 3.8.1 + + --enable-preview + maven-surefire-plugin 3.0.0-M5 + + --enable-preview + maven-jar-plugin @@ -40,6 +45,9 @@ maven-javadoc-plugin 3.4.1 + + --enable-preview --snippet-path ${project.basedir}/src/demo/java +
diff --git a/run.sh b/run.sh index 76a0b96..540433f 100755 --- a/run.sh +++ b/run.sh @@ -1 +1 @@ -java -cp jars/genealogy.jar:jars/genealogists.jar org.codefx.java_after_eight.Main +java --enable-preview -XX:+ShowCodeDetailsInExceptionMessages -p jars -m org.codefx.java_after_eight.genealogy