├── .gitignore ├── LICENSE ├── README.md ├── build.sh ├── content ├── articles │ ├── 2014-06-27-foobar.md │ ├── 2014-09-14-libfx-0-1-1-released.md │ ├── 2014-09-17-codefx-up-and-running.md │ ├── 2014-09-22-decorator-pattern-saved-my-day.md │ ├── 2014-09-25-workflows-refactoring.md │ ├── 2014-09-30-decorator-pattern-java-8.md │ ├── 2014-10-04-architecture-lost-years.md │ ├── 2014-10-07-intention-revealing-code-java-8-optional.md │ ├── 2014-10-18-design-optional.md │ ├── 2014-10-22-why-isnt-optional-serializable.md │ ├── 2014-10-26-concepts-serialization.md │ ├── 2014-10-29-serialization-proxy-pattern.md │ ├── 2014-11-02-serialize-optional.md │ ├── 2014-11-09-lambdas-java-peek-hood.md │ ├── 2014-11-13-javafx-sources-in-eclipse.md │ ├── 2014-11-23-libfx-0-2-0-released.md │ ├── 2014-11-28-use-listenerhandles.md │ ├── 2014-12-03-multiple-return-statements.md │ ├── 2014-12-08-instances-non-capturing-lambdas.md │ ├── 2014-12-31-goodbye-2014.md │ ├── 2015-01-01-hello-2015.md │ ├── 2015-01-07-new-javadoc-tags.md │ ├── 2015-01-15-everything-about-default-methods.md │ ├── 2015-01-26-android-emulator-haxm-thinkpad-t440p.md │ ├── 2015-02-15-value-based-classes.md │ ├── 2015-02-26-interface-evolution-with-default-methods-methods.md │ ├── 2015-03-09-test-collection-implementations-with-guava.md │ ├── 2015-03-16-roll-your-own-pirate-elvis-operator.md │ ├── 2015-03-26-agile-architecture.md │ ├── 2015-04-10-interface-evolution-with-default-methods-interfaces.md │ ├── 2015-04-17-getting-rid-of-anonymous-classes.md │ ├── 2015-04-27-how-java-9-and-project-jigsaw-may-break-your-code.md │ ├── 2015-05-02-libfx-0-2-1-released.md │ ├── 2015-05-06-crafted-design.md │ ├── 2015-05-11-first-release-of-jdeps-maven-plugin.md │ ├── 2015-05-18-javafx-project-jigsaw-jep-253.md │ ├── 2015-05-26-transforming-collections.md │ ├── 2015-05-28-libfx-0-3-0-released.md │ ├── 2015-06-18-motivation-goals-project-jigsaw.md │ ├── 2015-06-30-features-project-jigsaw-java-9.md │ ├── 2015-07-06-casting-in-java-8-and-beyond.md │ ├── 2015-07-15-comment-your-fucking-code.md │ ├── 2015-07-31-thoughts-on-comments.md │ ├── 2015-08-04-all-about-project-jigsaw-on-infoq.md │ ├── 2015-08-14-interview-about-comments-on-dzone.md │ ├── 2015-08-14-the-road-to-valhalla.md │ ├── 2015-08-27-stephen-colebourne-optional-a-strict-approach.md │ ├── 2015-09-07-stream-performance.md │ ├── 2015-09-18-stream-performance-your-ideas.md │ ├── 2015-09-28-taxonomy-comments.md │ ├── 2015-10-05-apache-jmeter-tutorial.md │ ├── 2015-10-19-jar-hell.md │ ├── 2015-10-26-will-there-be-module-hell.md │ ├── 2015-11-05-javaone-2015-prepare-for-jdk-9.md │ ├── 2015-11-09-javaone-2015-introduction-to-modular-development.md │ ├── 2015-11-11-javaone-2015-advanced-modular-development.md │ ├── 2015-11-13-javaone-2015-under-the-hood-of-project-jigsaw.md │ ├── 2015-11-23-junit-lambda-prototype.md │ ├── 2015-12-02-delay-of-java-9-release.md │ ├── 2015-12-07-jdeps-maven-plugin-0-2.md │ ├── 2015-12-25-jigsaw-hands-on-guide.md │ ├── 2015-12-31-goodbye-2015.md │ ├── 2016-01-03-hello-2016.md │ ├── 2016-01-14-stream-findfirst-findany-reduce.md │ ├── 2016-01-27-implied-readability.md │ ├── 2016-02-08-comments-costs-benefits.md │ ├── 2016-03-07-atom-on-gentoo.md │ ├── 2016-03-17-seven-reasons-against-blogging.md │ ├── 2016-04-26-doomed-code-review.md │ ├── 2016-05-14-codefx-levels-up.md │ ├── 2016-05-25-jeeconf-2016.md │ ├── 2016-06-06-implement-equals-correctly.md │ ├── 2016-06-13-implement-hashcode-correctly.md │ ├── 2016-06-20-java-9-stream.md │ ├── 2016-06-26-java-9-optional.md │ ├── 2016-07-25-oh-no-forgot-streamiterate.md │ ├── 2016-08-04-goodbye-disy-hello-sitepoint.md │ ├── 2016-08-15-code-reviews-disy-part-1.md │ ├── 2016-09-01-rebutting-5-common-stream-tropes.md │ ├── 2016-09-05-ultimate-guide-java-9.md │ ├── 2016-09-26-code-reviews-disy-part-2.md │ ├── 2016-10-26-code-reviews-disy-part-3.md │ ├── 2016-11-24-hello-world.md │ ├── 2016-12-01-future-java-might-look-like.md │ ├── 2016-12-07-whats-taking-long.md │ ├── 2016-12-14-snapshots-gradle-maven-publish-plugin.md │ ├── 2016-12-21-javaone-2016.md │ ├── 2016-12-29-goodbye-2016.md │ ├── 2017-01-01-hello-2017.md │ ├── 2017-01-18-reflection-vs-encapsulation.md │ ├── 2017-01-26-quo-vadis-scala.md │ ├── 2017-01-31-why-elvis-should-not-visit-java.md │ ├── 2017-02-13-repackaging-exceptions-streams.md │ ├── 2017-04-03-module-system-optional-dependencies.md │ ├── 2017-05-02-java-9-resources-talks-articles-blogs-books-courses.md │ ├── 2017-07-17-jdeps-tutorial.md │ ├── 2017-07-24-java-9-migration-guide.md │ ├── 2017-08-14-planning-your-java-9-update.md │ ├── 2017-09-11-five-command-line-options-hack-java-module-system.md │ ├── 2017-09-27-unified-logging-with-the-xlog-option.md │ ├── 2017-10-03-java-module-system-tutorial.md │ ├── 2017-10-23-jsr-305-java-9.md │ ├── 2017-11-16-java-10-var-type-inference.md │ ├── 2017-12-18-maven-on-java-9.md │ ├── 2017-12-31-goodbye-2017-hello-2018.md │ ├── 2018-02-05-java-9-tutorial.md │ ├── 2018-02-26-multi-release-jars-multiple-java-versions.md │ ├── 2018-06-11-intersection-types-var.md │ ├── 2018-06-18-tricks-var-anonymous-classes.md │ ├── 2018-06-25-traits-var.md │ ├── 2018-08-05-junit-5-architecture-jupiter.md │ ├── 2018-08-05-junit-5-basics.md │ ├── 2018-08-05-junit-5-disabled-conditions.md │ ├── 2018-08-05-junit-5-dynamic-tests.md │ ├── 2018-08-05-junit-5-extension-model.md │ ├── 2018-08-05-junit-5-parameterized-tests.md │ ├── 2018-08-05-junit-5-setup.md │ ├── 2018-09-25-java-11-migration-guide.md │ ├── 2018-10-15-http-2-api-tutorial.md │ ├── 2018-10-22-reactive-http-2-requests-responses.md │ ├── 2018-11-06-scripting-java-shebang.md │ ├── 2018-11-12-java-11-gems.md │ ├── 2019-03-04-teeing-collector.md │ ├── 2019-03-18-java-12-guide.md │ ├── 2019-05-13-jakarta-ee-javax-and-a-week-of-turmoil.md │ ├── 2019-05-29-immutable-collections-in-java.md │ ├── 2019-06-19-text-blocks.md │ ├── 2019-07-11-enable-preview-language-features.md │ ├── 2019-08-07-the-jpms-maturity-model.md │ ├── 2019-08-16-switch-expressions.md │ ├── 2019-09-15-application-class-data-sharing.md │ ├── 2019-09-17-java-13-guide.md │ ├── 2020-10-07-junit-pioneer-1-0.md │ ├── 2020-11-04-java-16-stream-mapmulti.md │ ├── 2020-11-16-stream-mapmulti-group.md │ ├── 2020-12-10-Java-2077.md │ ├── 2021-01-19-spring-boot-react-folders.md │ ├── 2021-02-16-pattern-matchig.md │ ├── 2021-02-23-type-patterns.md │ ├── 2021-03-03-unix-domain-sockets.md │ └── 2021-03-16-java-16-guide.md ├── talks │ ├── best-practices.md │ ├── comment-your-code.md │ ├── expert-java-8.md │ ├── java-9-migration.md │ ├── java-after-n.md │ ├── java-module-system.md │ ├── java-next.md │ ├── java-var.md │ ├── java-x.md │ ├── junit-5.md │ └── junit-pioneer.md └── videos │ ├── 2016-12-06-interview-devoxx-be-2016.md │ ├── 2017-03-29-nighthacking-javaland-2017.md │ ├── 2017-09-20-impressions-javazone-2017.md │ ├── 2017-09-21-welcome-java-9.md │ ├── 2017-11-16-var-java-10.md │ ├── 2018-09-20-effective-java-00-kickoff.md │ ├── 2018-09-24-effective-java-01-static-factory-methods.md │ ├── 2018-10-02-java-11-new-dawn.md │ ├── 2018-10-09-effective-java-02-builders.md │ ├── 2018-10-18-java-12-switch-expression.md │ ├── 2018-10-24-robert-scholte-maven-3-4-5.md │ ├── 2018-11-27-effective-java-03-utilities-04-singleton-05-dependency-injection.md │ ├── 2019-02-24-java-12-experiments.md │ ├── 2019-04-18-caliz-1-learning-graal.md │ ├── 2019-05-01-caliz-2-wrapping-graal.md │ ├── 2019-05-16-caliz-3-background-compilation.md │ ├── 2019-05-26-jpms-sander-mak.md │ ├── 2019-09-18-keynote-blip-code-one-2019.md │ ├── 2020-04-07-oliver-drotbohm-modularity.md │ ├── 2020-04-28-generics-1-basics.md │ ├── 2021-01-26-jdk-news-1.md │ ├── 2021-02-11-brian-goetz-25h.md │ └── 2021-03-05-jdk-news-2.md ├── genealogists ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── codefx │ │ └── java_after_eight │ │ └── genealogists │ │ ├── package-info.java │ │ ├── random │ │ ├── RandomGenealogist.java │ │ └── RandomGenealogistService.java │ │ ├── repo │ │ ├── RepoGenealogist.java │ │ └── RepoGenealogistService.java │ │ ├── silly │ │ ├── SillyGenealogist.java │ │ └── SillyGenealogistService.java │ │ ├── tags │ │ ├── TagGenealogist.java │ │ └── TagGenealogistService.java │ │ └── type │ │ ├── TypeGenealogist.java │ │ └── TypeGenealogistService.java │ └── resources │ └── META-INF │ └── services │ └── org.codefx.java_after_eight.genealogist.GenealogistService ├── genealogy ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── codefx │ │ └── java_after_eight │ │ ├── Config.java │ │ ├── Main.java │ │ ├── ProcessDetails.java │ │ ├── Utils.java │ │ ├── genealogist │ │ ├── Genealogist.java │ │ ├── GenealogistService.java │ │ ├── RelationType.java │ │ └── TypedRelation.java │ │ ├── genealogy │ │ ├── Genealogy.java │ │ ├── Relation.java │ │ └── Weights.java │ │ ├── post │ │ ├── Article.java │ │ ├── Content.java │ │ ├── Description.java │ │ ├── Post.java │ │ ├── Repository.java │ │ ├── Slug.java │ │ ├── Tag.java │ │ ├── Talk.java │ │ ├── Title.java │ │ ├── Video.java │ │ ├── VideoSlug.java │ │ └── factories │ │ │ ├── ArticleFactory.java │ │ │ ├── PostFactory.java │ │ │ ├── RawFrontMatter.java │ │ │ ├── RawPost.java │ │ │ ├── TalkFactory.java │ │ │ └── VideoFactory.java │ │ └── recommendation │ │ ├── Recommendation.java │ │ └── Recommender.java │ └── test │ └── java │ └── org │ └── codefx │ └── java_after_eight │ ├── TextParserTests.java │ ├── UtilsTests.java │ ├── genealogy │ ├── GenealogyTests.java │ ├── RelationTestHelper.java │ ├── RelationTests.java │ └── WeightsTests.java │ ├── post │ ├── DescriptionTests.java │ ├── PostTestHelper.java │ ├── SlugTests.java │ ├── TagTests.java │ ├── TitleTests.java │ └── factories │ │ └── ArticleFactoryTests.java │ └── recommendation │ └── RecommenderTests.java ├── pom.xml ├── recommendations.config ├── run.sh ├── stats-code.sh ├── stats-time.sh └── stats.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | 3 | .metadata 4 | bin/ 5 | tmp/ 6 | *.tmp 7 | *.bak 8 | *.swp 9 | *~.nib 10 | local.properties 11 | .settings/ 12 | .loadpath 13 | .recommenders 14 | 15 | .project 16 | .classpath 17 | *.launch 18 | 19 | # JetBrains 20 | 21 | .idea/ 22 | *.iws 23 | *.iml 24 | /out/ 25 | 26 | # Visual Studio Code 27 | 28 | .factoryPath 29 | .vscode/ 30 | 31 | # Gradle 32 | 33 | .gradle/ 34 | build/ 35 | 36 | # Maven 37 | 38 | target/ 39 | jars/ 40 | 41 | # JVM crash logs 42 | # see http://www.java.com/en/download/help/error_hotspot.xml 43 | hs_err_pid* 44 | 45 | # Project 46 | 47 | recommendations.json 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java After Eight/Eleven 2 | 3 | Nice Java 8/11 code base that gets way nicer with Java 9-19 and beyond. 4 | 5 | Code base for [my talk _Java After Eight/Eleven_](https://nipafx.dev/talk-java-after-n/). 6 | See the `java-$VERSION-guide.md` files [in the slides repo](https://github.com/nipafx/slides/tree/master/java-after-eight) to see which Java 9+ features go where. 7 | For a more structured approach to these features, check my [Java X demo repository](https://github.com/nipafx/demo-java-x). 8 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "building..." 5 | rm -f recommendations.json 6 | rm -rf jars/* 7 | mvn clean verify -q 8 | -------------------------------------------------------------------------------- /content/articles/2014-06-27-foobar.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Foobar" 3 | tags: [meta, techniques] 4 | date: 2014-06-27 5 | slug: foobar 6 | description: "A foobar post that may accidentally teach you about Foobar." 7 | searchKeywords: "Foobar" 8 | featuredImage: foobar 9 | --- 10 | 11 | Sorry, but this is no real post. 12 | I just needed a first entry to enable the RSS feed... 13 | 14 | But if you couldn't tell by the post's name that it will contain nothing useful, then perhaps it's not so foobar after all. 15 | Read up on Foobar (e.g. [on Wikipedia](http://en.wikipedia.org/wiki/Foo)) and use it. 16 | Often! 17 | Every time you're standing in front of a whiteboard or programming with someone else and you need a name that your audience should not waste their mental energy on, pick one of these: `foobar`, `foo`, `bar`, `baz`, ... 18 | 19 | And in case you are not standing in front of a whiteboard or are not programming with someone else on a regular basis... well, you should change that, it's fun! 20 | 😉 21 | -------------------------------------------------------------------------------- /content/articles/2014-09-14-libfx-0-1-1-released.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "LibFX 0.1.1 Released" 3 | tags: [javafx, libfx] 4 | date: 2014-09-14 5 | slug: libfx-0-1-1 6 | description: "Release post for LibFX 0.1.1 including a description of `Nestings` and pointers to GitHub, Maven and the Javadoc." 7 | searchKeywords: "libfx" 8 | featuredImage: libfx-library 9 | repo: libfx 10 | --- 11 | 12 | Just today I released [LibFX 0.1.1](https://github.com/nipafx/LibFX/releases/tag/v0.1.1)! 13 | 14 | > That's one small step for everybody else, one giant leap for me. 15 | 16 | Or something like that... 17 | 😉 18 | It's surely no big deal for a seasoned open source developer but since it's the very first production-ready release of my very first own open source library, it feels like quite the accomplishment to me. 19 | 20 | So I am here to proudly present it! 21 | 22 | ## LibFX 0.1.1 23 | 24 | So what can **LibFX** do for you? 25 | Currently it has only one feature, namely *Nestings*. 26 | It is [described in detail in the project's wiki](https://github.com/nipafx/LibFX/wiki/Nestings) but I will give a quick introduction here. 27 | 28 | 29 | 30 | ### Nestings 31 | 32 | Nestings enhance JavaFX' properties with a neat way to capture object hierarchies. 33 | Imagine the model for your UI has an `ObjectProperty currentEmployee` and the employee has an `ObjectProperty
addressProperty()` which in turn has a `StringProperty streetNameProperty()`. 34 | 35 | Now let's say you create an editor that you want to use to display and edit the current employee's street name. 36 | Note that the current employee can be replaced as well as the employee's address or the address' street name and of course you want your editor to always be up to date and point to the correct property. 37 | 38 | With the standard FX classes you are out of luck and essentially have to implement a lot of listening to property changes and updating bindings which will soon clutter your code. 39 | 40 | Enter **LibFX**! 41 | Using [method references](http://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html) you can do this: 42 | 43 | ```java 44 | StringProperty currentEmployeesStreetName = Nestings 45 | .on(currentEmployeeProperty) 46 | .nest(Employee::addressProperty) 47 | .nest(Address::streetNameProperty) 48 | .buildProperty(); 49 | ``` 50 | 51 | Now you have a property which always points to current employee's current address' street name. 52 | This is a fully functional property so you can do all the usual things with it. 53 | In this case, you could bidirectionally bind the editor's value property to it and you're done! 54 | 55 | ## Infrastructure 56 | 57 | But as important and surely as work-intensive as the feature was the infrastructure. 58 | I finally got (almost) all the tools up and running. 59 | 60 | ### About LibFX 61 | 62 | The project is hosted on [GitHub](https://github.com/nipafx/LibFX), which also offers the awesome [GitHub Pages](https://pages.github.com/). 63 | 64 | They run the project page under [libfx.codefx.org](http://libfx.codefx.org), which is the central point for all information regarding **LibFX**. 65 | It links to all tools, services and resources involved in creating the library, which especially include the [wiki](https://github.com/nipafx/LibFX/wiki) and the [Javadoc](http://libfx.codefx.org/javadoc/). 66 | 67 | ### Maven Coordinates 68 | 69 | LibFX 0.1.1 is available in Maven Central and these are the coordinates: 70 | 71 | ```xml 72 | org.codefx.libfx 73 | LibFX 74 | 0.1.1 75 | ``` 76 | 77 | Maven Central provides [further information for **LibFX** 0.1.1](http://search.maven.org/#artifactdetails%7Corg.codefx.libfx%7CLibFX%7C0.1.1%7Cjar), which can be used for other build tools. 78 | 79 | ### Continuous Inspection and Integration 80 | 81 | As far as I can tell at the moment, these are the only pieces missing to the puzzle. 82 | I don't think continuous integration is very important at this stage of the project but I'm curious and would like to get to know it before it really counts. 83 | 84 | But what I really crave is [SonarQube](http://www.sonarqube.org/)! 85 | I want it badly! 86 | But I'm still looking for a way to get it without breaking my budget. 87 | As soon as I find one I'll fix every possible issue and be back here to brag about the code quality. 88 | 😁 89 | -------------------------------------------------------------------------------- /content/articles/2014-09-17-codefx-up-and-running.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "CodeFX Up And Running" 3 | tags: [meta] 4 | date: 2014-09-17 5 | slug: codefx-up-and-running 6 | description: "Summer recess is officially over and shit got done! Finally, CodeFX is ready to take on the world." 7 | searchKeywords: "CodeFX" 8 | featuredImage: codefx-up-and-running 9 | --- 10 | 11 | 12 | 13 | This post is _old_ and almost everything it touches on changed since then - starting with CodeFX, which is now nipafx (hey, that's me!). 14 | Such are the woes of the internet. 15 | 😁 16 | 17 | 18 | 19 | ## Up 20 | 21 | Besides starting my new day job, I finished some long waiting coding and infrastructure work. 22 | 23 | ### Code 24 | 25 | I cleaned up my *SnapshotView* over at [ControlsFX](https://controlsfx.org) (which is currently waiting to be pulled) and finally got everything working to [release the first production-ready version of LibFX](libfx-0-1-1). 26 | 27 | ### CodeFX 28 | 29 | Maybe even more important in this context is that I finally got this blog ready to go! 30 | I tweaked the theme (and hope everybody reacts to the Eclipse purple I recklessly plagiarized) and got some much needed plugins like the very cool [Crayon](https://wordpress.org/plugins/crayon-syntax-highlighter/) which paints my code like this: 31 | 32 | ```java 33 | StringProperty currentEmployeesStreetName = Nestings 34 | .on(currentEmployeeProperty) 35 | .nest(Employee::addressProperty) 36 | .nest(Address::streetNameProperty) 37 | .buildProperty(); 38 | ``` 39 | 40 | Foregoing the neither pretty nor overly usable WordPress comments I looked for another way to lure you, the reader, into a conversation. 41 | I would've liked to go for [Discourse](https://www.discourse.org/) but couldn't find a cheap (read: for free) way to run it. 42 | Who knows, if this blog takes off, I might spend some money on it after all. 43 | 44 | But for now we're going with [Disqus](https://disqus.com/). 45 | To keep the site loading fast, comments are only loaded on demand (see the button at the end of this post) and in case you already have a Disqus account you can of course use it here as well. 46 | 47 | Finally, I'm done fighting with newsletter plugins, be it *Mailpoet*, *Newsletter Ready!* or *Newsletter* (ha, no links for you!). 48 | I'm sticking with the latter and since it doesn't provide a convenient way to send out complete posts, you won't get them via the newsletter. 49 | So if you want to read without stopping by, you'll have to [subscribe to the RSS feed](/feed.xml). 50 | 51 | ## Running 52 | 53 | So now that everything's done there are no more excuses. 54 | I'm ready to [achieve ultimate blog success in one easy step](https://blog.codinghorror.com/how-to-achieve-ultimate-blog-success-in-one-easy-step/)! 55 | ☺️ 56 | 57 | And look at that, I even have some ideas of (interesting?) things to write about! 58 | Like a tutorial on creating a first open source project - something I recently went through myself. 59 | Or how to create a JavaFX control the way it's supposed to be done... 60 | 61 | So I’ll be back and I hope so will you! 62 | -------------------------------------------------------------------------------- /content/articles/2014-10-04-architecture-lost-years.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Impulse: \"Architecture - The Lost Years\"" 3 | tags: [architecture, impulse] 4 | date: 2014-10-04 5 | slug: architecture-lost-years 6 | description: "Discussing the talk \"Architecture - The Lost Years\", which Robert C. Martin held on several occasions." 7 | searchKeywords: "Architecture Lost Years" 8 | featuredImage: architecture-lost-years 9 | --- 10 | 11 | Wow! 12 | 13 | There's not much more to say. 14 | The architecture Robert C. 15 | Martin presents in [this talk](http://www.youtube.com/watch?v=WpkDN78P884 "Robert C. Martin at Ruby Midwest 2011") is mind-blowing. 16 | Never mind that it's 20 years old - nobody seems to use it so it counts as new. 17 | (By the way those are the lost years in the title "Architecture - The Lost Years".) 18 | 19 | Just go and watch the video! 20 | 21 | I'll not even try to properly summarize the talk as I think it should be watched in its entirety. 22 | But just in case you wonder whether you really want to do that, I'll try to make you curious. 23 | 24 | ## The Talk 25 | 26 | ([Link to YouTube](http://www.youtube.com/watch?v=WpkDN78P884 "Robert C. Martin at Ruby Midwest 2011")) 27 | 28 | Just in case you want to watch it again, use [this link](http://www.youtube.com/watch?v=asLUTiJJqdE "Robert C. Martin at COHAA"). 29 | It doesn't have the slides but a fun introduction about lasers. 30 | 31 | Unfortunately I couldn't find the slides online. 32 | In case you do, you can link them in the comments. 33 | 34 | ## Making You Curious 35 | 36 | The following summary has a big hole in it where the actual architecture is described. 37 | But the rest is more or less there. 38 | 39 | ### Folder Structure 40 | 41 | Martin starts with showing a standard Ruby on Rails app. 42 | And while the used framework is obvious from the code's top level folders, the actual intent of the program is not. 43 | But shouldn't it be? 44 | 45 | > Architecture of an application is all about its intent. 46 | 47 | He compares that with building architecture where the ground plan will clearly give away the building's purpose. 48 | For code the ground plan would be the content of the topmost folder, maybe the ones below that. 49 | So, likewise, these folders should clearly show the code's purpose. 50 | And they should do this by listing the use cases so there should be folders like *CreateOrder* and *AddItemToOrder*. 51 | 52 | Does your code look like that? 53 | Mine doesn't. 54 | 55 | ### Details 56 | 57 | He also mentions two things which should be an implementation detail in properly architectured systems: 58 | 59 | - the user interface 60 | - the database 61 | 62 | The former not only includes the UI technology but also (and especially) the Web. 63 | That's just a delivery mechanism to get the user in touch with the business logic! 64 | It should in no way be of any importance to the system's architecture. 65 | 66 | And the database is also something that dangles off the side of your application. 67 | It's a mere implementation detail. 68 | 69 | If all that doesn't make you curious, you're either really good or very unexcitable. 70 | 71 | ### Use Case Driven Design 72 | 73 | So how to achieve all that? 74 | With the [use case driven approach presented by Ivar Jacobson](https://www.amazon.com/dp/0201544350) in 1993. 75 | (There also exists a [slimmed down, updated and free version](http://www.ivarjacobson.com/download.ashx?id=1282) from 2011.) 76 | 77 | Martin goes on to explain the concept. 78 | 79 | I'm not going to summarize that part. 80 | It is too complex and important to be cramped into a few lines. 81 | I'm currently thinking about writing a small app just to test drive the concept. 82 | If I do, I might make some posts out of my experience, so stay tuned: [⇒ newsletter](news), [⇒ RSS](/feed.xml) 83 | 84 | 85 | 86 | At GeeCON 2014 Sandro Mancuso gave a talk called *Crafted Design*. 87 | It is based on the same observation and problem statement as Martin's talk and comes to a very similar solution. 88 | I covered it in my post [Impulse: "Crafted Design"](crafted-design) and it contains a description of the architecture, which I did not write here. 89 | You should check it out!* 90 | 91 | 92 | 93 | ### Effects 94 | 95 | Martin mentions two major advantages of such an approach. 96 | 97 | #### Good Architecture 98 | 99 | If you ban all subsystems which are not part of the central business logic to the outer reaches of your code and abstract them behind interfaces, you can easily substitute them. 100 | This leads to good architecture as Martin defines it: 101 | 102 | > A good architecture allows major decisions to be deferred! 103 | 104 | > A good architecture maximized the number of decisions not made. 105 | 106 | He gives [FitNesse](http://www.fitnesse.org/) as an example. 107 | It was supposed to get a database to persist its content - in fact this was one of the first things the team considered. 108 | But for no special reason they deferred that decision a couple of times. 109 | Eventually, the system was ready and still had no database because, as it turns out, it was not necessary. 110 | And when a customer really needed one, it was plugged in without effort. 111 | 112 | #### Testing 113 | 114 | Of course no talk by Martin would be complete without him insisting on having a comprehensive and fast test suite. 115 | He quickly outlines why this is so important: If you can't test everything and do it fast, you won't refactor constantly, in which case you're doomed. 116 | (Yes, it's that simple.) 117 | 118 | But keeping tests fast is impossible if they typically involve the database or the UI. 119 | Unfortunately, this is the reality for many systems as those subsystems are not sufficiently decoupled to be substituted with mocks during tests. 120 | 121 | Jacobson's approach, on the other hand, promotes exactly that kind of decoupling. 122 | If the UI, database, file system, online services and whoknowswhatelse all become plugins, you can mock them easily and have your tests run blazingly fast. 123 | 124 | ## Reflection 125 | 126 | As I didn't cover the main part of the talk, we can't reflect on that. 127 | Instead, I'm going to insist some more: 128 | 129 | - [watch the talk](https://www.youtube.com/watch?v=WpkDN78P884 "Robert C. 130 | Martin at Ruby Midwest 2011") 131 | - [or this one](https://www.youtube.com/watch?v=asLUTiJJqdE "Robert C. 132 | Martin at COHAA") 133 | - [or this one](https://www.youtube.com/watch?v=HhNIttd87xs "Robert C. 134 | Martin at Hakka Labs") 135 | - [or this one](https://www.youtube.com/watch?v=Nltqi7ODZTM "Robert C. 136 | Martin at NDC 2012") (looks like he's been touring) 137 | - [get the original book by Jacobosn](https://www.amazon.com/dp/0201544350) 138 | - [get the updated version](http://www.ivarjacobson.com/download.ashx?id=1282) 139 | - try it out 140 | - give me your opinion 141 | 142 | -------------------------------------------------------------------------------- /content/articles/2014-11-13-javafx-sources-in-eclipse.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "JavaFX Sources in Eclipse" 3 | tags: [tools, javafx] 4 | date: 2014-11-13 5 | slug: javafx-sources-in-eclipse 6 | description: "A quick step by step guide how to use the JavaFX sources in Eclipse by attaching them to the current JDK." 7 | searchKeywords: "JavaFX sources in eclipse" 8 | featuredImage: javafx-with-attached-source 9 | --- 10 | 11 | Just a quickie about how to tell Eclipse to attach the JavaFX sources for an improved development experience. 12 | 13 | ## Java Sources 14 | 15 | When you're using [Eclipse](http://eclipse.org/) with a JDK as your system library, the IDE will helpfully display [Javadoc](https://en.wikipedia.org/wiki/Javadoc) when you hover over members of official Java classes and let you jump into the code whenever you want to. 16 | This is extremely helpful to get to know the classes and take a look at their inner workings. 17 | 18 | This is possible because the JDK comes with its own sources and Eclipse knows about them. 19 | It attaches those sources to the compiled classes from the JDK and thus provides you those benefits. 20 | 21 | ## JavaFX Sources 22 | 23 | For reasons unknown to me, Eclipse does not automatically do this for the JavaFX classes (from the package `javafx` ), though. 24 | But because like the rest of the JRE, the FX sources are also bundled with the JDK, few things are easier than to attach them. 25 | 26 | In Eclipse: 27 | 28 | 1. open preferences (*Window* \~> *Preferences*) 29 | 2. edit the used JRE (*Java* \~> *Installed JREs* \~> Select JDK on the right \~> *Edit*) 30 | 3. start to attach FX sources (select **jfxrt.jar** \~> *Source Attachement)* 31 | 4. configure the sources (select *External location* \~> *External File*) 32 | 5. select the sources bundled with the JDK 33 | 34 | (go to JDK root folder, e.g. C:/Program Files/Java/jdk1.8.0\_20 35 | 36 |     \~> select **javafx-src.zip**) 37 | 38 | 6. almost done, just OK/Finish your way back... 39 | 40 | To verify that this worked, open the *Open Type* dialog (by default with *Ctrl-Shift-T*) and check that you can open types from JavaFX like `javafx.application.Application`, `javafx.application.Platform` or `javafx.beans.property.Property`. 41 | 42 | You should also be able to see tooltips like this one: 43 | 44 | ## Reflection 45 | 46 | You have seen a step by step guide how to attach the JavaFX sources in Eclipse so it shows the Javadoc and lets you jump into the source code. 47 | 48 | Last step: Have even more fun with JavaFX! 49 | -------------------------------------------------------------------------------- /content/articles/2014-11-23-libfx-0-2-0-released.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "LibFX 0.2.0 Released" 3 | tags: [javafx, libfx] 4 | date: 2014-11-23 5 | slug: libfx-0-2-0 6 | description: "Release post for LibFX 0.2.0 including and pointers to GitHub, Feature descriptions, Maven coordinates and the Javadoc." 7 | searchKeywords: "libfx 0.2.0" 8 | featuredImage: libfx-library 9 | repo: libfx 10 | --- 11 | 12 | Yesterdays I released [**LibFX 0.2.0**](https://github.com/nipafx/LibFX/releases/tag/v0.2.0)! 13 | It has nice new features I've been needing for other projects: 14 | 15 | - [`ControlPropertyListener`](https://github.com/nipafx/LibFX/wiki/ControlPropertyListener): creating listeners for the property map of JavaFX' controls 16 | - [`ListenerHandle`](https://github.com/nipafx/LibFX/wiki/ListenerHandle): encapsulating an observable and a listener for easier add/remove of the listener 17 | - [`SerializableOptional`](https://github.com/nipafx/LibFX/wiki/SerializableOptional): serializable wrapper for `Optional` 18 | - [`WebViewHyperlinkListener`](https://github.com/nipafx/LibFX/wiki/WebViewHyperlinkListener): add hyperlink listeners to JavaFX' `WebView` 19 | 20 | And don't forget about [Nestings](https://github.com/nipafx/LibFX/wiki/Nestings): using all the power of JavaFX' properties for nested object aggregations. 21 | 22 | ## Getting Started 23 | 24 | The links above point to the [**LibFX** wiki on GitHub](https://github.com/nipafx/LibFX/wiki). 25 | It has an article for each feature explaining the concept, giving some examples and pointing to the best resource in the code to get started. 26 | 27 | Most key features also have self-contained demos, which can be found in [their own source folder](https://github.com/nipafx/LibFX/tree/master/src/demo/java/org/codefx/libfx). 28 | 29 | Finally, there's extensive Javadoc under [libfx.codefx.org/javadoc/](http://libfx.codefx.org/javadoc/). 30 | 31 | ## Getting LibFX 0.2.0 32 | 33 | You can get **LibFX 0.2.0** here: 34 | 35 | 36 | 37 | ```xml 38 | 39 | org.codefx.libfx 40 | LibFX 41 | 0.2.0 42 | 43 | ``` 44 | 45 | ```java 46 | compile 'org.codefx.libfx:LibFX:0.2.0' 47 | ``` 48 | 49 | It's licensed under GLP 3.0 but other arrangements can be made - just [shoot me an email](mailto:nicolai@nipafx.dev). 50 | -------------------------------------------------------------------------------- /content/articles/2015-01-26-android-emulator-haxm-thinkpad-t440p.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Running Android Emulator With HAXM On Thinkpad T440p" 3 | tags: [tools] 4 | date: 2015-01-26 5 | slug: android-emulator-haxm-thinkpad-t440p 6 | description: "Quick guide to how to use the Android emulator with HAXM (based on VT-x) on a Thinkpad T440p." 7 | searchKeywords: "HAXM" 8 | featuredImage: haxm 9 | --- 10 | 11 | Over the weekend, I started to work on [my first Android app](https://github.com/CodeFX-org/privacy-guide). 12 | I had a little trouble getting the [Android emulator](http://developer.android.com/tools/devices/index.html) to run with *HAXM*, Intel's *Hardware Accelerated Execution Manager*, which builds on top of the virtualization hardware *VT-x*. 13 | Maybe others with the same problems find their way here... 14 | 15 | These are the steps that worked **for me** - your mileage may vary. 16 | Read them carefully; I might have let my frustration slip into the steps at some point or other. 17 | 18 | ## Hardware Support 19 | 20 | First check whether your processor actually supports virtualization. 21 | 22 | 1. [go to Intel](http://ark.intel.com/) 23 | 2. find your processor 24 | 3. check whether *Intel® Virtualization Technology (VT-x)* is listed under *Advanced Technologies* 25 | 4. also read the footnote if there is one 26 | 27 | Side note: [*VT-x* is included in *VT-d*](https://software.intel.com/en-us/articles/intel-virtualization-technology-for-directed-io-vt-d-enhancing-intel-platforms-for-efficient-virtualization-of-io-devices) 28 | 29 | ## Download _HAXM_ installer 30 | 31 | Now you can download the installer for the emulator accelerator. 32 | 33 | 1. open the Android SDK Manager 34 | - in Android Studio it's under *Tools* \~> *Android* \~> *SDK Manager* 35 | - in Eclipse it's under *Window* \~> *Android SDK Manager* (look [here](http://stackoverflow.com/a/13885869/2525313) if it's missing) 36 | 2. check *Intel x86 Emulator Accelerator (HAXM installer)* under *Extras* and install packages 37 | 38 | ## Install _HAXM_ 39 | 40 | Next step is to actually install HAXM. 41 | 42 | 1. above the package list the SDK manager shows the path to the SDK 43 | 2. go there and then continue to *./extras/intel/Hardware\_Accelerated\_Execution\_Manager* 44 | 3. run the *intelhaxm-android* installer 45 | 46 | ## Fail In Different Ways (optional) 47 | 48 | In case the installation doesn't work because the virtualization features are not turned on, there are several ways to waste some time... 49 | 50 | 1. you can download intel's Processor Identification Utility (for [Windows](https://downloadcenter.intel.com/Detail_Desc.aspx?DwnldID=7838) and [Linux](http://www.intel.com/support/processors/tools/piu/sb/CS-033142.htm)) and see it claim that *Intel (R) Virtualization Technology* is *Yes* (not my wording) 51 | 2. you can install [CPU-Z](http://www.cpuid.com/softwares/cpu-z.html) or [i-Nex](http://www.omgubuntu.co.uk/2014/02/nex-cpu-z-hardware-stat-tool-linux) and watch it report *VT-X* (under *instructions*) to be turned on 52 | 53 | ## Fix It 54 | 55 | After that small detour you might realize that the tools are lying to you... 56 | 57 | 1. enter your BIOS (on my T440p: *Enter* on first boot screen, then *F1*) 58 | 2. go to *Config* \~> *CPU* and be frustrated that it's not there... 59 | 3. go to *Security* \~> *Virtualization* and turn everything on 60 | 4. reboot and install successfully 61 | 62 | ## Some more links... 63 | 64 | - [Speeding Up the Android Emulator on Intel Architecture - Intel](https://software.intel.com/en-us/android/articles/speeding-up-the-android-emulator-on-intel-architecture) 65 | - [HAXM Install Will Not Detect Enabled VT-x - thread in Intel forum](https://software.intel.com/en-us/forums/topic/328242) 66 | - [active *Hyper-V* might lock access to *VT-x*](https://forums.lenovo.com/t5/T400-T500-and-newer-T-series/Intel-VT-x-on-T440-64-bit-Virtual-Machine-Support/m-p/1639600/highlight/true#M100150 "Intel VT-x on T440? 67 | (64-bit Virtual Machine Support) - Lenovo Forum") 68 | 69 | Worked? 70 | Didn't work? 71 | Tweet, leave a comment, shoot me a mail... 72 | -------------------------------------------------------------------------------- /content/articles/2015-05-02-libfx-0-2-1-released.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "LibFX 0.2.1 Released" 3 | tags: [libfx] 4 | date: 2015-05-02 5 | slug: libfx-0-2-1 6 | description: "Release post for LibFX 0.2.1 including pointers to GitHub, feature descriptions, Maven coordinates and the Javadoc." 7 | searchKeywords: "libfx 0.2.1" 8 | featuredImage: libfx-library 9 | repo: libfx 10 | --- 11 | 12 | I released [**LibFX 0.2.1**](https://github.com/nipafx/LibFX/releases/tag/v0.2.1) yesterday! 13 | It contains a bugfix in [Nestings](https://github.com/nipafx/LibFX/wiki/Nestings). 14 | 15 | ## Getting Started 16 | 17 | If you want to find out about **LibFX** check out the [wiki on GitHub](https://github.com/nipafx/LibFX/wiki). 18 | It has an article for each feature explaining the concept, giving some examples and pointing to the best resource in the code to get started. 19 | 20 | Most key features also have self-contained demos, which can be found in [their own source folder](https://github.com/nipafx/LibFX/tree/master/src/demo/java/org/codefx/libfx). 21 | 22 | Finally, there's extensive Javadoc under [libfx.codefx.org/javadoc](http://libfx.codefx.org/javadoc). 23 | 24 | ## Getting LibFX 0.2.1 25 | 26 | You can get **LibFX 0.2.1** here: 27 | 28 | 29 | 30 | ```xml 31 | 32 | org.codefx.libfx 33 | LibFX 34 | 0.2.1 35 | 36 | ``` 37 | 38 | ```groovy 39 | compile 'org.codefx.libfx:LibFX:0.2.1' 40 | ``` 41 | 42 | **LibFX** is licensed under GLP 3.0 but other arrangements can be made - just [shoot me an email](mailto:nicolai@nipafx.dev). 43 | -------------------------------------------------------------------------------- /content/articles/2015-05-11-first-release-of-jdeps-maven-plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "First Release of JDeps Maven Plugin" 3 | tags: [java-9, jdeps, tools, project-jigsaw] 4 | date: 2015-05-11 5 | slug: jdeps-maven-plugin-0-1 6 | description: "The JDeps Maven Plugin will break a project's build if it contains dependencies on JDK-internal APIs. This helps to prepare for Java 9." 7 | intro: "The JDeps Maven Plugin will break a project's build if it contains dependencies on JDK-internal APIs. This helps to prepare for Java 9, where these dependencies will be unaccessible." 8 | searchKeywords: "JDeps Maven Plugin" 9 | featuredImage: jdeps-mvn-motor 10 | repo: jdeps-maven-plugin 11 | --- 12 | 13 | Two weeks ago I wrote about [how Java 9 may break your code](how-java-9-and-project-jigsaw-may-break-your-code). 14 | A substantial obstacle for the transition to Java 9 can be a project's dependencies on JDK-internal APIs. 15 | These will be unaccessible in the new Java version and code containing them will not compile. 16 | It is hence important to weed them out in the remaining time. 17 | (Btw, Java 9 is [scheduled for September 2016](http://mail.openjdk.java.net/pipermail/jdk9-dev/2015-May/002172.html).) 18 | 19 | To help our projects (and maybe yours) with that, I created the [*JDeps Maven Plugin*](https://github.com/nipafx/JDeps-Maven-Plugin). 20 | It breaks the build if the code contains any problematic dependencies. 21 | 22 | ## JDeps 23 | 24 | To help identify problematic dependencies the JDK 8 contains the [Java Dependency Analysis tool *jdeps*](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jdeps.html). 25 | Run against a jar, a folder or a single class it will analyze and output dependencies. 26 | Analysis and output can be configured with several command line options. 27 | 28 | Run as `jdeps -jdkinternals` it will only list the dependencies on JDK-internal API. 29 | Exactly these dependencies are the ones that would break if compiled against Java 9. 30 | It is hence the basis of the Maven plugin. 31 | 32 | ## JDeps Maven Plugin 33 | 34 | The plugin runs `jdeps -jdkinternals` against the compiled classes, parses the output and breaks the build if it contained any dependencies. 35 | 36 | 37 | 38 | ### Configuration 39 | 40 | To use it in a project include this in its pom: 41 | 42 | ```xml 43 | 44 | 45 | ... 46 | 47 | org.codefx.maven.plugin 48 | jdeps-maven-plugin 49 | 0.1 50 | 51 | 52 | 53 | jdkinternals 54 | 55 | 56 | 57 | 58 | ... 59 | 60 | 61 | ``` 62 | 63 | The plugin is extremely simple and the current version neither allows nor requires any further configuration. 64 | 65 | ### Execution 66 | 67 | The plugin will be executed during [the verify phase](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html). 68 | It will hence run in a full build with `mvn verify` (or any later phase). 69 | With `mvn jdeps:jdkinternals` it can be run directly. 70 | 71 | If your project contains any internal dependencies the build will fail with a message like this: 72 | 73 | ``` 74 | [ERROR] Failed to execute goal 75 | org.codefx.maven.plugin:jdeps-maven-plugin:0.1:jdkinternals 76 | (default-cli) on project MavenLab: 77 | [ERROR] Some classes contain dependencies on JDK-internal API: 78 | [ERROR] . org.codefx.lab.ExampleClass 79 | [ERROR] . -> sun.misc.BASE64Decoder [JDK internal API, rt.jar] 80 | [ERROR] . -> sun.misc.Unsafe [JDK internal API, rt.jar] 81 | [ERROR] . org.codefx.lab.AnotherExampleClass 82 | [ERROR] . -> sun.misc.Unsafe [JDK internal API, rt.jar] 83 | [ERROR] . org.codefx.lab.foo.ExampleClassInAnotherPackage 84 | [ERROR] . -> sun.misc.BASE64Decoder [JDK internal API, rt.jar] 85 | ``` 86 | 87 | ### Roadmap 88 | 89 | The plugin fulfills the minimum requirements to be useful: it breaks the build if JDeps discovers any dependencies on JDK-internal APIs. 90 | But this may be inconvenient for larger projects which might already contain some. 91 | It would be nice to be able to configure the plugin so that it ignores certain known dependencies. 92 | 93 | This is the next step. 94 | A simple configuration consisting of elements of the form `org.codefx.lab.ExampleClass -> sun.misc.*` will allow to ignore certain dependencies. 95 | The plugin can hence be included in any build and be configured such that it only breaks when new dependencies are introduced. 96 | 97 | You can track the progress in [this issue](https://github.com/nipafx/JDeps-Maven-Plugin/issues/1). 98 | 99 | ## Existing Plugins 100 | 101 | There are (at least) two Maven plugins which allow to use JDeps. 102 | 103 | **[Maven JDeps by Philippe Marschall](https://github.com/marschall/jdeps-maven-plugin)**: 104 | Runs JDeps against the compiled classes and either prints the output or creates a report from it. 105 | Has no consequences for the build. 106 | 107 | **[Apache Maven JDeps](http://maven.apache.org/plugins-archives/maven-jdeps-plugin-LATEST/maven-jdeps-plugin/)**: 108 | In development. 109 | Seems to be aimed at breaking the build when discovering internal dependencies but this does currently not work. 110 | 111 | I wanted fast results and full control over where this is going for my projects. 112 | I hence decided to reimplement parts of the functionality. 113 | 114 | If this JDeps Maven plugin proves to be useful I will reach out and try to get some code included in either of those plugins (most likely the official one from Apache). 115 | -------------------------------------------------------------------------------- /content/articles/2015-05-28-libfx-0-3-0-released.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "LibFX 0.3.0 Released" 3 | tags: [libfx] 4 | date: 2015-05-28 5 | slug: libfx-0-3-0 6 | description: "Release post for LibFX 0.3.0 including pointers to GitHub, feature descriptions, Maven coordinates and the Javadoc." 7 | searchKeywords: "libfx 0.3.0" 8 | featuredImage: libfx-library 9 | repo: libfx 10 | --- 11 | 12 | I just released [**LibFX 0.3.0**](https://github.com/nipafx/LibFX/releases/tag/v0.3.0)! 13 | 14 | ## Features 15 | 16 | The killer feature are [transforming collections](https://github.com/nipafx/LibFX/wiki/TransformingCollections) about which I already [blogged a few days ago](java-transforming-collections). 17 | I also created an easy way to stream nodes of all kinds of trees. 18 | (I didn't get around to write up a wiki page for it yet but when I do, you will find it [here](https://github.com/nipafx/LibFX/wiki/TreeStreams).) 19 | 20 | And don't forget about the other features: 21 | 22 | **[ControlPropertyListener](https://github.com/nipafx/LibFX/wiki/ControlPropertyListener)**: 23 | Creating listeners for the property map of JavaFX' controls. 24 | 25 | **[ListenerHandle](https://github.com/nipafx/LibFX/wiki/ListenerHandle)**: 26 | Encapsulating an observable and a listener for easier add/remove of the listener ([I blogged about it here](java-listenerhandles)). 27 | 28 | **[Nestings](https://github.com/nipafx/LibFX/wiki/Nestings)**: 29 | Using all the power of JavaFX' properties for nested object aggregations. 30 | 31 | **[SerializableOptional](https://github.com/nipafx/LibFX/wiki/SerializableOptional)**: 32 | Serializable wrapper for `Optional`. 33 | 34 | **[WebViewHyperlinkListener](https://github.com/nipafx/LibFX/wiki/WebViewHyperlinkListener)**: 35 | Add hyperlink listeners to JavaFX' `WebView`. 36 | 37 | ## Getting Started 38 | 39 | The links above point to the [**LibFX** wiki on GitHub](https://github.com/nipafx/LibFX/wiki). 40 | It has an article for each feature explaining the concept, giving some examples and pointing to the best resource in the code to get started. 41 | 42 | Most key features also have self-contained demos, which can be found in [their own source folder](https://github.com/nipafx/LibFX/tree/master/src/demo/java/org/codefx/libfx). 43 | 44 | Finally, there's extensive Javadoc under [libfx.codefx.org/apidocs/](http://libfx.codefx.org/apidocs/). 45 | 46 | ## Getting LibFX 0.3.0 47 | 48 | You can download the jars from the [GitHub release site](https://github.com/nipafx/LibFX/releases/tag/v0.3.0) or use your favorite dependency management system: 49 | 50 | 51 | 52 | ```xml 53 | 54 | org.codefx.libfx 55 | LibFX 56 | 0.3.0 57 | 58 | ``` 59 | 60 | ```groovy 61 | compile 'org.codefx.libfx:LibFX:0.3.0' 62 | ``` 63 | 64 | **LibFX** is licensed under GLP 3.0 but other arrangements can be made - just ping me wherever you find me. 65 | -------------------------------------------------------------------------------- /content/articles/2015-07-06-casting-in-java-8-and-beyond.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Casting In Java 8 (And Beyond?)" 3 | tags: [java-8, optional, streams] 4 | date: 2015-07-06 5 | slug: casting-in-java-8-and-beyond 6 | description: "Proposal to implement new casting methods on Java's `Class`. They aim to fulfill the need for improved ways to cast which was created by Java 8." 7 | intro: "Proposal to implement new casting methods on Java's `Class`. They aim to fulfill the need for improved ways to cast which was created by Java 8's `Optional` and `Stream`." 8 | searchKeywords: "cast" 9 | featuredImage: casting-java-8-and-beyond 10 | --- 11 | 12 | Casting an instance to a type reeks of bad design. 13 | Still, there are situations where there is no other choice. 14 | The ability to do this has hence been part of Java since day one. 15 | 16 | I think Java 8 created a need to slightly improve this ancient technique. 17 | 18 | ## Static Casting 19 | 20 | The most common way to cast in Java is as follows: 21 | 22 | ```java 23 | Object obj; // may be an integer 24 | if (obj instanceof Integer) { 25 | Integer objAsInt = (Integer) obj; 26 | // do something with 'objAsInt' 27 | } 28 | ``` 29 | 30 | This uses the `instanceof` and cast operators, which are baked into the language. 31 | The type to which the instance is cast, in this case `Integer`, must be statically known at compile time, so let's call this static casting. 32 | 33 | If `obj` is no `Integer`, the above test would fail. 34 | If we try to cast it anyways, we'd get a `ClassCastException`. 35 | If `obj` is `null`, it fails the `instanceof` test but could be cast because `null` can be a reference of any type. 36 | 37 | ## Dynamic Casting 38 | 39 | A technique I encounter less often uses the methods on `Class` that correspond to the operators: 40 | 41 | ```java 42 | Object obj; // may be an integer 43 | if (Integer.class.isInstance(obj)) { 44 | Integer objAsInt = Integer.class.cast(obj); 45 | // do something with 'objAsInt' 46 | } 47 | ``` 48 | 49 | Note that while in this example the class to cast to is also known at compile time, this is not necessarily so: 50 | 51 | ```java 52 | Object obj; // may be an integer 53 | Class type = // may be Integer.class 54 | if (type.isInstance(obj)) { 55 | T objAsType = type.cast(obj); 56 | // do something with 'objAsType' 57 | } 58 | ``` 59 | 60 | Because the type is unknown at compile type, we'll call this dynamic casting. 61 | 62 | The outcomes of tests and casts for instances of the wrong type and null references are exactly as for static casting. 63 | 64 | ## Casting In Streams And Optionals 65 | 66 | ### The Present 67 | 68 | Casting the value of an `Optional` or the elements of a `Stream` is a two-step-process: First we have to filter out instances of the wrong type, then we can cast to the desired one. 69 | 70 | With the methods on `Class`, we do this with method references. 71 | Using the example of `Optional`: 72 | 73 | ```java 74 | Optional obj; // may contain an Integer 75 | Optional objAsInt = obj 76 | .filter(Integer.class::isInstance) 77 | .map(Integer.class::cast); 78 | ``` 79 | 80 | That we need two steps to do this is no big deal but I feel like it is somewhat awkward and more verbose than necessary. 81 | 82 | ### The Future (Maybe) 83 | 84 | I propose to implement casting methods on `Class` which return an `Optional` or a `Stream`. 85 | If the passed instance is of the correct type, an `Optional` or a singleton `Stream` containing the cast instance would be returned. 86 | Otherwise both would be empty. 87 | 88 | Implementing these methods is trivial: 89 | 90 | ```java 91 | public Optional castIntoOptional(Object obj) { 92 | if (isInstance(obj)) 93 | return Optional.of((T) obj); 94 | else 95 | Optional.empty(); 96 | } 97 | 98 | public Stream castIntoStream(Object obj) { 99 | if (isInstance(obj)) 100 | return Stream.of((T) obj); 101 | else 102 | Stream.empty(); 103 | } 104 | ``` 105 | 106 | This lets us use `flatMap` to filter and cast in one step: 107 | 108 | ```java 109 | Stream stream; // may contain integers 110 | Stream streamOfInts = stream. 111 | flatMap(Integer.class::castIntoStream); 112 | ``` 113 | 114 | Instances of the wrong type or null references would fail the instance test and would lead to an empty `Optional` or `Stream`. 115 | There would never be a `ClassCastException`. 116 | 117 | ### Costs And Benefits 118 | 119 | What is left to be determined is whether these methods would pull their own weight: 120 | 121 | - How much code could actually use them? 122 | - Will they improve readability for the average developer? 123 | - Is saving one line worth it? 124 | - What are the costs to implement and maintain them? 125 | 126 | I'd answer these questions with *not much*, *a little*, *yes*, *low*. 127 | So it's close to a zero-sum game but I am convinced that there is a small but non-negligible benefit. 128 | 129 | What do you think? 130 | Do you see yourself using these methods? 131 | -------------------------------------------------------------------------------- /content/articles/2015-08-04-all-about-project-jigsaw-on-infoq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "All About Project Jigsaw On InfoQ" 3 | tags: [java-next, java-9, project-jigsaw] 4 | date: 2015-08-04 5 | slug: project-jigsaw-on-infoq 6 | description: "My posts about Project Jigsaw got polished and published on InfoQ." 7 | searchKeywords: "" 8 | featuredImage: project-jigsaw-on-infoq 9 | --- 10 | 11 | Over the last few weeks I had the opportunity to polish [my posts about Project Jigsaw](tag:project-jigsaw) for InfoQ. 12 | The result was published today: 13 | 14 | **[Project Jigsaw is Really Coming in Java 9](http://www.infoq.com/articles/Project-Jigsaw-Coming-in-Java-9)** 15 | 16 | Besides refining what I wrote about [motivation, goals](motivation-goals-project-jigsaw), [the core concept, features](features-project-jigsaw) and [risks](how-java-9-and-project-jigsaw-may-break-your-code) it casts light on the history and current structure, and presents the strawman syntax from [JEP 200](http://openjdk.java.net/jeps/200). 17 | If you haven't read much about Jigsaw, this is your chance to get a comprehensive view of it. 18 | 19 | And thanks to [Victor Grazi](http://www.infoq.com/author/Victor-Grazi) for his excellent editing and perseverance. 20 | It improved and tightened the text noticeably. 21 | 22 | ## Next Steps 23 | 24 | Working on the article took a lot of time. 25 | (Which is why I count this lousy announcement here as one of the three posts I want to publish each month.) But now that I am done with that the road is clear to experimenting with the module system itself! 26 | 27 | This be possible as soon as the current work on [JSR 376](https://www.jcp.org/en/jsr/detail?id=376) is merged into the OpenJDK repositories, which can happen [any day now](http://mail.openjdk.java.net/pipermail/jigsaw-dev/2015-July/004393.html). 28 | 29 | Stay tuned. 30 | -------------------------------------------------------------------------------- /content/articles/2015-08-14-interview-about-comments-on-dzone.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Interview About Comments On DZone" 3 | tags: [clean-comments, documentation] 4 | date: 2015-08-14 5 | slug: interview-about-comments-on-dzone 6 | description: "Matt Werner from DZone interviewed me about my stance on comments." 7 | searchKeywords: "" 8 | featuredImage: comment-your-fucking-code 9 | --- 10 | 11 | Forgetful me. 12 | About three weeks ago [Matt Werner from DZone](https://dzone.com/users/1057629/mwerner.html) gave me a chance to ramble about [my rant on commenting your fucking code](comment-your-fucking-code). 13 | We did this in form of a Q&A, which went up on DZone last week. 14 | Here you go: 15 | 16 | **[You Should Still Comment Your Code](https://dzone.com/articles/you-should-still-comment-your-code-a-qa-with-nicol)** 17 | 18 | Luckily I was given the chance to smooth out the rough parts, so it reads better than I sound. 19 | 20 | ## Why Even Talk About Comments? 21 | 22 | If you've read my [rant](comment-your-fucking-code) and my following [thoughts on comments](thoughts-on-comments), you know that my perspective is at odds with what many people think about comments in clean code. 23 | But there are also many people which share my distrust towards the "comments are a failure" position, that was mainly inspired by Uncle Bob's Clean Code. 24 | 25 | So yes, I see reasons to continue talking about comments. 26 | Over the next couple of posts (about this topic; don't fret, there will be others) I will try to carve out a position that reconciles my tendency to overdocument with the utter lack of helpful documentation I see on a daily basis. 27 | -------------------------------------------------------------------------------- /content/articles/2015-11-09-javaone-2015-introduction-to-modular-development.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "JavaOne 2015: Introduction to Modular Development" 3 | tags: [java-next, impulse, java-9, community, project-jigsaw] 4 | date: 2015-11-09 5 | slug: javaone-2015-introduction-to-modular-development 6 | description: "JavaOne 2015 saw a series of talks by the Project Jigsaw team about modularity in Java 9. This one introduces the basic concepts." 7 | searchKeywords: "JavaOne" 8 | featuredImage: javaone-project-jigsaw-introduction-sf 9 | --- 10 | 11 | 12 | 13 | After [preparing for JDK 9](javaone-2015-prepare-for-jdk-9) let's continue with an introduction to modular development! 14 | 15 | - **Content**: Introduction to the module system and the concept of modules 16 | - **Speaker**: Alan Bateman 17 | - **Links**: [Video](https://www.youtube.com/watch?v=8RhwmJlZQgs&t=4h25m19s) and [Slides](http://openjdk.java.net/projects/jigsaw/j1/intro-modular-dev-j1-2015.pdf) 18 | 19 | 20 | 21 | ## What Is A Module? 22 | 23 | [PLAY](https://www.youtube.com/watch?v=8RhwmJlZQgs#t=4h26m38s) 24 | Alan Bateman starts by explaining the basic concept of modules as named, self describing collections of code and data. 25 | This part is more than covered by [The State Of The Module System](http://openjdk.java.net/projects/jigsaw/spec/sotms/) (SOTMS): 26 | 27 | - [Modules](http://openjdk.java.net/projects/jigsaw/spec/sotms/#modules) and [module declarations](http://openjdk.java.net/projects/jigsaw/spec/sotms/#module-declarations) 28 | - [Module graphs](http://openjdk.java.net/projects/jigsaw/spec/sotms/#module-graphs) 29 | - [Readability](http://openjdk.java.net/projects/jigsaw/spec/sotms/#readability) and [implied readability](http://openjdk.java.net/projects/jigsaw/spec/sotms/#implied-readability) 30 | - [Accessibility](http://openjdk.java.net/projects/jigsaw/spec/sotms/#accessibility) 31 | 32 | ## Platform Modules 33 | 34 | [PLAY](https://www.youtube.com/watch?v=8RhwmJlZQgs#t=4h39m58s) 35 | Platform modules are the ones that make up the JDK - [SOTMS explains them](http://openjdk.java.net/projects/jigsaw/spec/sotms/#platform-modules) as well. 36 | Their dependency graph is shown on [Slide 19](http://openjdk.java.net/projects/jigsaw/j1/intro-modular-dev-j1-2015.pdf#page=19): 37 | 38 | 39 | 40 | A [very similar graph](https://bugs.openjdk.java.net/secure/attachment/21573/jdk-tr.png) that includes the OpenJDK-specific modules can be found in [JEP 200](http://openjdk.java.net/jeps/200). 41 | 42 | Bateman also mentions `java -listmods`, which will list all the available platform modules. 43 | (Note that there are [discussions on the mailing list](http://mail.openjdk.java.net/pipermail/jigsaw-dev/2015-October/005042.html) to rename the flag.) 44 | 45 | ## Command Line 46 | 47 | [PLAY](https://www.youtube.com/watch?v=8RhwmJlZQgs#t=4h42m39s) 48 | After explaining the [module path](http://openjdk.java.net/projects/jigsaw/spec/sotms/#module-path) Bateman gives an introduction to the various new command line options. 49 | The [Jigsaw quick-start guide](http://openjdk.java.net/projects/jigsaw/quick-start) has us covered here. 50 | 51 | An option the guide does not mention is `java -Xdiag:resolver`, which outputs additional information regarding module dependency resolution. 52 | 53 | ## Packaging As Modular JAR 54 | 55 | [PLAY](https://www.youtube.com/watch?v=8RhwmJlZQgs#t=4h49m54s) 56 | Modules can be packaged into so called modular JARs, which the [quick-start guide](http://openjdk.java.net/projects/jigsaw/quick-start#packaging) covers as well. 57 | 58 | Bateman stresses that such JARs work both on the module path in Java 9 as well as on the class path in Java 8 (as long as they target 1.8). 59 | He also quickly shows how the module path and class path can be mixed to launch a program. 60 | 61 | ## Linking 62 | 63 | [PLAY](https://www.youtube.com/watch?v=8RhwmJlZQgs#t=4h56m13s) 64 | Linking allows to bundle some modules and all of their transitive dependencies into a run-time image. 65 | If the initial modules are platform modules, the result will essentially be a variant of the JDK. 66 | This is in fact how the current Jigsaw builds are being created. 67 | 68 | This is done with the new tool *jlink* and the [quick-start guide](http://openjdk.java.net/projects/jigsaw/quick-start#linker) shows how to do it. 69 | 70 | ## Questions 71 | 72 | There were a couple of interesting questions. 73 | 74 | ### Is There Any Solution For Optional Dependencies? 75 | 76 | [PLAY](https://www.youtube.com/watch?v=8RhwmJlZQgs#t=5h04m43s) 77 | In earlier Jigsaw prototypes there was a notion of *optional dependencies*. 78 | Working out the precise semantics turned out to be hard so the feature was not implemented. 79 | Research showed that optional dependencies can typically be refactored to services that might or might not be present at runtime. 80 | 81 | Services are covered by the [quick-start guide](http://openjdk.java.net/projects/jigsaw/quick-start#services). 82 | 83 | ### Can jlink Cross-Compile? 84 | Can It Create A Self-Executing File? 85 | 86 | [PLAY](https://www.youtube.com/watch?v=8RhwmJlZQgs#t=5h05m39s) 87 | "Yes" and "Not directly but other tools will be improved so that will be doable in the future". 88 | 89 | ### Can Modules Be Versioned? 90 | 91 | [PLAY](https://www.youtube.com/watch?v=8RhwmJlZQgs#t=5h07m42s) 92 | Long story short: "Versions are hard, we don't want to replicate build tool functionality, so 'No'". 93 | For more, listen to Mark Reinhold's full answer. 94 | 95 | ### Can jlink Use Cross-Module Optimizations? 96 | 97 | [PLAY](https://www.youtube.com/watch?v=8RhwmJlZQgs#t=5h10m22s) 98 | Yes. 99 | 100 | ### How Does JavaDoc Handle Modules? 101 | 102 | [PLAY](https://www.youtube.com/watch?v=8RhwmJlZQgs#t=5h14m37s) 103 | JavaDoc will be upgraded so that it understands what modules are. 104 | It will display them along with packages and classes. 105 | And it will also by default not generate documentation for types in not-exported packages. 106 | -------------------------------------------------------------------------------- /content/articles/2015-12-02-delay-of-java-9-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Six-Month Delay Of Java 9 Release" 3 | tags: [java-next, java-9, project-jigsaw] 4 | date: 2015-12-02 5 | slug: delay-of-java-9-release 6 | description: "Mark Reinhold proposed a six-month delay of JSR 376 / Project Jigsaw and thus of the Java 9 release. According to this JDK 9 would come out in March 2017." 7 | searchKeywords: "Java 9 Release" 8 | featuredImage: delay-of-Java-9-release-proposed 9 | --- 10 | 11 | Yesterday evening Mark Reinhold, Chief Architect of the Java Platform Group at Oracle and Specification Lead of [JSR 376](https://www.jcp.org/en/jsr/detail?id=376), for which Project Jigsaw is the current prototype, proposed a six-month extension of the schedules [for the JSR](http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2015-December/000233.html) and [for Java 9](http://mail.openjdk.java.net/pipermail/jdk9-dev/2015-December/003149.html) release. 12 | 13 | ## Proposal 14 | 15 | The interleaved schedule proposals for modularity and Java 9 look as follows: 16 | 17 | - **2016-01** — JSR 376: Early Draft Review 18 | - **2016-05** — JDK 9: Feature Complete 19 | - **2016-06** — JSR 376: Public Review 20 | - **2016-08** — JDK 9: Rampdown Start 21 | - **2016-10** — JDK 9: Zero Bug Bounce 22 | - **2016-12** — JSR 376: Proposed Final Draft 23 | - **2016-12** — JDK 9: Rampdown Phase 2 24 | - **2017-01** — JDK 9: Final Release Candidate 25 | - **2017-03** — JSR 376: Final Release 26 | - **2017-03** — JDK 9: General Availability 27 | 28 | The definition for the JDK 9 milestones are [the same as for JDK 8](http://openjdk.java.net/projects/jdk8/milestones#definitions) and worth a read. 29 | Especially feature complete, which means that "\[a\]ll features have been implemented and integrated into the master forest, together with unit tests." It does not mean that development stops. 30 | Instead reckless improvements are still possible, at least until rampdown phases start, in which "increasing levels of scrutiny are applied to incoming changes." 31 | 32 | The proposals are up for debate until December 8th but I'd be very surprised to not see them become the new schedule. 33 | 34 | ## Reason 35 | 36 | The reasons for this delay clearly are JSR 376 and Jigsaw: 37 | 38 | > In the current JDK 9 schedule [\[7\]](http://openjdk.java.net/projects/jdk9/) the Feature Complete milestone is set for 10 December, less than two weeks from today, but Jigsaw needs more time. 39 | The JSR 376 EG has not yet published an Early Draft Review specification, the volume of interest and the high quality of the feedback received over the last two months suggests that there will be much more to come, and we want to ensure that the maintainers of the essential build tools and IDEs have adequate time to design and implement good support for modular development. 40 | > 41 | > [Mark Reinhold - 1 Dec 2015](http://mail.openjdk.java.net/pipermail/jdk9-dev/2015-December/003149.html) 42 | 43 | This makes a lot of sense. 44 | And while I think highly of the current prototype there is still lots of work to do. 45 | The additional six months will give the engineers more time to address the various problems and improve migration compatibility. 46 | 47 | > As with previous schedule changes, the intent here is not to open the gates to a flood of new features unrelated to Jigsaw, nor to permit the scope of existing features to grow without bound. 48 | It would be best to use the additional time to stabilize, polish, and fine-tune the features that we already have rather than add a bunch of new ones. 49 | The later FC milestone does apply to all features, however, so reasonable proposals to target additional JEPs to JDK 9 will be considered so long as they do not add undue risk to the overall release. 50 | > 51 | > [Mark Reinhold - 1 Dec 2015](http://mail.openjdk.java.net/pipermail/jdk9-dev/2015-December/003149.html) 52 | 53 | The additional time might help in convincing some of the more critical members of the community. 54 | As it currently stands there are even members of the JSR 376 expert group which are openly opposing the path Jigsaw took. 55 | The common counter proposal is solely based on class loaders as used by, e.g., OSGi implementations. 56 | -------------------------------------------------------------------------------- /content/articles/2016-03-07-atom-on-gentoo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Building Atom On Gentoo" 3 | tags: [tools] 4 | date: 2016-03-07 5 | slug: atom-on-gentoo 6 | description: "See how to build Atom on Gentoo straight from the sources." 7 | intro: "While not particularly hard, building Atom on Gentoo is a little elusive and poorly documented. Look here for enlightenment!" 8 | searchKeywords: "atom on gentoo" 9 | featuredImage: atom-on-gentoo 10 | --- 11 | 12 | I've been very happy with my [switch to Gentoo](hello-2016#linux). 13 | One of the cool things is that I can use the package manager for most development tools as [Portage](https://wiki.gentoo.org/wiki/Portage) contains up-to-date versions of most of them. 14 | (Case in point, I just updated Node.js to a version that was released just four days ago.) All the more shocked was I when I found out that [Atom](https://atom.io/) isn't one of them and that information on how to install Atom on Gentoo is elusive. 15 | 16 | Maybe because in the end the process isn't that complicated. 17 | Basically I just had to build from source, mostly following the [documentation on how to do that](https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#linux). 18 | I'll still repeat it here to have it readily available when I have to update. 19 | 20 | ## Building Atom 21 | 22 | These steps worked for [Atom 1.5.3](https://github.com/atom/atom/releases/tag/v1.5.3). 23 | 24 | ### Requirements 25 | 26 | The first three requirements listed in the [documentation](https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#linux) are a 32/64 bit OS, the C++ tool chain and Git. 27 | Every Gentoo install covers that. 28 | 29 | Then come [Node.js](http://nodejs.org/download/) and its package manager [npm](https://www.npmjs.com/), which comes bundled with it. 30 | I [unmasked](https://wiki.gentoo.org/wiki/Knowledge_Base:Unmasking_a_package) all 5.x releases of Node (with `=net-libs/nodejs-5*`) but you should get a sufficiently new version even if you don't do that - make sure to check, though. 31 | 32 | Next on the list are the development headers for the [GNOME keyring](https://wiki.gnome.org/Projects/GnomeKeyring), which is the only non-obvious (and generally somewhat peculiar) step. 33 | Emerging `gnome-base/libgnome-keyring-3.12.0` fixed this for me. 34 | 35 | ### Build & Install 36 | 37 | These are just the [install instructions](https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#instructions): 38 | 39 | ```shell 40 | git clone https://github.com/atom/atom 41 | cd atom 42 | git checkout v1.5.3 43 | script/build 44 | sudo script/grunt install 45 | ``` 46 | 47 | Have fun! 48 | 49 | 50 | 51 | ## Alternatives 52 | 53 | For an install that is better integrated with the OS you might want to use a real [ebuild](https://devmanual.gentoo.org/quickstart/). 54 | 55 | There are a couple of [Portage overlays containing Atom](https://gpo.zugaina.org/app-editors/atom), most noteworthy the ones belonging to the Gentoo-based distributions [Sabayon](https://en.wikipedia.org/wiki/Sabayon_Linux) and [Funtoo](https://en.wikipedia.org/wiki/Funtoo_Linux) but also others. 56 | I opted against this solution because using the overlay of another distribution looks like a troublesome move to a novice and using "some guy's overlay" doesn't sit well with my general paranoia. 57 | 58 | But you can also do it yourself - or so [Till claims](atom-on-gentoo) in the comments. 59 | If I find some time I'll try it and update this post. 60 | -------------------------------------------------------------------------------- /content/articles/2016-07-25-oh-no-forgot-streamiterate.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Oh No, I Forgot `Stream::iterate`!" 3 | tags: [java-9, streams] 4 | date: 2016-07-25 5 | slug: java-9-stream-iterate 6 | description: "In Java 9 `Stream` gets a couple of new methods - one of them is an overload of `iterate` that takes a predicate and returns a finite stream." 7 | intro: "There I go babbling about new stream methods and then I forget one: a `Stream::iterate` overload that produces a finite stream." 8 | searchKeywords: "stream iterate" 9 | featuredImage: java-9-stream 10 | --- 11 | 12 | There I go talking about the new things that [Java 9 will bring to the stream API](java-9-stream) and then I forget one: a new overload for `iterate`. 13 | [D'oh!](https://www.youtube.com/watch?v=cnaeIAEp2pU) I updated that post but to make sure you don't miss it, I also put it into this one. 14 | 15 | ## `Stream::iterate` 16 | 17 | `Stream` already has a method `iterate`. 18 | It's a static factory method that takes a seed element of type `T` and a function from `T` to `T`. 19 | Together they are used to create a `Stream` by starting with the seed element and iteratively applying the function to get the next element: 20 | 21 | ```java 22 | Stream.iterate(1, i -> i + 1) 23 | .forEach(System.out::println); 24 | // output: 1 2 3 4 5 ... 25 | ``` 26 | 27 | Great! 28 | But how can you make it stop? 29 | Well, you can't, the stream is infinite. 30 | 31 | Or rather you *couldn't* because this is where the new overload comes in. 32 | It has an extra argument in the middle: a predicate that is used to assess each element before it is put into the stream. 33 | As soon as the first elements fails the test, the stream ends: 34 | 35 | ```java 36 | Stream.iterate(1, i -> i <= 3, i -> i + 1) 37 | .forEach(System.out::println); 38 | // output: 1 2 3 39 | ``` 40 | 41 | As it is used above, it looks more like a traditional for loop than the more succinct but somewhat alien `IntStream.rangeClosed(1, 3)` (which I still prefer but YMMV). 42 | It can also come in handy to turn "iterator-like" data structures into streams, like the ancient `Enumeration`: 43 | 44 | ```java 45 | Enumeration en = // ... 46 | if (en.hasMoreElements()) { 47 | Stream.iterate( 48 | en.nextElement(), 49 | el -> en.hasMoreElements(), 50 | el -> en.nextElement()) 51 | .forEach(System.out::println); 52 | } 53 | ``` 54 | 55 | You could also use it to manipulate a data structure while you stream over it, like [popping elements off a stack](http://stackoverflow.com/q/38159906/2525313). 56 | This not generally advisable, though, because the source may end up in a surprising state - you might want to discard it afterwards. 57 | 58 | 59 | 60 | Not True! 61 | Turns out neither the `Enumeration` above nor the `Stack` mentioned in the link can be streamed like this - at least not fully. 62 | The predicate (in our cases `el -> en.nextElement()` and `el -> stack.pop()`) is evaluated *after* an element was taken from the source. 63 | This is in line with how the traditional `for`-loop works but has an unfortunate effect. 64 | 65 | After taking the last element from the source but before pushing it into the stream, the predicate is consulted and returns false because there is no element *after* the last one. 66 | The element does hence not appear in the stream, which means the last element is always missing. 67 | 68 | Thanks to Piotr for pointing this out! 69 | 70 | 71 | -------------------------------------------------------------------------------- /content/articles/2016-08-04-goodbye-disy-hello-sitepoint.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Goodbye Disy, Hello SitePoint" 3 | tags: [meta, personal] 4 | date: 2016-08-04 5 | slug: goodbye-disy-hello-sitepoint 6 | description: "I worked for Disy for about 2 years. Now I said goodbye to become the editor of SitePoint's burgeoning Java channel and have more time for other projects." 7 | featuredImage: goodbye-disy-hello-sitepoint 8 | --- 9 | 10 | I've been working for [Disy](http://www.disy.net/en/home.html) for about 2 years. 11 | During that time my side projects [have grown and grown](codefx-levels-up) and taken up ever more of my free time. 12 | When [SitePoint](http://sitepoint.com/) offered me a part-time position as an editor, I did a 180 - now I have a couple of projects and a side job. 13 | 14 | ## Goodbye Disy 15 | 16 | Working at Disy was a lot of fun! 17 | The technical day to day work never quite got me but we had a lot of very interesting meta-problems to solve. 18 | 19 | [A monolith](http://www.disy.net/en/products/cadenza.html) with about 1.5 million lines of code split across \~400 Maven modules poses challenges that you won't learn to solve from coding up a [to-do-list-app](https://medium.com/@filiph/the-hello-world-fallacy-ef4f43ca8b7e#.pvppu1rv0) in the freshest fad. 20 | Fast(ish) reliable builds, dependency management, shared code ownership, code quality, tool independence - all of these and more are much harder to achieve when you're not just a handful of devs hacking together a web app in twelve months. 21 | 22 | (I know, I know, "Why don't you just build microservices?" Has anyone even tried that with a Swing app? 23 | No? 24 | Hmm, I wonder why...) 25 | 26 | More than anything else I learned to be conservative when it comes to libraries, frameworks, tools, patterns, everything really. 27 | Even a little cynical maybe - seeing so many of them fall to pieces when used on a large code base made me wonder whether they were even meant to be used for real-world projects. 28 | I consider this experience an extremely valuable one to have made. 29 | 30 | And then there were the people and, by extension, the company, which were plain awesome! 31 | No longer working with them everyday was the hardest part of saying goodbye to Disy. 32 | 33 | Guys and gals, I will miss you! 34 | 35 | ## Hello SitePoint 36 | 37 | SitePoint is building a Java channel and asked me to become its editor. 38 | Oh, the pride! 39 | And it's right up my alley, too! 40 | I've built three blogs (this one, [Do-Foss](http://blog.do-foss.de/en/), and [Disy's TechBlog](https://blog.disy.net/)) and feel right at home in the Java community, This offer gave me an opportunity to build on this and make it a more integral part of my life. 41 | 42 | So since Monday I've been doing my new thing: finding authors, curating content, and generally scheming to make the channel a great success. 43 | 44 | "So where is it?" you might ask and my answer would be "Not quite there yet." I will tentatively point you towards [the Java tag](https://www.sitepoint.com/tag/java-2/) but with two caveats: 45 | 46 | - We're still building valuable content. 47 | - Don't bookmark anything yet, another URL might supersede this one. 48 | 49 | If you don't want to miss the big unveiling, follow me. 50 | 51 | ## What Else? 52 | 53 | So what are the other projects I was talking about? 54 | 55 | ### Java Module System in Action 56 | 57 | Yes, it's still on... 58 | I woefully neglected working on it but with more free time now, I made it my top priority after SitePoint. 59 | 60 | ### CodeFX 61 | 62 | I will of course continue to write here. 63 | I can't wait to bore you with more details about [JUnit 5](tag:junit-5), [Java 9](tag:java-9)/[Project Jigsaw](tag:project-jigsaw), or [Clean Comments](tag:clean-comments). 64 | Heck, I might even write about something else one day. 65 | 66 | That's actually one thing I planned. 67 | My list of ideas contains a lot of topics that are not immediately technical but address development as a whole. 68 | I've always been interested in the cultural and social aspects of creating software and can't wait to share my thoughts. 69 | On Disy's blog I already started to write about [how we/they do code reviews](https://blog.disy.net/code-reviews/) - a series, which I will soon cross-post here. 70 | 71 | ### Bla, Bla, Bla 72 | 73 | Yes, I do talks: 74 | 75 | - [past talks](past-talks) 76 | - [upcoming talks](schedule) 77 | 78 | (If you know me, you're now thinking "Of course you are... *roll-eyes*".) 79 | 80 | ### For Hire 81 | 82 | Turns out that knowledge about Java 9 and JUnit 5 interests other people as well (who would've thought?). 83 | If you or your company are of such a curious persuasion and would like to get some help on planning migrations or training you and your fellow developers, you can hire me for that - just [drop me a mail](mailto:nicolai@nipafx.dev). 84 | 85 | ### Err, coding? 86 | 87 | Ah, yes. 88 | Looks like I'm no longer a coder who writes - technically I'm now a writer/editor who ... codes? 89 | Do I? 90 | Well, it's a passion, so I'm sure it'll push its way through. 91 | In fact, just yesterday I started to work on [a new side project](https://github.com/CodeFX-org/WorkFlowyFX), which uses - take a deep breath - JavaScript. 92 | Shocker! 93 | 94 | But I have to make sure that I have time left to code. 95 | Otherwise I'll just end up an author writing about things he has no clue about. 96 | And I hate those! 97 | -------------------------------------------------------------------------------- /content/articles/2016-09-05-ultimate-guide-java-9.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "The Ultimate Guide To Java 9" 3 | tags: [java-next, java-9] 4 | date: 2016-09-05 5 | slug: ultimate-guide-java-9 6 | canonicalUrl: https://www.sitepoint.com/ultimate-guide-to-java-9/ 7 | description: "Java 9 is coming! Besides Jigsaw it brings new language features and many new and improved APIs. Check out the ultimate guide." 8 | intro: "I'm sure you've heard this and that about Java 9: modularity, collection literals (or not?), private methods on interfaces, etc. But if you really want to know all that's coming, you need this ultimate guide." 9 | searchKeywords: "" 10 | featuredImage: ultimate-guide-to-java-9 11 | --- 12 | 13 | Today was the grand opening of [SitePoint's Java channel](http://sitepoint.com/java) and we kicked it off with [the ultimate guide to Java 9](https://www.sitepoint.com/ultimate-guide-to-java-9/). 14 | We left out Project Jigsaw because [so much has already been written about it](tag:project-jigsaw) and focused on everything else - and there's *a lot* of it! 15 | 16 | - Language Changes 17 | - Private Interface (Default) Methods 18 | - Try-With-Resources on Effectively Final Variables 19 | - Diamond Operator for Anonymous Classes 20 | - SaveVarargs on Private Methods 21 | - No More Deprecation Warnings for Imports 22 | - APIs 23 | - OS Processes 24 | - Multi-Resolution Images 25 | - Stack Walking 26 | - Redirected Platform Logging 27 | - Reactive Streams 28 | - Collection Factory Methods 29 | - Native Desktop Integration 30 | - Deserialization Filter 31 | - Networking 32 | - HTTP/2 33 | - Datagram Transport Layer Security (DTLS) 34 | - TLS Application-Layer Protocol Negotiation Extension (TLS ALPN) 35 | - OCSP Stapling for TLS 36 | - XML 37 | - OASIS XML Catalogs Standard 38 | - Xerces 2.11.0 39 | - Extensions to Existing APIs, e.g. 40 | - [Optional](java-9-optional), [Stream](java-9-stream), and Collectors 41 | - DateTime API 42 | - Matcher 43 | - Atomic… 44 | - Array utilities 45 | - Low Level APIs 46 | - Variable Handles Aka VarHandles 47 | - Enhanced Method Handles 48 | - Dynalink 49 | - Nashorn Parser API 50 | - Spin-Wait Hints 51 | - Deprecations 52 | - Applet API 53 | - Corba 54 | - Observer, Observable 55 | - SHA-1 56 | - Removals 57 | 58 | Now, don't tell me, you're not curious! 59 | Go check it out: 60 | 61 | **[The Ultimate Guide to Java 9](https://www.sitepoint.com/ultimate-guide-to-java-9/)** 62 | -------------------------------------------------------------------------------- /content/articles/2016-11-24-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SPJCN I: Hello World" 3 | tags: [community] 4 | date: 2016-11-24 5 | slug: spjcn-hello-world 6 | canonicalText: "I originally wrote this for Sitepoint's Java newsletter, but this issue never got published online." 7 | description: "In the first issue of SitePoint's Java Channel Newsletter (September 9th 2016) I babble about community and conferences." 8 | featuredImage: spjcn-i-new-york 9 | --- 10 | 11 | Hello World, and welcome to our Java newsletter - not just any but the very first! 12 | Definitely a reason to celebrate so I'm having a beer. 13 | If you want to get yourself a drink as well, now would be a good time. 14 | I'll wait here and think about what to write next. 15 | 16 | ## Introductions 17 | 18 | Since we'll be spending some time together, why don't I start by introducing myself? 19 | My name's Nicolai and I'm your editor (don't tell SitePoint, they think I'm *their* editor). 20 | I'll be making sure that [the Java channel](https://sitepoint.com/java) is brimming with relevant content and will work hard to make it your go-to source for everything Java. 21 | On top of that, they'll let me out of my cage every two weeks to crank out one of these editorials. 22 | 23 | I've been coding Java on and off since 2001 and began doing it professionally in 2011. 24 | Being a know-it-all I started blogging in 2014, which led me to becoming a part-time author and eventually an editor. 25 | If you want to know more (and why wouldn't you?), you can find me at [nipafx.dev](https://nipafx.dev). 26 | To get in touch use [mail](mailto:nicolai.parlog@sitepoint.com), [Twitter](https://twitter.com/nipafx), or, if you absolutely have to, [Google+](https://google.com/+NicolaiParlog). 27 | 28 | ## So What Is This About? 29 | 30 | There are 9 million people out there using Java \[citation needed\]. 31 | Frankly, thinking about that blows my mind. 32 | Imagine being in New York City, Cairo, or Bangkok and *everyone* is a Java developer! 33 | 34 | Wherever you go, whoever you talk to, everybody's having a screen right next to them, showing IntelliJ, Eclipse, or Netbeans. 35 | Red for failing tests, green for passing, white-on-black consoles trailing logs or watching Maven download the world. 36 | Jenkins serves freshly finished builds. 37 | In your office, on the streets, in the coffee shop, at parties - everybody's hacking away at some Java project. 38 | 39 | Are you wondering about how to manage exceptions in stream pipelines, whether SonarQube makes FindBugs obsolete, how to tune the garbage collector, or whether Java 9 will break your project? 40 | Imagine you could strike up a conversation with literally everybody you meet in this Java metropolis of ours. 41 | 42 | Now, unfortunately we're not all living in the same city. 43 | We're dispersed over all the continents and what holds us together are the conferences, mailing lists, private and company blogs, sites like SitePoint, newsletters, courses, and whatnot. 44 | They are constantly and chaotically moving Java along. 45 | 46 | But I know that you're busy - working, learning, living takes time and following what's going in the community comes on top. 47 | So here's my proposal: I will put my ear to the ground, curate and summarize what I hear, and report back to you every two weeks. 48 | Your task is to get in touch with me about anything you find interesting and of course to enjoy reading the newsletter. 49 | What do you think - do we have a deal? 50 | 51 | Ok, let's go then! 52 | I've been blabbering about community a lot so why not continue down that road? 53 | 54 | ## Conference Season 55 | 56 | After its seasonal break, the conference carousel is picking up again. 57 | During the next couple of months a lot of great meetings are taking place all over the world - just to name a few: 58 | 59 | - [JavaZone](https://javazone.no/), Oslo, last couple of days 60 | - [JAXLondon](https://jaxlondon.com/), London, October 10th to 12th 61 | - [GeeCON](http://www.geecon.org/), Pargue, October 20th and 21st 62 | - [Devoxx Morocco](https://www.devoxx.ma/), Casablanca, November 1st to 3rd 63 | - [Devoxx Belgium](https://www.devoxx.be/), Antwerpen, November 7th to 11th 64 | - [QCon](http://www.qconferences.com/) in Shanghai, San Francisco, and Tokyo 65 | 66 | But one overshadows them all: [JavaOne](https://www.oracle.com/javaone/index.html). 67 | From September 18th to 22nd thousands of Java-ists will meet in San Francisco to discuss everything Java. 68 | Don't you wish someone would pay for you and send you there? 69 | I sure do... but, alas, nobody does so we have to content ourselves with YouTube, where the talks will show up soon after they were held. 70 | I'll binge watch as many as I can and come back with a list of recommendations - including yours if you liked a particular talk and told me about it. 71 | 72 | In the meantime you could check out [our summary of the Java Virtual Machine Language Summit](https://www.sitepoint.com/jvmls-2016/), which took place in the first week of August in Santa Clara. 73 | A great panel of speakers discussed the future of Java and the JVM, mostly related to the projects Jigsaw (modularity), Valhalla (value types and generics over primitives), and Panama (going native). 74 | While targeting JVM aficionados there are a lot of takeaways for regular developers like you and me as well. 75 | 76 | ## Wrapping Things Up 77 | 78 | Let me leave you with a couple of articles I think you might find interesting: 79 | 80 | - [The Ultimate Guide to Java 9](https://www.sitepoint.com/ultimate-guide-to-java-9/) 81 | - [HTTP/2 Client in Java 9](https://getpocket.com/redirect?url=http%3A%2F%2Fblog.oio.de%2F2016%2F08%2F24%2Fhttp2-client-java-9%2F&formCheck=887e26de4268d0ee02dc2a1e9f0a9833) 82 | - [Java, Unicode, and the Mysterious Compile Error](https://www.sitepoint.com/java-unicode-mysterious-compile-error/) 83 | - [Using jOOλ to Combine Several Java 8 Collectors into One](https://blog.jooq.org/2016/08/29/using-joo%CE%BB-to-combine-several-java-8-collectors-into-one/) 84 | 85 | I wish you a great time! 86 | 87 | so long ... Nicolai 88 | -------------------------------------------------------------------------------- /content/articles/2017-08-14-planning-your-java-9-update.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Planning Your Java 9 Update" 3 | tags: [java-9, jdeps, migration] 4 | date: 2017-08-14 5 | slug: planning-your-java-9-update 6 | description: "A Java 9 update is not always trivial; quite the opposite, migrating to Java 9 can be challenging. Here's how to gather and categorize problems." 7 | intro: "Time to put your Java 9 knowledge into practice and plan your applications migration. Here's how to get an overview of what needs to be done." 8 | searchKeywords: "Java 9 update" 9 | featuredImage: java-9-migration-planning 10 | --- 11 | 12 | You've read [the module system tutorial](java-module-system-tutorial) and [the Java 9 migration guide](java-9-migration-guide) and now you're ready to start your Java 9 update? 13 | Great! 14 | Here are some tips on how to get a sense of what's awaiting you. 15 | 16 | ## Looking For Trouble 17 | 18 | There are two obvious steps to take to gather the first data points for how challenging your update will be: 19 | 20 | - Run your regular build entirely on Java 9, ideally in a way that lets you gather all errors instead of stopping at the first. 21 | - If you're developing an application, build it as you do normally (meaning, not yet on Java 9) and then run it on Java 9. 22 | Consider using `--illegal-access=debug` or `deny` to get more information on illegal access. 23 | 24 | Carefully analyze the output, take note of new warnings and errors and try to link them to what you know about [migration challenges](java-9-migration-guide). 25 | Also look out for warnings or errors due to removed command line options. 26 | 27 | It is a good idea to apply some quick fixes like [adding exports](java-9-migration-guide#illegal-access-to-internal-apis) or [Java EE modules](java-9-migration-guide#dependencies-on-java-ee-modules). 28 | This allows you to see the tougher problems that may be hiding behind benign ones. 29 | In this phase, no fix is too dirty or too hacky - anything that gets the build to throw a new error is a victory. 30 | If you get too many compile errors, you could compile with Java 8 and just run the tests on Java 9. 31 | 32 | Then [run JDeps](jdeps-tutorial-analyze-java-project-dependencies) on your project *and your dependencies*. 33 | Analyze dependencies on [JDK-internal APIs](java-9-migration-guide#illegal-access-to-internal-apis) and take not of any [Java EE modules](java-9-migration-guide#dependencies-on-java-ee-modules). 34 | Also look for [split packages between platform modules and application JARs](java-9-migration-guide#split-packages). 35 | A good way to get started are the following two JDeps calls, where all your project's dependencies are in the `libs` folder: 36 | 37 | ```shell 38 | jdeps --jdk-internals -R --class-path 'libs/*' project.jar 39 | jdeps -s -R --class-path 'libs/*' project.jar 40 | ``` 41 | 42 | Finally, search your code base for calls to `AccessibleObject::setAccessible`, [casts to `URLClassLoader`](java-9-migration-guide#casting-to-urlclassloader), [parsing of `java.version` system properties](java-9-migration-guide#new-version-strings), or [handcrafting resource URLs](java-9-migration-guide#rummaging-around-in-runtime-images). 43 | Put everything you found on one big list - now it's time to analyze it. 44 | 45 | ## How Bad Is It? 46 | 47 | The problems you've found should fall into the two categories "I've seen it in before" and "What the fuck is going on?". 48 | For the former, split it up further into "Has at least a temporary fix" and "Is a hard problem." Particularly hard problems are removed APIs and package splits between platform modules and JARs that do not implement an endorsed standard or a standalone technology. 49 | 50 | It's very important not to confuse prevalence with importance. 51 | You might get about a thousand errors because a Java EE module is missing, but fixing that is trivial. 52 | You're in big trouble, though, if your core feature depends on that one cast of the application class loader to `URLClassLoader`. 53 | Or you might have a critical dependency on a removed API but because you've designed your system well, it just causes a few compile errors in one subproject. 54 | 55 | A good approach is to ask yourself for each specific problem that you don't know a solution for off the top of your head: "How bad would it be if I cut out the troublesome code and everything that depends on it?" How much would that hurt your project? 56 | 57 | In that vein, would it be possible to temporarily deactivate the troublesome code? 58 | Tests can be ignored, features toggled with flags. 59 | Get a sense for the how feasible it is to delay a fix and run the build or the application without it. 60 | 61 | When you're all done you should have a list of issues in these three categories: 62 | 63 | - a known problem with an easy fix 64 | - a known, hard problem 65 | - an unknown problem, needs investigation 66 | 67 | For problems in the last two categories, you should know how dangerous they are for your project and how easy you could get by without fixing them right now. 68 | 69 | ## On Estimating Numbers 70 | 71 | Chances are, somebody wants you to make an estimate that involves some hard numbers - maybe in hours, maybe in cash. 72 | That's tough in general, but here it is particularly problematic. 73 | 74 | A [Java 9 migration](java-9-migration-guide) makes you face the music of decisions long past. 75 | Your project might be tightly coupled to an outdated version of that Web framework you wanted to update for years, or it might a have accrued a lot of technical debt around that unmaintained library. 76 | And unfortunately both stop working on Java 9. 77 | What you have to do now is pay back some technical debt and everybody knows that the fees and interest can be hard to estimate. 78 | Finally, just like a good boss battle, the critical problem, the one that costs you the most to fix, could very well be hidden behind a few other troublemakers, so you can't see it until you're in too deep. 79 | 80 | I'm not saying these scenarios are *likely* just that they're *possible*, so be careful with guessing how long it might take you to migrate to Java 9. 81 | 82 | ## Jumping Into Action 83 | 84 | The next step is to actually start working on the issues you collected. 85 | I recommend to not do that in isolation, but to set your build tool and continuous integration up to build your project with Java 9 and then start solving them one by one. 86 | More on that in another post. 87 | -------------------------------------------------------------------------------- /content/articles/2017-12-31-goodbye-2017-hello-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Goodbye 2017, Hello 2018" 3 | tags: [turn-of-the-year] 4 | date: 2017-12-31 5 | slug: goodbye-2017-hello-2018 6 | description: "2017 draws to a close and 2018 is knocking. My annual review and preview went to my newsletter, so subscribe or head over to Medium to read them." 7 | featuredImage: 2017-2018 8 | --- 9 | 10 | Every [turn of the year](tag:turn-of-the-year), I write a pair of posts: One looks back on my professional achievements and failures of the passing year, the other one defines goals for the next one. 11 | I used to publish these posts here on CodeFX: 12 | 13 | - [Goodbye 2014](goodbye-2014) and [Hello 2015](hello-2015) 14 | - [Goodbye 2015](goodbye-2015) and [Hello 2016](hello-2016) 15 | - [Goodbye 2016](goodbye-2016) and [Hello 2017](hello-2017) 16 | 17 | That was never a good fit, though, because this blog is supposed to contain only technical content. 18 | But this year, I have [an active newsletter](news) where such content fits in perfectly, so I use it for these two posts. 19 | If you don't feel like [subscribing](news), head over to Medium, [where I publish them a few days after](http://medium.com/codefx-weekly) (including [full RSS](https://medium.com/feed/codefx-weekly)). 20 | 21 | In fact, [Goodbye 2017 is already online](https://medium.com/codefx-weekly/goodbye-2017-14ecd2481cba), Hello 2018 will show up next week ([here you go](https://medium.com/codefx-weekly/hello-2018-f7a284abc1f1)). 22 | 23 | **I wish you a happy new year’s eve! 24 | I hope to see you again in 2018.** 25 | -------------------------------------------------------------------------------- /content/talks/best-practices.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Best Practices Considered Harmful" 3 | tags: [techniques] 4 | date: 2019-06-03 5 | slug: talk-best-practices-harmful 6 | description: "A lightning talk about how best practices promote a one-size-fits all mentality that harms our ability to create solutions that are tailored to the problems at hand" 7 | searchKeywords: "best practices" 8 | featuredImage: best-practices 9 | slides: https://slides.nipafx.dev/best-practices 10 | --- 11 | 12 | Software development is full of "best practices" - index your database columns, write tests for your code, don't reinvent the wheel... 13 | But are these really "best"? 14 | Is it a good idea to blindly implement them? 15 | Shouldn't we discuss this? 16 | 17 | We absolutely should! 18 | This short talk dismantles the myth of "best practices", discarding a horrible name in order to redeem the underlying, often useful and proven solutions. 19 | -------------------------------------------------------------------------------- /content/talks/comment-your-code.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Comment Your &*☠# Code!" 3 | tags: [agile, clean-comments, documentation] 4 | date: 2016-06-24 5 | slug: talk-comment-your-code 6 | description: "A heartfelt rant / thoughtful talk arguing for more comments in code" 7 | searchKeywords: "comments" 8 | featuredImage: comment-your-code 9 | slides: https://slides.nipafx.dev/comment-your-code 10 | videoSlug: comment-your-code-accento-2019 11 | --- 12 | 13 | You think your code is so clean that it doesn't need any comments? 14 | Or are your colleagues convinced that all comments are failures? 15 | Then this talk is for you! 16 | 17 | Let's first dispute some common arguments against commenting code: 18 | 19 | * Comments lie? 20 | * Tests are better? 21 | * Good names suffice? 22 | 23 | We find fault with all of them (and more)! 24 | 25 | With that out of the way we categorize comments and analyze their costs and benefits. 26 | This wills us the means to discuss the end: 27 | Which comments will improve a code base? 28 | 29 | Of course, every team has to come up with its own answer, but the vocabulary and ideas presented in this talk can help find it. 30 | -------------------------------------------------------------------------------- /content/talks/expert-java-8.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Expert Java 8" 3 | tags: [java-8, lambda, streams, optional, default-methods] 4 | date: 2017-02-07 5 | slug: talk-expert-java-8 6 | description: "With this talk, I help you get the most out of lambdas, `Stream`s, `Optional`s, and default methods, helping you master Java 8's core features" 7 | searchKeywords: "java 8" 8 | featuredImage: java-8-expert 9 | slides: https://slides.nipafx.dev/expert-java-8 10 | videoSlug: expert-java-8-jfokus-2017 11 | --- 12 | 13 | By now Java 8's features are well understood, but some practical details are still unclear. Did you ever wonder... 14 | 15 | * how to create a "lambda-enabled" API? 16 | * whether your methods should return or even accept `Stream`s and how that might impact your design? 17 | * how to write readable `Stream` pipelines, especially when exceptions are involved? 18 | * about the details of and opinions on `Optional`? 19 | * how to use default methods to evolve interfaces without breaking client code? 20 | 21 | This talk discusses these and other advanced topics of Java 8, so you can get the most out of its awesome features. 22 | -------------------------------------------------------------------------------- /content/talks/java-9-migration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "To JAR Hell And Back" 3 | # subtitle: A Live Migration to Java 11 4 | tags: [java-9, java-11, j_ms, migration] 5 | date: 2017-07-25 6 | slug: talk-java-9-migration 7 | description: "A live-coding talk where we take a typical Java 8 code base and update it to Java 9 and beyond, overcoming some common and some less common hurdles like dependencies on internal APIs and split packages" 8 | searchKeywords: "java 9 migration" 9 | featuredImage: java-9-migration 10 | slides: https://slides.nipafx.dev/java-9-migration 11 | videoSlug: java-9-migration-devoxx-be-2018 12 | repo: java-9-migration 13 | --- 14 | 15 | I'm sure you've heard about compatibility issues when upgrading from Java 8 to 9 and beyond, but did you try it yourself yet? 16 | This live coding session starts with a typical Java 8 application and runs up against and eventually overcomes the common hurdles: 17 | 18 | * build system configuration 19 | * dependency analysis with `jdeps` 20 | * dependencies on internal APIs and Java EE modules 21 | * split packages 22 | 23 | To get the most out of this talk, you should have a good understanding of the module system basics - afterwards you will know how to approach *your* application's migration to Java 9 and the module system. 24 | -------------------------------------------------------------------------------- /content/talks/java-after-n.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Java After Eleven" 3 | tags: [java-11, java-12, java-13, java-14, java-15, java-16] 4 | date: 2020-02-05 5 | slug: talk-java-after-n 6 | description: "A live-coding talk during which I update a Java 11 code base to Java 16, making good use of new language features, additional and improved APIs, and JVM capabilities" 7 | featuredImage: java-after-n 8 | slides: https://slides.nipafx.dev/java-after-eight 9 | videoSlug: java-after-eight-jfokus-2020 10 | repo: java-after-eight 11 | --- 12 | 13 | Most projects that updated past Java 8 decided to stick to the LTS release 11. 14 | The new cadence created the illusion of not much happening after that, but nothing could be further from the truth - with new language features like switch expressions, text blocks, records, and sealed classes, Java is moving faster than ever. 15 | 16 | In this talk, we'll take a simple Java 11 code base, update it to 16, and refactor it to use the new language features and APIs. 17 | You'll be surprised how much the code changes! 18 | -------------------------------------------------------------------------------- /content/talks/java-module-system.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "The Java Module System Beyond The Basics" 3 | tags: [j_ms, migration] 4 | date: 2016-03-16 5 | slug: talk-java-module-system 6 | description: "In this talk, I go beyond the module system basics and present more advanced features for those who want to become their team's module system expert" 7 | searchKeywords: "java module system" 8 | featuredImage: java-module-system 9 | slides: https://slides.nipafx.dev/jpms 10 | videoSlug: jpms-java-saigon-2018 11 | repo: jpms-monitor 12 | --- 13 | 14 | Java 9 shipped the Java Platform Module System, which brings language-level modularity to the Java ecosystem. 15 | But you already know that and even spent some time to learn the basics? 16 | That's great because this talk takes you beyond that and shows how to... 17 | 18 | * model finer-grained dependencies and APIs 19 | * decouple modules with services 20 | * weigh encapsulation versus reflection 21 | * analyze dependencies with `jdeps` 22 | * safe the day with important command line flags 23 | * create runtime images with `jlink` 24 | 25 | With this under your belt, you're ready to become your team's module system expert. 26 | -------------------------------------------------------------------------------- /content/talks/java-next.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Java Next" 3 | # subtitle: "New Releases, Amber, Valhalla, and More Goodies" 4 | tags: [java-next, project-amber, project-loom, project-valhalla] 5 | date: 2018-05-30 6 | slug: talk-java-next 7 | description: "In this talk we reflect over recent developments and peek into Java's future: the six-month release cycle, the new license and support landscape as well as recent and upcoming features" 8 | searchKeywords: "java update" 9 | featuredImage: java-next 10 | slides: https://slides.nipafx.dev/java-next 11 | videoSlug: java-next-vjug-2019 12 | --- 13 | 14 | With Java's switch to a six-month release schedule, the community needs to step up its game to stay up to date. 15 | Instead of having a year or more to prepare for a new release, it's a mere three months - we need to be better informed about what's going on to see changes coming before they're out. 16 | 17 | This talk: 18 | 19 | * explains the details behind the new schedule and how to stay up to date 20 | * discusses license and support of JVM distributions 21 | * showcases Java 9 to 15 language features like `var`, `switch` expressions, and records 22 | * shows the future of Java, brought by projects like Valhalla, Amber, and others 23 | * looks at features of upcoming releases like pattern matching, value types, and fibers 24 | 25 | Afterwards, you will not only know about the immediate future but also how to stay up to date without much effort. 26 | -------------------------------------------------------------------------------- /content/talks/java-var.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Fun With `var`" 3 | tags: [anonymous-classes, default-methods, generics, lambda, java-10, var] 4 | date: 2018-11-08 5 | slug: talk-java-var 6 | description: "A live-coding talk where I show off all you need to know about `var` in Java. And then some." 7 | searchKeywords: "java var" 8 | featuredImage: java-var 9 | slides: https://slides.nipafx.dev/java-var 10 | videoSlug: java-var-jfall-2018 11 | repo: java-x-demo 12 | --- 13 | 14 | Since Java 10 you can use `var` to let the compiler infer a local variable's type: 15 | 16 | ```java 17 | var users = new ArrayList(); 18 | ``` 19 | 20 | And that's pretty much it, right? 21 | Surprisingly, no! 22 | There are a lot of details to consider... 23 | 24 | * is this JavaScript?! 25 | * how exactly is the type inferred? 26 | * where can I use `var` and what should I look out for? 27 | * won't this lead to unreadable code? 28 | 29 | ... and a few fun things to do with `var`... 30 | 31 | * playing with anonymous classes (don't!) 32 | * faking traits (don't!) 33 | * faking intersection types (do!) 34 | 35 | After this live-coding deep dive into `var`, you'll know all about Java 10's flagship feature. 36 | -------------------------------------------------------------------------------- /content/talks/java-x.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Java 16 Is Coming!" 3 | tags: [java-9, java-10, java-11, java-12, java-13, java-14, java-15] 4 | date: 2017-02-23 5 | slug: talk-java-x 6 | description: "In this talk, I introduce the Java language features, new/updated APIs, and new JVM capabilities that recent Java releases brought to the ecosystem" 7 | searchKeywords: "java 15" 8 | featuredImage: java-16 9 | slides: https://slides.nipafx.dev/java-x 10 | repo: java-x-demo 11 | --- 12 | 13 | Many projects are still on Java 8, some already on 11 and yet, these days Java 16 gets released! 14 | Java's releases between 9 and 16 have a lot in stock - in this talk you'll learn about... 15 | 16 | * new language features like sealed types, records, switch expressions, `var`, and more 17 | * new APIs like stack walking and the reactive HTTP/2 client 18 | * the additions to existing APIs, like `Stream` and `Optional` 19 | * other niceties like multi-release JARs and performance improvements 20 | 21 | After this talk you will be prepared to get started with Java 16. 22 | -------------------------------------------------------------------------------- /content/talks/junit-5.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "JUnit 5" 3 | # subtitle: Next Generation Testing on the JVM 4 | tags: [junit-5, testing] 5 | date: 2016-05-20 6 | slug: talk-junit-5 7 | description: "In this talk, I introduce JUnit 5 from basic tests to more advanced features like nesting, parameterization, parallelization, and extensions. We also discuss its architecture and compatibility with JUnit 4." 8 | searchKeywords: "junit 5" 9 | featuredImage: junit-5 10 | slides: https://slides.nipafx.dev/junit-5 11 | # videoSlug: https://www.youtube.com/watch?v=OA3UJYxTBRA https://www.youtube.com/watch?v=VfC00bby8XQ 12 | repo: junit-5-demo 13 | --- 14 | 15 | Java's most ubiquitous library got an update! This talk... 16 | 17 | * shows you how to write tests with JUnit 5 18 | * walks you through the changes compared to JUnit 4 19 | * expands on nested, parameterized, parallel and other kinds of tests 20 | * tells you how to build your own JUnit 5 extensions 21 | * presents the new architecture 22 | * discusses compatibility with previous JUnit versions, IDEs, and other testing tools 23 | 24 | Be careful, though: 25 | You might end up with an urge to start using it right away. 26 | -------------------------------------------------------------------------------- /content/talks/junit-pioneer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "To Jupiter And Beyond - On An Exploratory Mission With JUnit Pioneer" 3 | # subtitle: 4 | tags: [junit-5] 5 | date: 2021-03-18 6 | slug: talk-junit-pioneer 7 | description: "JUnit Pioneer gathers JUnit 5 extensions. This talk discusses the technical aspects, but also the mission, dev practices, automatic releases, and what Twitch has to do with all of this." 8 | searchKeywords: "junit 5 extensions" 9 | featuredImage: junit-pioneer-helmet 10 | slides: https://slides.nipafx.dev/junit-pioneer 11 | # videoSlug: 12 | --- 13 | 14 | JUnit is a huge success and [JUnit 5](https://junit.org/junit5) is an amazing overhaul of a tried-and-true formula. 15 | One of its most enticing new designs is [its extension model](https://nipafx.dev/junit-5-extension-model/), which is where [JUnit Pioneer](https://junit-pioneer.org/) comes in. 16 | It's a small project gathering all kinds of extensions. 17 | 18 | In this talk, we're going to take a close look at the project and we won't just stick to the technical aspects: 19 | 20 | * JUnit 5 and its extension model 21 | * Pioneer's extensions 22 | * Pioneer's mission statement 23 | * how live-streaming on Twitch grew a community 24 | * organizational style, contribution guide 25 | * code style, Git practices, release considerations 26 | * how we use Shipkit and GitHub actions for one-click releases 27 | 28 | If you're maintaining your own small open source project or are interested in a peek behind the scenes, this talk is for you. 29 | -------------------------------------------------------------------------------- /content/videos/2016-12-06-interview-devoxx-be-2016.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Rewrite, Architecture, Extensions Of JUnit 5" 3 | # subtitle: "Interview At Devoxx Belgium" 4 | tags: [community, junit-5] 5 | date: 2016-12-06 6 | slug: junit-5-rewrite-architecture-extensions 7 | videoSlug: interview-devoxx-be-2016 8 | description: "What were the reasons for the rewrite? How does JUnit 5 compare to JUnit 4? What's so special about the architecture and the extension points?" 9 | searchKeywords: "junit 5" 10 | featuredImage: interview-devoxx-be-2016 11 | --- 12 | 13 | Katharine Beaumont interviewed me at Devoxx Belgium about JUnit 5. 14 | We discussed the reasons for the rewrite, the differences to JUnit 4, the underlying architecture and its potential to spawn a new generation of testing frameworks in the JVM. 15 | Last but not least we talked about missing pieces, particularly in the extension API. 16 | -------------------------------------------------------------------------------- /content/videos/2017-03-29-nighthacking-javaland-2017.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Parameterized Tests in JUnit 5" 3 | # subtitle: "NightHacking Session At JavaLand 2017" 4 | tags: [junit-5] 5 | date: 2017-03-29 6 | slug: junit-5-parameterized-tests-nighthacking 7 | videoSlug: nighthacking-javaland-2017 8 | description: "At JavaLand 2017, I spent 15 minutes exploring JUnit 5's (then) brand-new parameterized test feature in a NightHacking session" 9 | searchKeywords: "JUnit 5 parameterized tests" 10 | featuredImage: nighthacking-javaland-2017 11 | repo: junit-5-demo 12 | --- 13 | 14 | Thanks Sebastian (Daschner) for inviting me to this session, exploring things live on camera is fun. 15 | (No, [really](https://twitch.tv/nipafx)!) 16 | -------------------------------------------------------------------------------- /content/videos/2017-09-20-impressions-javazone-2017.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Impressions of JavaZone 2017" 3 | tags: [community] 4 | date: 2017-09-20 5 | slug: impressions-javazone-2017 6 | videoSlug: impressions-javazone-2017 7 | description: "A few impressions of my stay at JavaZone 2017 to music from [WillRock](https://willrock.bandcamp.com/)" 8 | searchKeywords: "javazone 2017" 9 | featuredImage: impressions-javazone-2017 10 | --- 11 | 12 | JavaZone 2017 was the first time I had my camera with me and I decided to put the impressions into a short video. 13 | -------------------------------------------------------------------------------- /content/videos/2017-09-21-welcome-java-9.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Welcome, Java 9!" 3 | tags: [community, java-9] 4 | date: 2017-09-21 5 | slug: welcome-java-9 6 | videoSlug: welcome-java-9 7 | description: "Java 9 is out today and with other members of the community I'm throwing a welcome party. Get an all around view on the new Java release with various opinions, tips, and great sources!" 8 | searchKeywords: "Java 9" 9 | featuredImage: welcome-java-9 10 | repo: java-x-demo 11 | --- 12 | 13 | Simon Maple: 14 | 15 | * [Twitter](https://twitter.com/sjmaple) 16 | * [ZeroTurnaround](https://zeroturnaround.com/) 17 | * [Joda projects](http://www.joda.org/) 18 | * [modularity](https://www.manning.com/books/the-java-module-system?a_aid=nipa&a_bid=869915cb) [books](https://javamodularity.com/) 19 | 20 | Stephen Colebourne: 21 | 22 | * [Twitter](https://twitter.com/jodastephen) 23 | * [Blog](http://blog.joda.org/) 24 | * [OpenGamma](http://strata.opengamma.io/) 25 | * [Joda projects](http://www.joda.org/) 26 | * on modules and their names: [1](http://blog.joda.org/2017/04/java-se-9-jpms-module-naming.html), [2](http://blog.joda.org/2017/04/java-se-9-jpms-modules-are-not-artifacts.html) 27 | 28 | Christian Stein: 29 | 30 | * [Twitter](https://twitter.com/sormuras) 31 | * [JUnit 5](http://junit.org/junit5/) 32 | * [Bach (JShell-based build tool)](https://github.com/sormuras/bach) 33 | 34 | Nicolai Parlog: 35 | 36 | * [Twitter](https://twitter.com/nipafx) 37 | * [Blog](https://nipafx.dev) 38 | * [_The Java Module System_](https://www.manning.com/books/the-java-module-system?a_aid=nipa&a_bid=869915cb) 39 | 40 | Trisha Gee: 41 | 42 | * [Twitter](https://twitter.com/trisha_gee) 43 | * [Blog](https://trishagee.github.io/) 44 | * [JetBrains](https://trishagee.github.io/) 45 | * [_Real World Java 9_](https://www.youtube.com/watch?v=GkP83hvdeMk) 46 | 47 | Chris Engelbert: 48 | 49 | * [Twitter](https://twitter.com/noctarius2k) 50 | * [Hazelcast](https://hazelcast.com/) 51 | 52 | Sander Mak: 53 | 54 | * [Twitter](https://twitter.com/Sander_Mak) 55 | * [Blog](http://branchandbound.net/) 56 | * [Luminis](https://www.luminis.eu) 57 | * [_Java 9 Modularity_](https://javamodularity.com/) 58 | 59 | Jens Schauder: 60 | 61 | * [Twitter](https://twitter.com/jensschauder) 62 | * [Blog](http://blog.schauderhaft.de/) 63 | * [Spring Data](https://projects.spring.io/spring-data-jpa/) 64 | * [Pivotal](https://pivotal.io/) 65 | 66 | Monica Beckwith: 67 | 68 | * [Twitter](https://twitter.com/mon_beck) 69 | * [Code Karam](http://www.codekaram.com/) 70 | 71 | Rafael Winterhalter: 72 | 73 | * [Twitter](https://twitter.com/rafaelcodes) 74 | * [Site](http://rafael.codes/) 75 | * [ByteBuddy](http://bytebuddy.net) 76 | * [Mockito](http://mockito.org/) 77 | * [JavaZone talks](https://vimeo.com/javazone) 78 | 79 | Wayne Beaton: 80 | 81 | * [Twitter](https://twitter.com/waynebeaton) 82 | * [Eclipse](http://eclipse.org/) 83 | * [OpenJ9](https://www.eclipse.org/openj9) 84 | * [Eclipse support for Java 9](http://wiki.eclipse.org/java9) 85 | 86 | Simon Ritter: 87 | 88 | * [Twitter](https://twitter.com/speakjava) 89 | * [Azul Systems](http://azul.com/) 90 | * [_55 New Features in JDK 9_](https://www.youtube.com/watch?v=CMMzG8I23lY) 91 | * [_JDK 9: Pitfalls For The Unwary_](https://www.azul.com/jdk-9-pitfalls-for-the-unwary/) 92 | * [_JDK 9: XXtra Command Line Options_](https://www.azul.com/jdk-9-xxtra-command-line-options/) 93 | -------------------------------------------------------------------------------- /content/videos/2017-11-16-var-java-10.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "First contact with `var` in Java 10" 3 | tags: [java-10, var] 4 | date: 2017-11-16 5 | slug: var-java-10 6 | videoSlug: var-java-10 7 | description: "How to use `var`, where it works and where it doesn't (and why), and how it might impact readability" 8 | searchKeywords: "Java 10 var" 9 | featuredImage: var-java-10 10 | repo: java-x-demo 11 | --- 12 | 13 | Java 10 will be released in March 2018 and it comes with local-variable type inference, which means we can use `var` instead of a type name when declaring local variables. 14 | In this video I tell you how to use `var`, where it works and where it doesn't (and why), and how it might impact readability. 15 | -------------------------------------------------------------------------------- /content/videos/2018-09-20-effective-java-00-kickoff.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Kicking off a series on Effective Java, Third Edition" 3 | tags: [book-club] 4 | date: 2018-09-20 5 | slug: effective-java-kickoff 6 | videoSlug: effective-java-00 7 | description: "Kick-off to [a YouTube series on Effective Java, Third Edition](https://www.youtube.com/playlist?list=PL_-IO8LOLuNqUzvXfRCWRRJBswKEbLhgN) - let's find some angles Josh didn't cover" 8 | searchKeywords: "Effective Java" 9 | featuredImage: effective-java-00 10 | repo: effective-java 11 | --- 12 | 13 | Effective Java,Third Edition, took me by surprise. 14 | After having read the second edition, I figured I would only read the new items, but I was so wrong! 15 | It sucked my right back in and I ended up rereading the entire book. 16 | I rediscovered all the details I forgot, connected the content with my personal experiences from about a decade of Java development, and had a lot of fun trying to find angles Josh hadn't covered. 17 | 18 | This inspired me to start [a YouTube series on Effective Java](https://www.youtube.com/playlist?list=PL_-IO8LOLuNqUzvXfRCWRRJBswKEbLhgN), which this video kicks off. 19 | Come with me on a journey that goes back to the roots and makes us experts in the Java core language! 20 | -------------------------------------------------------------------------------- /content/videos/2018-09-24-effective-java-01-static-factory-methods.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Static Factory Methods - Effective Java, Item 1" 3 | # subtitle 4 | tags: [book-club, patterns] 5 | date: 2018-09-24 6 | slug: effective-java-static-factory-methods 7 | videoSlug: effective-java-01 8 | description: "How to use static factory methods to overcome three shortcomings of constructors" 9 | searchKeywords: "Effective Java" 10 | featuredImage: effective-java-01 11 | repo: effective-java 12 | --- 13 | 14 | Static factory methods are awesome! 15 | They allow us to overcome three shortcomings of constructors by allowing us to freely choose a name, take control over returned instances, and take control over the returned type. 16 | I use them every day. 17 | 18 | Also, I'm actually sorry about all the "actuallies". 😉 19 | 20 | Links to follow up: 21 | 22 | * [BED-Con in Berlin](http://www.bed-con.org/) 23 | * [Twitter-poll](https://twitter.com/nipafx/status/1037310344585261056) 24 | * [§1 GG](https://twitter.com/nipafx/status/1038376068728676353) 25 | * [Value-based classes](java-value-based-classes) 26 | * [Comment your &*☠# Code](comment-your-fucking-code) 27 | -------------------------------------------------------------------------------- /content/videos/2018-10-02-java-11-new-dawn.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Java 11: A New Dawn - Releases, Oracle JDK vs OpenJDK, and LTS" 3 | tags: [java-11, java-next] 4 | date: 2018-10-02 5 | slug: java-11-releases-license-lts 6 | videoSlug: java-11-new-dawn 7 | description: "Oracle's announcements of the six-month release cadence and new licensing caused quite a ruckus - now that things calmed down, lets discuss where we're headed" 8 | searchKeywords: "Java 11 LTS" 9 | featuredImage: java-11-new-dawn 10 | --- 11 | 12 | Java 11 is a game changer! 13 | Not so much for technical reasons (unless you come from Java 8), but because of the new release cadence (six months), licensing (Oracle JDK vs OpenJDK), and long-term support (not free by Oracle). 14 | I discuss all of these in detail to make sure you know what to expect. 15 | 16 | Links to follow up: 17 | 18 | * [Slides](https://slides.nipafx.dev/java-next/2018-09-30-codefx@yt/) 19 | * [Java 11 migration](java-11-migration-guide) 20 | * [Incubator modules](http://openjdk.java.net/jeps/11) 21 | * [Language previews](http://openjdk.java.net/jeps/12) 22 | * [Oracle JDK vs OpenJDK](https://blogs.oracle.com/java-platform-group/oracle-jdk-releases-for-java-11-and-later) 23 | * [Red Hat stewardship](https://developers.redhat.com/blog/2018/09/24/the-future-of-java-and-openjdk-updates-without-oracle-support/) 24 | -------------------------------------------------------------------------------- /content/videos/2018-10-09-effective-java-02-builders.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Use Builders... Cautiously - Effective Java, Item 2" 3 | # subtitle 4 | tags: [book-club, patterns] 5 | date: 2018-10-09 6 | slug: effective-java-builders 7 | videoSlug: effective-java-02 8 | description: "Why and how to avoid the builder pattern and how to make best use of it if you can't" 9 | searchKeywords: "Effective Java" 10 | featuredImage: effective-java-02 11 | repo: effective-java 12 | --- 13 | 14 | The builder pattern is a powerful tool to ease the instantiation of complex classes. 15 | Whether constructor parameters are too numerous, there are too many of the same type, or whether many are optional - with a builder you can make your life easier. 16 | Although, I posit, often you can make your life even easier by directly tackling the class' or constructor's complexity. 17 | 18 | In this video I show an example of how to simplify a class to make a builder obsolete, but also how to build more powerful builders that add more value than just simplifying constructor calls. 19 | 20 | Links to follow up: 21 | 22 | * [JavaZone in Oslo](https://2018.javazone.no/) 23 | * [my opinion on `Optional`](intention-revealing-code-java-8-optional) 24 | * [`Map.of` et al](java-9-tutorial/#collection-factories) 25 | * [named & default parameters in Kotlin](http://www.deadcoderising.com/kotlin-how-to-use-default-parameters-in-functions-and-constructors/) 26 | * [automaton](https://brilliant.org/wiki/finite-state-machines/) 27 | * [partial application](https://en.wikipedia.org/wiki/Partial_application) 28 | * [self types with generics](https://www.sitepoint.com/self-types-with-javas-generics/) 29 | -------------------------------------------------------------------------------- /content/videos/2018-10-18-java-12-switch-expression.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "First Contact with Switch Expressions in Java 12" 3 | tags: [java-12, switch] 4 | date: 2018-10-18 5 | slug: java-12-switch-expression 6 | videoSlug: switch-java-12 7 | description: "With Java 12, `switch` is no longer just a statement, but becomes an expression. Let's take a look!" 8 | searchKeywords: "Java 12 switch" 9 | featuredImage: switch-java-12 10 | repo: java-x-demo 11 | --- 12 | 13 | With `switch` becoming an expression, it can have a return value (instead of having to assign or return results) and with a lambda-like syntax that doesn't fall-through (no more `break` 🎉) and exhaustiveness checks (less `default` ) it is much more readable. 14 | So much to talk about! 15 | 16 | * [Definitive Guide To Switch Expressions](java-13-switch-expressions) 17 | * [JEP 325 - Switch Expressions](https://openjdk.java.net/jeps/325) 18 | * [JEP 12 - Preview Language and VM Features](https://openjdk.java.net/jeps/12) 19 | * [Article on `var`](java-10-var-type-inference) 20 | -------------------------------------------------------------------------------- /content/videos/2018-10-24-robert-scholte-maven-3-4-5.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Maven 3 / 4 / 5 with Robert Scholte" 3 | tags: [conversation, tools] 4 | date: 2018-10-24 5 | slug: robert-scholte-maven-3-4-5 6 | videoSlug: robert-scholte-maven-3-4-5 7 | description: "Maven is one of the cornerstones of the Java ecosystem - here I talk with Robert Scholte, Chairman of the Apache Maven projects" 8 | searchKeywords: "Robert Scholte" 9 | featuredImage: robert-scholte-maven-3-4-5 10 | --- 11 | 12 | Everybody knows Maven, but few people know the developers behind it and what they plan for Java's most used build tool. 13 | I talk with Robert Scholte and he gives a glimpse of the project's inner workings as well as some ideas for Maven 4 and even 5. 14 | 15 | * [Recorded at JokerConf 2018](https://jokerconf.com/en/) 16 | * [Robert on Twitter](https://twitter.com/rfscholte) 17 | * [Maven issues to get you started](https://issues.apache.org/jira/browse/MNG-6432?jql=project%20%3D%20MNG%20AND%20labels%20in%20(starter%2C%20newbie%2C%20easyfix%2C%20beginner)%20ORDER%20BY%20created%20DESC) 18 | * [Snyk JVM Ecosystem Report 2018](https://snyk.io/blog/jvm-ecosystem-report-2018) 19 | -------------------------------------------------------------------------------- /content/videos/2018-11-27-effective-java-03-utilities-04-singleton-05-dependency-injection.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Utilities, Singletons and Dependency Injection - Effective Java, Items 3-5" 3 | # subtitle 4 | tags: [book-club, patterns] 5 | date: 2018-11-27 6 | slug: effective-java-utilities-singleton-dependency-injection 7 | videoSlug: effective-java-03-04-05 8 | description: "Mildly surprising (to me), it makes sense to discuss these three patters in one video - so here it goes" 9 | searchKeywords: "Effective Java" 10 | featuredImage: effective-java-03-04-05 11 | repo: effective-java 12 | --- 13 | 14 | What do singletons, utility classes, and dependency injection have in common? 15 | All three worry about controlling instantiation - when, how, and by whom? 16 | Effective Java items 3, 4, and 5 have something to say about that. 17 | 18 | * [jDays in Gothenburg (item 4)](https://jdays.se/) 19 | * [J-Fall in Pathé Ede (item 3)](https://jfall.nl/) 20 | * [Devoxx Belgium in Antwerp (item 5)](https://devoxx.be/) 21 | * [Reduce to only element](java-stream-findfirst-findany-reduce) 22 | * [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) 23 | * [facade pattern](http://jargon.js.org/_glossary/FACADE_PATTERN.md) 24 | -------------------------------------------------------------------------------- /content/videos/2019-02-24-java-12-experiments.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Java 12 Experiments (Live Stream)" 3 | tags: [java-12] 4 | date: 2019-02-24 5 | slug: java-12-experiments 6 | videoSlug: java-12-experiments-live 7 | description: "In my first live stream ever (yay!), we explored Java 12's API improvements" 8 | searchKeywords: "Java 12" 9 | featuredImage: java-12-experiments-live 10 | repo: java-x-demo 11 | --- 12 | 13 | My first [live stream](https://twitch.tv/nipafx)! 14 | We dug around in Java 12, cataloging new features and experimenting with a few of them, most notably `String::indent` and `transform`, `Collectors::teeing`, and additions to `CompletableFuture`. 15 | 16 | Thanks again to everyone who was there. 🙏 17 | I had an amazing time. 18 | -------------------------------------------------------------------------------- /content/videos/2019-04-18-caliz-1-learning-graal.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Caliz I: Wrapping JVM 11 and learning about Graal AOT" 3 | tags: [performance, tools] 4 | date: 2019-04-18 5 | slug: caliz-learning-graal 6 | videoSlug: caliz-1 7 | description: "First steps toward an acceptable scripting experience with single-source-file execution and Graal native images" 8 | searchKeywords: "Caliz" 9 | featuredImage: caliz-1 10 | repo: caliz 11 | --- 12 | 13 | With Java 11's single-source-file execution and Graal's blazing fast native images (aka ahead-of-time compilation), it should be possible to create an acceptable scripting experience with Java. 14 | Here's the first part of a live stream where I did exactly that. 15 | 16 | Thanks to everyone who was there. 🙏 17 | I had an amazing time. 18 | 19 | Links to follow up: 20 | 21 | * [Caliz](https://github.com/nipafx/Caliz) 22 | * [Scripting Java](scripting-java-shebang) 23 | * [Graal AOT](https://www.graalvm.org/docs/reference-manual/aot-compilation/) 24 | -------------------------------------------------------------------------------- /content/videos/2019-05-01-caliz-2-wrapping-graal.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Caliz II: Wrapping Graal AOT" 3 | tags: [performance, tools] 4 | date: 2019-05-01 5 | slug: caliz-wrapping-graal 6 | videoSlug: caliz-2 7 | description: "Extending Caliz to create native images of Java \"scripts\" (single source files) with with Graal" 8 | searchKeywords: "Caliz" 9 | featuredImage: caliz-2 10 | repo: caliz 11 | --- 12 | 13 | After I wrapped JVM 11 in the last video, it is now time to let Caliz use Graal AOT to create a native image. 14 | There are a few hurdles, though, so it took me some time. 15 | 16 | As always, thanks to everyone who was there. 17 | 🌻 I had an amazing time. 18 | 19 | Links to follow up: 20 | 21 | * [Caliz](https://github.com/nipafx/Caliz) 22 | * [Scripting Java](scripting-java-shebang) 23 | * [Graal AOT](https://www.graalvm.org/docs/reference-manual/aot-compilation/) 24 | -------------------------------------------------------------------------------- /content/videos/2019-05-16-caliz-3-background-compilation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Caliz III: Hashing scripts and background compilation" 3 | tags: [performance, tools] 4 | date: 2019-05-16 5 | slug: caliz-background-compilation 6 | videoSlug: caliz-3 7 | description: "Letting Caliz store native images and only create them for a given Java \"script\" (single source file) if needed" 8 | searchKeywords: "Caliz" 9 | featuredImage: caliz-3 10 | repo: caliz 11 | --- 12 | 13 | After I wrapping JVM 11 and Graal AOT in the last two videos, it is now time to determine for a given script, which way to go. 14 | 15 | Thanks again to everyone who was there for the amazing time. 🏆 16 | 17 | Links to follow up: 18 | 19 | * [Caliz](https://github.com/nipafx/Caliz) 20 | * [Scripting Java](scripting-java-shebang) 21 | * [Graal AOT](https://www.graalvm.org/docs/reference-manual/aot-compilation/) 22 | -------------------------------------------------------------------------------- /content/videos/2019-05-26-jpms-sander-mak.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "The Java Module System with Sander Mak" 3 | tags: [conversation, j_ms] 4 | date: 2019-05-26 5 | slug: java-module-system-sander-mak 6 | videoSlug: jpms-sander-mak 7 | description: "At J-Fall 2018 I talked to Sander Mak, modularity expert at Luminis, about the Java module system (J_MS), its adoption, how it compares to OSGi, and more." 8 | searchKeywords: "JMS OSGi" 9 | featuredImage: jpms-sander-mak 10 | --- 11 | 12 | Some links to follow up: 13 | 14 | * [J-Fall](https://jfall.nl/) 2018 15 | * [Sander's talk (in Dutch)](https://www.youtube.com/watch?v=CNypJD-41Ng) 16 | * [Sander on Twitter](https://twitter.com/Sander_Mak) 17 | * Sander Mak's book [_Java 9 Modularity_](https://javamodularity.com/) 18 | * my book [_The Java Module System_](https://www.manning.com/books/the-java-module-system?a_aid=nipa&a_bid=869915cb) 19 | -------------------------------------------------------------------------------- /content/videos/2019-09-18-keynote-blip-code-one-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "How We Upgraded From Java 8 And Why You Can (And Should) Do It Too" 3 | # subtitle: "Code One Keynote Blip" 4 | tags: [community, migration] 5 | date: 2019-09-18 6 | slug: upgrade-from-java-8 7 | videoSlug: keynote-blip-code-one-2019 8 | description: "My two minutes of fame during the Oracle Code One 2019 keynote" 9 | searchKeywords: "code one 2019" 10 | featuredImage: keynote-blip-code-one-2019 11 | --- 12 | 13 | Is it vain to say that I'm really proud that I got asked by Oracle's JVM team to record a blip for the keynote? 14 | Because I am! 15 | 😊 16 | -------------------------------------------------------------------------------- /content/videos/2020-04-07-oliver-drotbohm-modularity.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Modularity with Oliver Drotbohm" 3 | tags: [architecture, conversation, j_ms] 4 | date: 2020-04-07 5 | slug: oliver-drotbohm-modularity 6 | videoSlug: oliver-drotbohm-modularity 7 | description: "Oliver and I discuss modularity in Java with a focus on the Java module system" 8 | searchKeywords: "Java modularity" 9 | featuredImage: oliver-drotbohm-modularity 10 | --- 11 | 12 | [Oliver](https://twitter.com/odrotbohm) is Senior Principal Software Engineer at Pivotal, Java Champion, and OpenSource enthusiast. 13 | He's an expert in all things Spring, data, DDD, REST, and software architecture. 14 | He's into drums and music and is the inventor of the `@soundtrack` annotation. 15 | 16 | * [the tweet that started it all](https://twitter.com/lukaseder/status/1238393568730939392) 17 | * [Oliver's reply](https://twitter.com/odrotbohm/status/1238480137756332039) 18 | * [Salespoint](https://github.com/st-tu-dresden/salespoint) 19 | * [Moduliths](https://github.com/odrotbohm/moduliths) 20 | * [tabs better than spaces](https://www.reddit.com/r/javascript/comments/c8drjo/nobody_talks_about_the_real_reason_to_use_tabs/) 21 | * [J_MS issue summary](https://openjdk.java.net/projects/jigsaw/spec/issues/) 22 | * [J_MS'ed application](https://blog.disy.net/java-modules-modularization-1/) 23 | -------------------------------------------------------------------------------- /content/videos/2020-04-28-generics-1-basics.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Java Generics I - The Basics" 3 | tags: [generics] 4 | date: 2020-04-28 5 | slug: java-generics-basics 6 | videoSlug: generics-1 7 | description: "First part of a short series on Java Generics - this one explains the basics" 8 | searchKeywords: "Java generics" 9 | featuredImage: generics-1 10 | --- 11 | 12 | Nothing more to say, gotta watch the video. 😃 13 | -------------------------------------------------------------------------------- /content/videos/2021-02-11-brian-goetz-25h.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Java's Quirks and Wrong (?) Defaults with Brian Goetz" 3 | # subtitle: "Today's problems come from yesterday's solutions" 4 | tags: [conversation, migration, optional, serialization] 5 | date: 2021-02-11 6 | slug: 25h-brian-goetz 7 | videoSlug: brian-goetz-25h 8 | description: "Mutability, nullability, serialization, primitives - Nicolai Parlog discusses with Java language architect Brian Goetz why Java is the way it is." 9 | intro: "Mutability, nullability, serialization, primitives - Java seems to have gotten most things wrong. But could it have been successful otherwise? Brian Goetz and I discuss why Java is the way it is." 10 | featuredImage: brian-goetz-25h 11 | --- 12 | 13 | The runner-up for the video title was: 14 | 15 | > Today's problems come from yesterday's solutions. 16 | 17 | At [the 25-hour live stream](25h-java), Brian and I talk about a few things that annoy Java developers today, why they are the way they are, and what could have been done differently. Our topics (link to timestamps in video): 18 | 19 | * [What's wrong with serialization?](https://www.youtube.com/watch?v=ZyTH8uCziI4&t=03m28s) 20 | * [How it could've been better](https://www.youtube.com/watch?v=ZyTH8uCziI4&t=12m06s) 21 | * [`null` - the hole in Java's type system](https://www.youtube.com/watch?v=ZyTH8uCziI4&t=19m20s) 22 | * [Why that's not easy to fix](https://www.youtube.com/watch?v=ZyTH8uCziI4&t=23m20s) 23 | * [Strict mode, compatibility, trade-offs](https://www.youtube.com/watch?v=ZyTH8uCziI4&t=28m01s) 24 | * [Nominal vs structural typing](https://www.youtube.com/watch?v=ZyTH8uCziI4&t=34m03s) 25 | * [Java got all the defaults wrong](https://www.youtube.com/watch?v=ZyTH8uCziI4&t=36m23s) 26 | * [Primitives then and now](https://www.youtube.com/watch?v=ZyTH8uCziI4&t=43m10s) 27 | * [Brian's favorite language](https://www.youtube.com/watch?v=ZyTH8uCziI4&t=48m16s) 28 | 29 | 30 | 31 | 41 | 42 | 57 | -------------------------------------------------------------------------------- /genealogists/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.codefx.java-after-eight 9 | parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | org.codefx.java_after_eight 14 | genealogists 15 | 16 | 17 | 18 | org.codefx.java_after_eight 19 | genealogy 20 | 1.0-SNAPSHOT 21 | 22 | 23 | 24 | 25 | 26 | 27 | maven-jar-plugin 28 | 29 | genealogists 30 | ../jars 31 | 32 | 33 | 34 | maven-javadoc-plugin 35 | 36 | 37 | 38 | javadoc 39 | 40 | package 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /genealogists/src/main/java/org/codefx/java_after_eight/genealogists/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * For more possible genealogists, see: 3 | * - text similarities: https://medium.com/@adriensieg/text-similarities-da019229c894 4 | * - Stanford CoreNLP: https://github.com/stanfordnlp/CoreNLP 5 | */ 6 | package org.codefx.java_after_eight.genealogists; 7 | -------------------------------------------------------------------------------- /genealogists/src/main/java/org/codefx/java_after_eight/genealogists/random/RandomGenealogist.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogists.random; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.RelationType; 5 | import org.codefx.java_after_eight.genealogist.TypedRelation; 6 | import org.codefx.java_after_eight.post.Post; 7 | 8 | import java.util.Random; 9 | 10 | import static java.util.Objects.requireNonNull; 11 | 12 | public class RandomGenealogist implements Genealogist { 13 | 14 | private static final RelationType TYPE = new RelationType("random"); 15 | 16 | private final Random random; 17 | 18 | public RandomGenealogist(Random random) { 19 | this.random = requireNonNull(random); 20 | } 21 | 22 | @Override 23 | public TypedRelation infer(Post post1, Post post2) { 24 | long score = random.nextLong(); 25 | return new TypedRelation(post1, post2, TYPE, score); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /genealogists/src/main/java/org/codefx/java_after_eight/genealogists/random/RandomGenealogistService.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogists.random; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.GenealogistService; 5 | import org.codefx.java_after_eight.post.Post; 6 | 7 | import java.util.Collection; 8 | import java.util.Random; 9 | 10 | public class RandomGenealogistService implements GenealogistService { 11 | 12 | @Override 13 | public Genealogist procure(Collection posts) { 14 | Random random = new Random(); 15 | return new RandomGenealogist(random); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /genealogists/src/main/java/org/codefx/java_after_eight/genealogists/repo/RepoGenealogist.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogists.repo; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.RelationType; 5 | import org.codefx.java_after_eight.genealogist.TypedRelation; 6 | import org.codefx.java_after_eight.post.Article; 7 | import org.codefx.java_after_eight.post.Post; 8 | import org.codefx.java_after_eight.post.Repository; 9 | import org.codefx.java_after_eight.post.Video; 10 | 11 | import java.util.Objects; 12 | import java.util.Optional; 13 | 14 | public class RepoGenealogist implements Genealogist { 15 | 16 | private static final RelationType TYPE = new RelationType("repo"); 17 | 18 | @Override 19 | public TypedRelation infer(Post post1, Post post2) { 20 | long score = determineScore(post1, post2); 21 | return new TypedRelation(post1, post2, TYPE, score); 22 | } 23 | 24 | private long determineScore(Post post1, Post post2) { 25 | Optional repo1 = getRepository(post1); 26 | Optional repo2 = getRepository(post2); 27 | 28 | if (repo1.isPresent() != repo2.isPresent()) 29 | return 0; 30 | // at this point, either both are empty or both are non-empty 31 | if (!repo1.isPresent()) 32 | return 20; 33 | return Objects.equals(repo1, repo2) ? 100 : 50; 34 | } 35 | 36 | private Optional getRepository(Post post) { 37 | if (post instanceof Article) 38 | return ((Article) post).repository(); 39 | if (post instanceof Video) 40 | return ((Video) post).repository(); 41 | return Optional.empty(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /genealogists/src/main/java/org/codefx/java_after_eight/genealogists/repo/RepoGenealogistService.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogists.repo; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.GenealogistService; 5 | import org.codefx.java_after_eight.post.Post; 6 | 7 | import java.util.Collection; 8 | 9 | public class RepoGenealogistService implements GenealogistService { 10 | 11 | @Override 12 | public Genealogist procure(Collection posts) { 13 | return new RepoGenealogist(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /genealogists/src/main/java/org/codefx/java_after_eight/genealogists/silly/SillyGenealogist.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogists.silly; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.RelationType; 5 | import org.codefx.java_after_eight.genealogist.TypedRelation; 6 | import org.codefx.java_after_eight.post.Post; 7 | 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | import static java.lang.Math.round; 12 | import static java.util.stream.Collectors.toSet; 13 | 14 | public class SillyGenealogist implements Genealogist { 15 | 16 | private static final RelationType TYPE = new RelationType("silly"); 17 | 18 | @Override 19 | public TypedRelation infer(Post post1, Post post2) { 20 | Set post1Letters = titleLetters(post1); 21 | Set post2Letters = titleLetters(post2); 22 | Set intersection = new HashSet<>(post1Letters); 23 | intersection.retainAll(post2Letters); 24 | long score = round((100.0 * intersection.size()) / post1Letters.size()); 25 | 26 | return new TypedRelation(post1, post2, TYPE, score); 27 | } 28 | 29 | private static Set titleLetters(Post post) { 30 | return post 31 | .title() 32 | .text() 33 | .toLowerCase() 34 | .chars().boxed() 35 | .collect(toSet()); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /genealogists/src/main/java/org/codefx/java_after_eight/genealogists/silly/SillyGenealogistService.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogists.silly; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.GenealogistService; 5 | import org.codefx.java_after_eight.post.Post; 6 | 7 | import java.util.Collection; 8 | 9 | public class SillyGenealogistService implements GenealogistService { 10 | 11 | @Override 12 | public Genealogist procure(Collection posts) { 13 | return new SillyGenealogist(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /genealogists/src/main/java/org/codefx/java_after_eight/genealogists/tags/TagGenealogist.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogists.tags; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.RelationType; 5 | import org.codefx.java_after_eight.genealogist.TypedRelation; 6 | import org.codefx.java_after_eight.post.Post; 7 | import org.codefx.java_after_eight.post.Tag; 8 | 9 | import java.util.Set; 10 | 11 | import static java.lang.Math.round; 12 | import static java.util.stream.Collectors.toSet; 13 | 14 | public class TagGenealogist implements Genealogist { 15 | 16 | private static final RelationType TYPE = new RelationType("tag"); 17 | 18 | @Override 19 | public TypedRelation infer(Post post1, Post post2) { 20 | Set post2Tags = post2.tags().collect(toSet()); 21 | long numberOfSharedTags = post1 22 | .tags() 23 | .filter(post2Tags::contains) 24 | .count(); 25 | long numberOfPost1Tags = post1.tags().count(); 26 | long score = round((100.0 * 2 * numberOfSharedTags) / (numberOfPost1Tags + post2Tags.size())); 27 | return new TypedRelation(post1, post2, TYPE, score); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /genealogists/src/main/java/org/codefx/java_after_eight/genealogists/tags/TagGenealogistService.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogists.tags; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.GenealogistService; 5 | import org.codefx.java_after_eight.post.Post; 6 | 7 | import java.util.Collection; 8 | 9 | public class TagGenealogistService implements GenealogistService { 10 | 11 | @Override 12 | public Genealogist procure(Collection posts) { 13 | return new TagGenealogist(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /genealogists/src/main/java/org/codefx/java_after_eight/genealogists/type/TypeGenealogist.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogists.type; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.RelationType; 5 | import org.codefx.java_after_eight.genealogist.TypedRelation; 6 | import org.codefx.java_after_eight.post.Post; 7 | 8 | public class TypeGenealogist implements Genealogist { 9 | 10 | private static final RelationType TYPE = new RelationType("type"); 11 | 12 | @Override 13 | public TypedRelation infer(Post post1, Post post2) { 14 | long score = 0; 15 | switch (post2.getClass().getSimpleName()) { 16 | case "Article": 17 | score = 50; 18 | break; 19 | case "Video": 20 | score = 90; 21 | break; 22 | case "Talk": 23 | score = 20; 24 | break; 25 | } 26 | 27 | return new TypedRelation(post1, post2, TYPE, score); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /genealogists/src/main/java/org/codefx/java_after_eight/genealogists/type/TypeGenealogistService.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogists.type; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.GenealogistService; 5 | import org.codefx.java_after_eight.post.Post; 6 | 7 | import java.util.Collection; 8 | 9 | public class TypeGenealogistService implements GenealogistService { 10 | 11 | @Override 12 | public Genealogist procure(Collection posts) { 13 | return new TypeGenealogist(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /genealogists/src/main/resources/META-INF/services/org.codefx.java_after_eight.genealogist.GenealogistService: -------------------------------------------------------------------------------- 1 | # org.codefx.java_after_eight.genealogists.repo.RepoGenealogist 2 | # org.codefx.java_after_eight.genealogists.silly.SillyGenealogistService 3 | org.codefx.java_after_eight.genealogists.tags.TagGenealogistService 4 | # org.codefx.java_after_eight.genealogists.type.TypeGenealogist 5 | -------------------------------------------------------------------------------- /genealogy/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.codefx.java-after-eight 9 | parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | org.codefx.java_after_eight 14 | genealogy 15 | 16 | 17 | 18 | 19 | maven-jar-plugin 20 | 21 | genealogy 22 | ../jars 23 | 24 | 25 | true 26 | org.codefx.java_after_eight.Main 27 | 28 | 29 | 30 | 31 | 32 | maven-javadoc-plugin 33 | 34 | 35 | 36 | javadoc 37 | 38 | package 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.junit.jupiter 49 | junit-jupiter 50 | test 51 | 52 | 53 | org.junit.jupiter 54 | junit-jupiter-params 55 | test 56 | 57 | 58 | org.mockito 59 | mockito-core 60 | test 61 | 62 | 63 | org.mockito 64 | mockito-junit-jupiter 65 | test 66 | 67 | 68 | org.assertj 69 | assertj-core 70 | test 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/Config.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight; 2 | 3 | import java.io.IOException; 4 | import java.io.UncheckedIOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | import java.util.Optional; 9 | import java.util.concurrent.CompletableFuture; 10 | 11 | public class Config { 12 | 13 | private static final String CONFIG_FILE_NAME = "recommendations.config"; 14 | 15 | private final Path articleFolder; 16 | private final Path talkFolder; 17 | private final Path videoFolder; 18 | private final Optional outputFile; 19 | 20 | private Config(String[] raw) { 21 | if (raw.length == 0) 22 | throw new IllegalArgumentException("No article path defined."); 23 | 24 | this.articleFolder = readFolder(raw[0]); 25 | this.talkFolder = readFolder(raw[1]); 26 | this.videoFolder = readFolder(raw[2]); 27 | 28 | Optional outputFile = raw.length >= 4 29 | ? Optional.of(raw[3]) 30 | : Optional.empty(); 31 | this.outputFile = outputFile 32 | .map(file -> Paths.get(System.getProperty("user.dir")).resolve(file)); 33 | this.outputFile.ifPresent(file -> { 34 | boolean notWritable = Files.exists(file) && !Files.isWritable(file); 35 | if (notWritable) 36 | throw new IllegalArgumentException("Output path is not writable: " + this.outputFile.get()); 37 | }); 38 | } 39 | 40 | private static Path readFolder(String raw) { 41 | Path folder = Paths.get(raw); 42 | if (!Files.exists(folder)) 43 | throw new IllegalArgumentException("Path doesn't exist: " + folder); 44 | if (!Files.isDirectory(folder)) 45 | throw new IllegalArgumentException("Path is no directory: " + folder); 46 | return folder; 47 | } 48 | 49 | public Path articleFolder() { 50 | return articleFolder; 51 | } 52 | 53 | public Path talkFolder() { 54 | return talkFolder; 55 | } 56 | 57 | public Path videoFolder() { 58 | return videoFolder; 59 | } 60 | 61 | public Optional outputFile() { 62 | return outputFile; 63 | } 64 | 65 | public static CompletableFuture create(String[] args) { 66 | CompletableFuture rawConfig = args.length > 0 67 | ? CompletableFuture.completedFuture(args) 68 | : readProjectConfig() 69 | .exceptionally(__ -> readUserConfig().join()) 70 | .exceptionally(__ -> new String[0]); 71 | 72 | return rawConfig 73 | .thenApply(Config::new); 74 | } 75 | 76 | private static CompletableFuture readProjectConfig() { 77 | Path workingDir = Paths.get(System.getProperty("user.dir")).resolve(CONFIG_FILE_NAME); 78 | return readConfig(workingDir); 79 | } 80 | 81 | private static CompletableFuture readUserConfig() { 82 | Path workingDir = Paths.get(System.getProperty("user.home")).resolve(CONFIG_FILE_NAME); 83 | return readConfig(workingDir); 84 | } 85 | 86 | private static CompletableFuture readConfig(Path workingDir) { 87 | return CompletableFuture.supplyAsync(() -> { 88 | try { 89 | return Files.readAllLines(workingDir).toArray(new String[0]); 90 | } catch (IOException ex) { 91 | throw new UncheckedIOException(ex); 92 | } 93 | }); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/Main.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.GenealogistService; 5 | import org.codefx.java_after_eight.genealogy.Genealogy; 6 | import org.codefx.java_after_eight.genealogy.Relation; 7 | import org.codefx.java_after_eight.genealogy.Weights; 8 | import org.codefx.java_after_eight.post.Post; 9 | import org.codefx.java_after_eight.post.factories.ArticleFactory; 10 | import org.codefx.java_after_eight.post.factories.TalkFactory; 11 | import org.codefx.java_after_eight.post.factories.VideoFactory; 12 | import org.codefx.java_after_eight.recommendation.Recommendation; 13 | import org.codefx.java_after_eight.recommendation.Recommender; 14 | 15 | import java.nio.file.Files; 16 | import java.nio.file.Path; 17 | import java.util.ArrayList; 18 | import java.util.Collection; 19 | import java.util.List; 20 | import java.util.ServiceLoader; 21 | import java.util.stream.Stream; 22 | 23 | import static java.util.stream.Collectors.joining; 24 | import static java.util.stream.Collectors.toList; 25 | import static org.codefx.java_after_eight.Utils.concat; 26 | 27 | public class Main { 28 | 29 | public static void main(String[] args) { 30 | System.out.println(ProcessDetails.details()); 31 | 32 | Config config = Config.create(args).join(); 33 | Genealogy genealogy = createGenealogy(config.articleFolder(), config.talkFolder(), config.videoFolder()); 34 | Recommender recommender = new Recommender(); 35 | 36 | Stream relations = genealogy.inferRelations(); 37 | Stream recommendations = recommender.recommend(relations, 3); 38 | String recommendationsAsJson = recommendationsToJson(recommendations); 39 | 40 | if (config.outputFile().isPresent()) 41 | Utils.uncheckedFilesWrite(config.outputFile().get(), recommendationsAsJson); 42 | else 43 | System.out.println(recommendationsAsJson); 44 | } 45 | 46 | private static Genealogy createGenealogy(Path articleFolder, Path talkFolder, Path videoFolder) { 47 | List posts = concat( 48 | markdownFilesIn(articleFolder).map(ArticleFactory::createArticle), 49 | markdownFilesIn(talkFolder).map(TalkFactory::createTalk), 50 | markdownFilesIn(videoFolder).map(VideoFactory::createVideo) 51 | ).collect(toList()); 52 | Collection genealogists = getGenealogists(posts); 53 | return new Genealogy(posts, genealogists, Weights.allEqual()); 54 | } 55 | 56 | private static Stream markdownFilesIn(Path folder) { 57 | return Utils.uncheckedFilesList(folder) 58 | .filter(Files::isRegularFile) 59 | .filter(file -> file.toString().endsWith(".md")); 60 | } 61 | 62 | private static Collection getGenealogists(Collection posts) { 63 | List genealogists = new ArrayList<>(); 64 | ServiceLoader 65 | .load(GenealogistService.class) 66 | .forEach(service -> genealogists.add(service.procure(posts))); 67 | if (genealogists.isEmpty()) 68 | throw new IllegalArgumentException("No genealogists found."); 69 | return genealogists; 70 | } 71 | 72 | private static String recommendationsToJson(Stream recommendations) { 73 | String frame = "[\n$RECOMMENDATIONS\n]"; 74 | String recommendation = "" + 75 | "\t{" + 76 | "\n\t\t\"title\": \"$TITLE\",\n" + 77 | "\t\t\"recommendations\": [\n" + 78 | "$RECOMMENDED_POSTS\n" + 79 | "\t\t]\n" + 80 | "\t}"; 81 | String recommendedPost = "" + 82 | "\t\t\t{ \"title\": \"$TITLE\" }"; 83 | 84 | String recs = recommendations 85 | .map(rec -> { 86 | String posts = rec 87 | .recommendedPosts() 88 | .map(recArt -> recArt.title().text()) 89 | .map(recTitle -> recommendedPost.replace("$TITLE", recTitle)) 90 | .collect(joining(",\n")); 91 | return recommendation 92 | .replace("$TITLE", rec.post().title().text()) 93 | .replace("$RECOMMENDED_POSTS", posts); 94 | }) 95 | .collect(joining(",\n")); 96 | return frame.replace("$RECOMMENDATIONS", recs); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/ProcessDetails.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.lang.management.ManagementFactory; 9 | import java.lang.management.RuntimeMXBean; 10 | import java.lang.reflect.Field; 11 | import java.lang.reflect.Method; 12 | import java.util.Optional; 13 | 14 | import static java.lang.String.format; 15 | 16 | public class ProcessDetails { 17 | 18 | public static String details() { 19 | return format( 20 | "Process ID: %s | Major Java version: %s", 21 | getPid().map(Object::toString).orElse("unknown"), 22 | getMajorJavaVersion().map(Object::toString).orElse("unknown")); 23 | } 24 | 25 | public static Optional getPid() { 26 | Optional pid = getPidFromMxBeanName(); 27 | if (pid.isPresent()) 28 | return pid; 29 | pid = getPidFromMxBeanInternal(); 30 | if (pid.isPresent()) 31 | return pid; 32 | pid = getPidFromProcSelfSymlink(); 33 | if (pid.isPresent()) 34 | return pid; 35 | pid = getPidFromBashPid(); 36 | if (pid.isPresent()) 37 | return pid; 38 | 39 | return Optional.empty(); 40 | } 41 | 42 | private static Optional getPidFromMxBeanName() { 43 | // on many VMs `ManagementFactory.getRuntimeMXBean().getName()` 44 | // returns something like 1234@localhost 45 | String[] pidAndHost = ManagementFactory.getRuntimeMXBean().getName().split("@"); 46 | try { 47 | return Optional.of(Long.parseLong(pidAndHost[0])); 48 | } catch (NumberFormatException ex) { 49 | return Optional.empty(); 50 | } 51 | } 52 | 53 | private static Optional getPidFromMxBeanInternal() { 54 | // crosses fingers 55 | try { 56 | RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); 57 | Field jvm_field = runtime.getClass().getDeclaredField("jvm"); 58 | jvm_field.setAccessible(true); 59 | Object mgmt = jvm_field.get(runtime); 60 | 61 | Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId"); 62 | pid_method.setAccessible(true); 63 | int pid = (int) pid_method.invoke(mgmt); 64 | 65 | return Optional.of((long) pid); 66 | } catch (ClassCastException | ReflectiveOperationException ex) { 67 | return Optional.empty(); 68 | } 69 | } 70 | 71 | private static Optional getPidFromProcSelfSymlink() { 72 | try { 73 | String pid = new File("/proc/self").getCanonicalFile().getName(); 74 | return Optional.of(Long.parseLong(pid)); 75 | } catch (IOException | NumberFormatException ex) { 76 | return Optional.empty(); 77 | } 78 | } 79 | 80 | private static Optional getPidFromBashPid() { 81 | // if we're on Linux, this should work on all POSIX shells 82 | try { 83 | InputStream echoPid = new ProcessBuilder("sh", "-c", "echo $PPID").start().getInputStream(); 84 | String pid = new BufferedReader(new InputStreamReader(echoPid)).readLine(); 85 | return Optional.of(Long.parseLong(pid)); 86 | } catch (IOException | NumberFormatException ex) { 87 | return Optional.empty(); 88 | } 89 | } 90 | 91 | public static Optional getMajorJavaVersion() { 92 | try { 93 | String version = System.getProperty("java.version"); 94 | if (version.startsWith("1.")) 95 | return Optional.of(Integer.parseInt(version.substring(2, 3))); 96 | 97 | if (version.contains(".")) 98 | return Optional.of(Integer.parseInt(version.split("\\.")[0])); 99 | 100 | // hail mary 101 | return Optional.of(Integer.parseInt(version)); 102 | } catch (NumberFormatException ex) { 103 | return Optional.empty(); 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/Utils.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight; 2 | 3 | import java.io.IOException; 4 | import java.io.UncheckedIOException; 5 | import java.nio.charset.StandardCharsets; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.Objects; 11 | import java.util.Optional; 12 | import java.util.concurrent.atomic.AtomicReference; 13 | import java.util.function.BiPredicate; 14 | import java.util.stream.Collector; 15 | import java.util.stream.Stream; 16 | 17 | import static java.lang.String.format; 18 | 19 | public final class Utils { 20 | 21 | private Utils() { 22 | // private constructor to prevent accidental instantiation of utility class 23 | } 24 | 25 | public static String removeOuterQuotationMarks(String string) { 26 | return string.replaceAll("^\"|\"$", ""); 27 | } 28 | 29 | public static Stream uncheckedFilesList(Path dir) { 30 | try { 31 | return Files.list(dir); 32 | } catch (IOException ex) { 33 | throw new UncheckedIOException(ex); 34 | } 35 | } 36 | 37 | public static void uncheckedFilesWrite(Path path, String content) { 38 | try { 39 | Files.write(path, Arrays.asList(content), StandardCharsets.UTF_8); 40 | } catch (IOException ex) { 41 | throw new UncheckedIOException(ex); 42 | } 43 | } 44 | 45 | public static List uncheckedFilesReadAllLines(Path file) { 46 | try { 47 | return Files.readAllLines(file, StandardCharsets.UTF_8); 48 | } catch (IOException ex) { 49 | throw new UncheckedIOException(ex); 50 | } 51 | } 52 | 53 | public static Stream uncheckedFilesLines(Path file) { 54 | try { 55 | return Files.lines(file, StandardCharsets.UTF_8); 56 | } catch (IOException ex) { 57 | throw new UncheckedIOException(ex); 58 | } 59 | } 60 | 61 | @SuppressWarnings("unchecked") 62 | public static Stream concat(Stream... streams) { 63 | return Stream.of(streams).flatMap(s -> s); 64 | } 65 | 66 | public static Collector> collectEqualElement() { 67 | return collectEqualElement(Objects::equals); 68 | } 69 | 70 | public static Collector> collectEqualElement( 71 | BiPredicate equals) { 72 | return Collector.of( 73 | AtomicReference::new, 74 | (AtomicReference left, ELEMENT right) -> { 75 | if (left.get() != null && !equals.test(left.get(), right)) 76 | throw new IllegalArgumentException( 77 | format("Unequal elements in stream: %s vs %s", left.get(), right)); 78 | left.set(right); 79 | }, 80 | (AtomicReference left, AtomicReference right) -> { 81 | if (left.get() != null && right.get() != null && !equals.test(left.get(), right.get())) 82 | throw new IllegalArgumentException( 83 | format("Unequal elements in stream: %s vs %s", left.get(), right.get())); 84 | return left.get() != null ? left : right; 85 | }, 86 | reference -> Optional.ofNullable(reference.get()) 87 | ); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/genealogist/Genealogist.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogist; 2 | 3 | import org.codefx.java_after_eight.post.Post; 4 | 5 | public interface Genealogist { 6 | 7 | TypedRelation infer(Post post1, Post post2); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/genealogist/GenealogistService.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogist; 2 | 3 | import org.codefx.java_after_eight.post.Post; 4 | 5 | import java.util.Collection; 6 | 7 | /** 8 | * Used as a service to create {@link Genealogist}s - must have a public parameterless constructor. 9 | * 10 | *

Intended use: 11 | *

12 |  * {@code
13 |  * Collection posts = // ... ;
14 |  * GenealogistService service = new SpecificGenealogistService();
15 |  * Genealogist genealogist = service.create(posts);
16 |  * }
17 |  * 
18 | */ 19 | public interface GenealogistService { 20 | 21 | Genealogist procure(Collection posts); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/genealogist/RelationType.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogist; 2 | 3 | import java.util.Objects; 4 | 5 | import static java.util.Objects.requireNonNull; 6 | 7 | public class RelationType { 8 | 9 | // `RelationType` is a string (and not an enum) because {@code Genealogist} implementations 10 | // can be plugged in via services, which means their type is unknown at runtime. 11 | 12 | private final String value; 13 | 14 | public RelationType(String value) { 15 | this.value = requireNonNull(value); 16 | if (value.isEmpty()) 17 | throw new IllegalArgumentException("Relation types can't have an empty value."); 18 | } 19 | 20 | public String value() { 21 | return value; 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) 27 | return true; 28 | if (o == null || getClass() != o.getClass()) 29 | return false; 30 | RelationType slug = (RelationType) o; 31 | return value.equals(slug.value); 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | return Objects.hash(value); 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "RelationType{" + 42 | "value='" + value + '\'' + 43 | '}'; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/genealogist/TypedRelation.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogist; 2 | 3 | import org.codefx.java_after_eight.post.Post; 4 | 5 | import java.util.Objects; 6 | 7 | import static java.util.Objects.requireNonNull; 8 | 9 | public class TypedRelation { 10 | 11 | private final Post post1; 12 | private final Post post2; 13 | private final RelationType type; 14 | private final long score; 15 | 16 | public TypedRelation(Post post1, Post post2, RelationType type, long score) { 17 | this.post1 = requireNonNull(post1); 18 | this.post2 = requireNonNull(post2); 19 | this.type = requireNonNull(type); 20 | this.score = score; 21 | if (score < 0 || 100 < score) 22 | throw new IllegalArgumentException("Score should be in interval [0; 100]: " + score); 23 | } 24 | 25 | public Post post1() { 26 | return post1; 27 | } 28 | 29 | public Post post2() { 30 | return post2; 31 | } 32 | 33 | public RelationType type() { 34 | return type; 35 | } 36 | 37 | public long score() { 38 | return score; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) 44 | return true; 45 | if (o == null || getClass() != o.getClass()) 46 | return false; 47 | TypedRelation that = (TypedRelation) o; 48 | return score == that.score && 49 | post1.equals(that.post1) && 50 | post2.equals(that.post2) && 51 | type.equals(that.type); 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return Objects.hash(post1, post2, type, score); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "Relation{" + 62 | "post1=" + post1.slug().value() + 63 | ", post2=" + post2.slug().value() + 64 | ", type='" + type + '\'' + 65 | ", score=" + score + 66 | '}'; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Genealogy.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogy; 2 | 3 | import org.codefx.java_after_eight.genealogist.Genealogist; 4 | import org.codefx.java_after_eight.genealogist.TypedRelation; 5 | import org.codefx.java_after_eight.post.Post; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collection; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.stream.Stream; 12 | 13 | import static java.util.Objects.requireNonNull; 14 | 15 | public class Genealogy { 16 | 17 | private final Collection posts; 18 | private final Collection genealogists; 19 | private final Weights weights; 20 | 21 | public Genealogy(Collection posts, Collection genealogists, Weights weights) { 22 | this.posts = requireNonNull(posts); 23 | this.genealogists = requireNonNull(genealogists); 24 | this.weights = requireNonNull(weights); 25 | } 26 | 27 | public Stream inferRelations() { 28 | return aggregateTypedRelations(inferTypedRelations()); 29 | } 30 | 31 | private Stream aggregateTypedRelations(Stream typedRelations) { 32 | Map>> sortedTypedRelations = new HashMap<>(); 33 | typedRelations.forEach(relation -> sortedTypedRelations 34 | .computeIfAbsent(relation.post1(), __ -> new HashMap<>()) 35 | .computeIfAbsent(relation.post2(), __ -> new ArrayList<>()) 36 | .add(relation)); 37 | return sortedTypedRelations 38 | .values().stream() 39 | .flatMap(postWithRelations -> postWithRelations.values().stream()) 40 | .map(relations -> Relation.aggregate(relations.stream(), weights)); 41 | } 42 | 43 | private Stream inferTypedRelations() { 44 | return posts.stream() 45 | .flatMap(post1 -> posts.stream() 46 | .map(post2 -> new Posts(post1, post2))) 47 | // no need to compare posts with themselves 48 | .filter(posts -> posts.post1 != posts.post2) 49 | .flatMap(posts -> genealogists.stream() 50 | .map(genealogist -> new PostResearch(genealogist, posts))) 51 | .map(PostResearch::infer); 52 | } 53 | 54 | private static class Posts { 55 | 56 | final Post post1; 57 | final Post post2; 58 | 59 | Posts(Post post1, Post post2) { 60 | this.post1 = post1; 61 | this.post2 = post2; 62 | } 63 | 64 | } 65 | 66 | private static class PostResearch { 67 | 68 | final Genealogist genealogist; 69 | final Posts posts; 70 | 71 | PostResearch(Genealogist genealogist, Posts posts) { 72 | this.genealogist = genealogist; 73 | this.posts = posts; 74 | } 75 | 76 | TypedRelation infer() { 77 | return genealogist.infer(posts.post1, posts.post2); 78 | } 79 | 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Relation.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogy; 2 | 3 | import org.codefx.java_after_eight.genealogist.TypedRelation; 4 | import org.codefx.java_after_eight.post.Post; 5 | 6 | import java.util.Objects; 7 | import java.util.stream.Stream; 8 | 9 | import static java.lang.Math.round; 10 | import static java.lang.String.format; 11 | import static java.util.Objects.requireNonNull; 12 | 13 | public class Relation { 14 | 15 | private final Post post1; 16 | private final Post post2; 17 | private final long score; 18 | 19 | Relation(Post post1, Post post2, long score) { 20 | this.post1 = requireNonNull(post1); 21 | this.post2 = requireNonNull(post2); 22 | this.score = score; 23 | 24 | if (score < 0 || 100 < score) 25 | throw new IllegalArgumentException("Score should be in interval [0; 100]: " + toString()); 26 | } 27 | 28 | static Relation aggregate(Stream typedRelations, Weights weights) { 29 | return typedRelations 30 | .map(relation -> new UnfinishedRelation(relation, weights.weightOf(relation.type()))) 31 | .reduce(UnfinishedRelation::fold) 32 | .map(UnfinishedRelation::finish) 33 | .orElseThrow(() -> new IllegalArgumentException("Can't create relation from zero typed relations.")); 34 | } 35 | 36 | public Post post1() { 37 | return post1; 38 | } 39 | 40 | public Post post2() { 41 | return post2; 42 | } 43 | 44 | public long score() { 45 | return score; 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (this == o) 51 | return true; 52 | if (o == null || getClass() != o.getClass()) 53 | return false; 54 | Relation relation = (Relation) o; 55 | return score == relation.score && 56 | post1.equals(relation.post1) && 57 | post2.equals(relation.post2); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | return Objects.hash(post1, post2, score); 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "Relation{" + 68 | "post1=" + post1.slug().value() + 69 | ", post2=" + post2.slug().value() + 70 | ", score=" + score + 71 | '}'; 72 | } 73 | 74 | private static class UnfinishedRelation { 75 | 76 | private final Post post1; 77 | private final Post post2; 78 | private double scoreTotal; 79 | private long scoreCount; 80 | 81 | public UnfinishedRelation(TypedRelation relation, double weight) { 82 | this.post1 = relation.post1(); 83 | this.post2 = relation.post2(); 84 | this.scoreTotal = relation.score() * weight; 85 | this.scoreCount = 1; 86 | } 87 | 88 | public UnfinishedRelation fold(UnfinishedRelation other) { 89 | if (post1 != other.post1) 90 | throw new IllegalArgumentException(format( 91 | "All typed relations must belong to the same post: %s vs %s", post1, other.post1)); 92 | if (post2 != other.post2) 93 | throw new IllegalArgumentException(format( 94 | "All typed relations must belong to the same post: %s vs %s", post2, other.post2)); 95 | scoreTotal += other.scoreTotal; 96 | scoreCount += other.scoreCount; 97 | return this; 98 | } 99 | 100 | public Relation finish() { 101 | return new Relation(post1, post2, round(scoreTotal / scoreCount)); 102 | } 103 | 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/genealogy/Weights.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogy; 2 | 3 | import org.codefx.java_after_eight.genealogist.RelationType; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class Weights { 9 | 10 | private final Map weights; 11 | private final double defaultWeight; 12 | 13 | public Weights(Map weights, double defaultWeight) { 14 | this.weights = new HashMap<>(weights); 15 | if (this.weights.entrySet().stream().anyMatch(entry -> entry.getKey() == null || entry.getValue() == null)) 16 | throw new NullPointerException("Neither relation type nor weight can be null."); 17 | this.defaultWeight = defaultWeight; 18 | } 19 | 20 | public static Weights allEqual() { 21 | return new Weights(new HashMap<>(), 1); 22 | } 23 | 24 | public double weightOf(RelationType genealogistType) { 25 | return weights.getOrDefault(genealogistType, defaultWeight); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/Article.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Objects; 5 | import java.util.Optional; 6 | import java.util.Set; 7 | import java.util.stream.Stream; 8 | 9 | import static java.util.Objects.requireNonNull; 10 | 11 | public class Article implements Post { 12 | 13 | private final Title title; 14 | private final Set tags; 15 | private final LocalDate date; 16 | private final Description description; 17 | private final Slug slug; 18 | 19 | private final Optional repository; 20 | private final Content content; 21 | 22 | public Article(Title title, Set tags, LocalDate date, Description description, Slug slug, Optional repository, Content content) { 23 | this.title = requireNonNull(title); 24 | this.tags = requireNonNull(tags); 25 | this.date = requireNonNull(date); 26 | this.description = requireNonNull(description); 27 | this.slug = requireNonNull(slug); 28 | this.repository = requireNonNull(repository); 29 | this.content = requireNonNull(content); 30 | } 31 | 32 | @Override 33 | public Title title() { 34 | return title; 35 | } 36 | 37 | @Override 38 | public Stream tags() { 39 | return tags.stream(); 40 | } 41 | 42 | @Override 43 | public LocalDate date() { 44 | return date; 45 | } 46 | 47 | @Override 48 | public Description description() { 49 | return description; 50 | } 51 | 52 | @Override 53 | public Slug slug() { 54 | return slug; 55 | } 56 | 57 | public Optional repository() { 58 | return repository; 59 | } 60 | 61 | public Content content() { 62 | return content; 63 | } 64 | 65 | @Override 66 | public boolean equals(Object o) { 67 | if (this == o) 68 | return true; 69 | if (o == null || getClass() != o.getClass()) 70 | return false; 71 | Article article = (Article) o; 72 | return slug.equals(article.slug); 73 | } 74 | 75 | @Override 76 | public int hashCode() { 77 | return Objects.hash(slug); 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return "Article{" + 83 | "title=" + title + 84 | ", tags=" + tags + 85 | ", date=" + date + 86 | ", description=" + description + 87 | ", slug=" + slug + 88 | ", repository=" + repository + 89 | '}'; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/Content.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import java.util.function.Supplier; 4 | import java.util.stream.Stream; 5 | 6 | @FunctionalInterface 7 | public interface Content extends Supplier> { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/Description.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import org.codefx.java_after_eight.Utils; 4 | 5 | import java.util.Objects; 6 | 7 | import static java.util.Objects.requireNonNull; 8 | 9 | public class Description { 10 | 11 | private final String text; 12 | 13 | public Description(String text) { 14 | requireNonNull(text); 15 | String unquotedText = Utils.removeOuterQuotationMarks(text).trim(); 16 | if (unquotedText.isEmpty()) 17 | throw new IllegalArgumentException("Description can't have an empty text."); 18 | this.text = unquotedText; 19 | } 20 | 21 | public String text() { 22 | return text; 23 | } 24 | 25 | @Override 26 | public boolean equals(Object o) { 27 | if (this == o) 28 | return true; 29 | if (o == null || getClass() != o.getClass()) 30 | return false; 31 | Description that = (Description) o; 32 | return text.equals(that.text); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return Objects.hash(text); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "Description{" + 43 | "text='" + text + '\'' + 44 | '}'; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/Post.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import java.time.LocalDate; 4 | import java.util.stream.Stream; 5 | 6 | public interface Post { 7 | 8 | Title title(); 9 | 10 | Stream tags(); 11 | 12 | LocalDate date(); 13 | 14 | Description description(); 15 | 16 | Slug slug(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/Repository.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import java.util.Objects; 4 | 5 | import static java.util.Objects.requireNonNull; 6 | 7 | public class Repository { 8 | 9 | private final String identifier; 10 | 11 | public Repository(String identifier) { 12 | this.identifier = requireNonNull(identifier); 13 | if (identifier.isEmpty()) 14 | throw new IllegalArgumentException("Repositories can't have an empty identifier."); 15 | } 16 | 17 | public String identifier() { 18 | return identifier; 19 | } 20 | 21 | @Override 22 | public boolean equals(Object o) { 23 | if (this == o) 24 | return true; 25 | if (o == null || getClass() != o.getClass()) 26 | return false; 27 | Repository that = (Repository) o; 28 | return identifier.equals(that.identifier); 29 | } 30 | 31 | @Override 32 | public int hashCode() { 33 | return Objects.hash(identifier); 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "Repository{" + 39 | "identifier='" + identifier + '\'' + 40 | '}'; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/Slug.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import java.util.Objects; 4 | 5 | import static java.util.Objects.requireNonNull; 6 | 7 | public class Slug implements Comparable { 8 | 9 | private final String value; 10 | 11 | public Slug(String value) { 12 | this.value = requireNonNull(value); 13 | if (value.isEmpty()) 14 | throw new IllegalArgumentException("Slugs can't have an empty value."); 15 | } 16 | 17 | public String value() { 18 | return value; 19 | } 20 | 21 | @Override 22 | public int compareTo(Slug right) { 23 | return this.value.compareTo(right.value); 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) 29 | return true; 30 | if (o == null || getClass() != o.getClass()) 31 | return false; 32 | Slug slug = (Slug) o; 33 | return value.equals(slug.value); 34 | } 35 | 36 | @Override 37 | public int hashCode() { 38 | return Objects.hash(value); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "Slug{" + 44 | "value='" + value + '\'' + 45 | '}'; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/Tag.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import java.util.Collections; 4 | import java.util.Objects; 5 | import java.util.Set; 6 | import java.util.stream.Stream; 7 | 8 | import static java.util.Objects.requireNonNull; 9 | import static java.util.stream.Collectors.toSet; 10 | 11 | public class Tag { 12 | 13 | private final String text; 14 | 15 | private Tag(String text) { 16 | this.text = requireNonNull(text); 17 | if (text.isEmpty()) 18 | throw new IllegalArgumentException("Tags can't have an empty text."); 19 | } 20 | 21 | public static Set from(String tagsText) { 22 | Set tags = Stream.of(tagsText 23 | .replaceAll("^\\[|\\]$", "") 24 | .split(",")) 25 | .map(String::trim) 26 | .filter(tag -> !tag.isEmpty()) 27 | .map(Tag::new) 28 | .collect(toSet()); 29 | return Collections.unmodifiableSet(tags); 30 | } 31 | 32 | public String text() { 33 | return text; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) 39 | return true; 40 | if (o == null || getClass() != o.getClass()) 41 | return false; 42 | Tag tag = (Tag) o; 43 | return text.equals(tag.text); 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | return Objects.hash(text); 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "Tag{" + 54 | "text='" + text + '\'' + 55 | '}'; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/Talk.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import java.net.URI; 4 | import java.time.LocalDate; 5 | import java.util.Objects; 6 | import java.util.Optional; 7 | import java.util.Set; 8 | import java.util.stream.Stream; 9 | 10 | import static java.util.Objects.requireNonNull; 11 | 12 | public class Talk implements Post { 13 | 14 | private final Title title; 15 | private final Set tags; 16 | private final LocalDate date; 17 | private final Description description; 18 | private final Slug slug; 19 | 20 | private final URI slides; 21 | private final Optional video; 22 | 23 | public Talk(Title title, Set tags, LocalDate date, Description description, Slug slug, URI slides, Optional video) { 24 | this.title = requireNonNull(title); 25 | this.tags = requireNonNull(tags); 26 | this.date = requireNonNull(date); 27 | this.description = requireNonNull(description); 28 | this.slug = requireNonNull(slug); 29 | this.slides = requireNonNull(slides); 30 | this.video = requireNonNull(video); 31 | } 32 | 33 | @Override 34 | public Title title() { 35 | return title; 36 | } 37 | 38 | @Override 39 | public Stream tags() { 40 | return tags.stream(); 41 | } 42 | 43 | @Override 44 | public LocalDate date() { 45 | return date; 46 | } 47 | 48 | @Override 49 | public Description description() { 50 | return description; 51 | } 52 | 53 | @Override 54 | public Slug slug() { 55 | return slug; 56 | } 57 | 58 | public URI slides() { 59 | return slides; 60 | } 61 | 62 | public Optional video() { 63 | return video; 64 | } 65 | 66 | @Override 67 | public boolean equals(Object o) { 68 | if (this == o) 69 | return true; 70 | if (o == null || getClass() != o.getClass()) 71 | return false; 72 | Talk video = (Talk) o; 73 | return slug.equals(video.slug); 74 | } 75 | 76 | @Override 77 | public int hashCode() { 78 | return Objects.hash(slug); 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return "Video{" + 84 | "title=" + title + 85 | ", tags=" + tags + 86 | ", date=" + date + 87 | ", description=" + description + 88 | ", slug=" + slug + 89 | ", slides=" + slides + 90 | ", video=" + video + 91 | '}'; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/Title.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import org.codefx.java_after_eight.Utils; 4 | 5 | import java.util.Objects; 6 | 7 | import static java.util.Objects.requireNonNull; 8 | 9 | public class Title { 10 | 11 | private final String text; 12 | 13 | public Title(String text) { 14 | requireNonNull(text); 15 | String unquotedText = Utils.removeOuterQuotationMarks(text).trim(); 16 | if (unquotedText.isEmpty()) 17 | throw new IllegalArgumentException("Titles can't have an empty text."); 18 | this.text = unquotedText; 19 | } 20 | 21 | public String text() { 22 | return text; 23 | } 24 | 25 | @Override 26 | public boolean equals(Object o) { 27 | if (this == o) 28 | return true; 29 | if (o == null || getClass() != o.getClass()) 30 | return false; 31 | Title title = (Title) o; 32 | return text.equals(title.text); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return Objects.hash(text); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "Title{" + 43 | "value='" + text + '\'' + 44 | '}'; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/Video.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Objects; 5 | import java.util.Optional; 6 | import java.util.Set; 7 | import java.util.stream.Stream; 8 | 9 | import static java.util.Objects.requireNonNull; 10 | 11 | public class Video implements Post { 12 | 13 | private final Title title; 14 | private final Set tags; 15 | private final LocalDate date; 16 | private final Description description; 17 | private final Slug slug; 18 | 19 | private final VideoSlug video; 20 | private final Optional repository; 21 | 22 | public Video(Title title, Set tags, LocalDate date, Description description, Slug slug, VideoSlug video, Optional repository) { 23 | this.title = requireNonNull(title); 24 | this.tags = requireNonNull(tags); 25 | this.date = requireNonNull(date); 26 | this.description = requireNonNull(description); 27 | this.slug = requireNonNull(slug); 28 | this.video = requireNonNull(video); 29 | this.repository = requireNonNull(repository); 30 | } 31 | 32 | @Override 33 | public Title title() { 34 | return title; 35 | } 36 | 37 | @Override 38 | public Stream tags() { 39 | return tags.stream(); 40 | } 41 | 42 | @Override 43 | public LocalDate date() { 44 | return date; 45 | } 46 | 47 | @Override 48 | public Description description() { 49 | return description; 50 | } 51 | 52 | @Override 53 | public Slug slug() { 54 | return slug; 55 | } 56 | 57 | public VideoSlug video() { 58 | return video; 59 | } 60 | 61 | public Optional repository() { 62 | return repository; 63 | } 64 | 65 | @Override 66 | public boolean equals(Object o) { 67 | if (this == o) 68 | return true; 69 | if (o == null || getClass() != o.getClass()) 70 | return false; 71 | Video video = (Video) o; 72 | return slug.equals(video.slug); 73 | } 74 | 75 | @Override 76 | public int hashCode() { 77 | return Objects.hash(slug); 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return "Video{" + 83 | "title=" + title + 84 | ", tags=" + tags + 85 | ", date=" + date + 86 | ", description=" + description + 87 | ", slug=" + slug + 88 | ", url=" + video + 89 | ", repository=" + repository + 90 | '}'; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/VideoSlug.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import java.util.Objects; 4 | 5 | import static java.util.Objects.requireNonNull; 6 | 7 | public class VideoSlug implements Comparable { 8 | 9 | private final String value; 10 | 11 | public VideoSlug(String value) { 12 | this.value = requireNonNull(value); 13 | if (value.isEmpty()) 14 | throw new IllegalArgumentException("Slugs can't have an empty value."); 15 | } 16 | 17 | public String value() { 18 | return value; 19 | } 20 | 21 | @Override 22 | public int compareTo(VideoSlug right) { 23 | return this.value.compareTo(right.value); 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) 29 | return true; 30 | if (o == null || getClass() != o.getClass()) 31 | return false; 32 | VideoSlug slug = (VideoSlug) o; 33 | return value.equals(slug.value); 34 | } 35 | 36 | @Override 37 | public int hashCode() { 38 | return Objects.hash(value); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "VideoSlug{" + 44 | "value='" + value + '\'' + 45 | '}'; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/factories/ArticleFactory.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post.factories; 2 | 3 | import org.codefx.java_after_eight.post.Article; 4 | import org.codefx.java_after_eight.post.Description; 5 | import org.codefx.java_after_eight.post.Repository; 6 | import org.codefx.java_after_eight.post.Slug; 7 | import org.codefx.java_after_eight.post.Tag; 8 | import org.codefx.java_after_eight.post.Title; 9 | 10 | import java.nio.file.Path; 11 | import java.time.LocalDate; 12 | import java.util.List; 13 | 14 | import static org.codefx.java_after_eight.post.factories.PostFactory.DATE; 15 | import static org.codefx.java_after_eight.post.factories.PostFactory.DESCRIPTION; 16 | import static org.codefx.java_after_eight.post.factories.PostFactory.REPOSITORY; 17 | import static org.codefx.java_after_eight.post.factories.PostFactory.SLUG; 18 | import static org.codefx.java_after_eight.post.factories.PostFactory.TAGS; 19 | import static org.codefx.java_after_eight.post.factories.PostFactory.TITLE; 20 | 21 | public final class ArticleFactory { 22 | 23 | private ArticleFactory() { 24 | // private constructor to prevent accidental instantiation of utility class 25 | } 26 | 27 | public static Article createArticle(Path file) { 28 | try { 29 | RawPost post = PostFactory.readPost(file); 30 | return createArticle(post); 31 | } catch (RuntimeException ex) { 32 | throw new RuntimeException("Creating article failed: " + file, ex); 33 | } 34 | } 35 | 36 | public static Article createArticle(List fileLines) { 37 | RawPost post = PostFactory.readPost(fileLines); 38 | return createArticle(post); 39 | } 40 | 41 | private static Article createArticle(RawPost post) { 42 | RawFrontMatter frontMatter = post.frontMatter(); 43 | return new Article( 44 | new Title(frontMatter.requiredValueOf(TITLE)), 45 | Tag.from(frontMatter.requiredValueOf(TAGS)), 46 | LocalDate.parse(frontMatter.requiredValueOf(DATE)), 47 | new Description(frontMatter.requiredValueOf(DESCRIPTION)), 48 | new Slug(frontMatter.requiredValueOf(SLUG)), 49 | frontMatter.valueOf(REPOSITORY).map(Repository::new), 50 | post.content()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/factories/PostFactory.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post.factories; 2 | 3 | import org.codefx.java_after_eight.Utils; 4 | import org.codefx.java_after_eight.post.Content; 5 | 6 | import java.nio.file.Path; 7 | import java.util.AbstractMap; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import static java.util.stream.Collectors.toMap; 13 | 14 | final class PostFactory { 15 | 16 | public static final String DATE = "date"; 17 | public static final String DESCRIPTION = "description"; 18 | public static final String REPOSITORY = "repo"; 19 | public static final String SLIDES = "slides"; 20 | public static final String SLUG = "slug"; 21 | public static final String TAGS = "tags"; 22 | public static final String TITLE = "title"; 23 | public static final String VIDEO = "videoSlug"; 24 | 25 | private static final String FRONT_MATTER_SEPARATOR = "---"; 26 | 27 | private PostFactory() { 28 | // private constructor to prevent accidental instantiation of utility class 29 | } 30 | 31 | public static RawPost readPost(Path file) { 32 | try { 33 | List eagerLines = Utils.uncheckedFilesReadAllLines(file); 34 | return readPost(eagerLines); 35 | } catch (RuntimeException ex) { 36 | throw new RuntimeException("Creating article failed: " + file, ex); 37 | } 38 | } 39 | 40 | public static RawPost readPost(List fileLines) { 41 | RawFrontMatter frontMatter = extractFrontMatter(fileLines); 42 | Content content = () -> extractContent(fileLines).stream(); 43 | return new RawPost(frontMatter, content); 44 | } 45 | 46 | private static RawFrontMatter extractFrontMatter(List fileLines) { 47 | List frontMatterLines = readFrontMatter(fileLines); 48 | Map frontMatter = frontMatterLines.stream() 49 | .filter(line -> !line.startsWith("#")) 50 | .map(PostFactory::keyValuePairFrom) 51 | .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); 52 | return new RawFrontMatter(frontMatter); 53 | } 54 | 55 | private static List readFrontMatter(List markdownFile) { 56 | List frontMatter = new ArrayList<>(); 57 | boolean frontMatterStarted = false; 58 | for (String line : markdownFile) { 59 | if (line.trim().equals(FRONT_MATTER_SEPARATOR)) { 60 | if (frontMatterStarted) 61 | return frontMatter; 62 | else 63 | frontMatterStarted = true; 64 | } else if (frontMatterStarted) 65 | frontMatter.add(line); 66 | } 67 | return frontMatter; 68 | } 69 | 70 | private static Map.Entry keyValuePairFrom(String line) { 71 | String[] pair = line.split(":", 2); 72 | if (pair.length < 2) 73 | throw new IllegalArgumentException("Line doesn't seem to be a key/value pair (no colon): " + line); 74 | String key = pair[0].trim(); 75 | if (key.isEmpty()) 76 | throw new IllegalArgumentException("Line \"" + line + "\" has no key."); 77 | 78 | String value = pair[1].trim(); 79 | return new AbstractMap.SimpleImmutableEntry<>(key, value); 80 | } 81 | 82 | private static List extractContent(List markdownFile) { 83 | List content = new ArrayList<>(); 84 | boolean frontMatterStarted = false; 85 | boolean contentStarted = false; 86 | for (String line : markdownFile) { 87 | if (line.trim().equals(FRONT_MATTER_SEPARATOR)) { 88 | if (frontMatterStarted) 89 | contentStarted = true; 90 | else 91 | frontMatterStarted = true; 92 | } else if (contentStarted) 93 | content.add(line); 94 | } 95 | return content; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/factories/RawFrontMatter.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post.factories; 2 | 3 | import java.util.Map; 4 | import java.util.Optional; 5 | 6 | class RawFrontMatter { 7 | 8 | private final Map lines; 9 | 10 | RawFrontMatter(Map lines) { 11 | this.lines = lines; 12 | } 13 | 14 | public Optional valueOf(String key) { 15 | return Optional.ofNullable(lines.get(key)); 16 | } 17 | 18 | public String requiredValueOf(String key) { 19 | return valueOf(key).orElseThrow( 20 | () -> new IllegalArgumentException("Required key '" + key + "' not present in front matter.")); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/factories/RawPost.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post.factories; 2 | 3 | import org.codefx.java_after_eight.post.Content; 4 | 5 | class RawPost { 6 | 7 | private final RawFrontMatter frontMatter; 8 | private final Content content; 9 | 10 | RawPost(RawFrontMatter frontMatter, Content content) { 11 | this.frontMatter = frontMatter; 12 | this.content = content; 13 | } 14 | 15 | public RawFrontMatter frontMatter() { 16 | return frontMatter; 17 | } 18 | 19 | public Content content() { 20 | return content; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/factories/TalkFactory.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post.factories; 2 | 3 | import org.codefx.java_after_eight.post.Description; 4 | import org.codefx.java_after_eight.post.Slug; 5 | import org.codefx.java_after_eight.post.Tag; 6 | import org.codefx.java_after_eight.post.Talk; 7 | import org.codefx.java_after_eight.post.Title; 8 | import org.codefx.java_after_eight.post.VideoSlug; 9 | 10 | import java.net.URI; 11 | import java.net.URISyntaxException; 12 | import java.nio.file.Path; 13 | import java.time.LocalDate; 14 | 15 | import static org.codefx.java_after_eight.post.factories.PostFactory.DATE; 16 | import static org.codefx.java_after_eight.post.factories.PostFactory.DESCRIPTION; 17 | import static org.codefx.java_after_eight.post.factories.PostFactory.SLIDES; 18 | import static org.codefx.java_after_eight.post.factories.PostFactory.SLUG; 19 | import static org.codefx.java_after_eight.post.factories.PostFactory.TAGS; 20 | import static org.codefx.java_after_eight.post.factories.PostFactory.TITLE; 21 | import static org.codefx.java_after_eight.post.factories.PostFactory.VIDEO; 22 | 23 | public final class TalkFactory { 24 | 25 | private TalkFactory() { 26 | // private constructor to prevent accidental instantiation of utility class 27 | } 28 | 29 | public static Talk createTalk(Path file) { 30 | try { 31 | RawPost post = PostFactory.readPost(file); 32 | return createTalk(post); 33 | } catch (RuntimeException ex) { 34 | throw new RuntimeException("Creating talk failed: " + file, ex); 35 | } 36 | } 37 | 38 | private static Talk createTalk(RawPost post) { 39 | RawFrontMatter frontMatter = post.frontMatter(); 40 | try { 41 | return new Talk( 42 | new Title(frontMatter.requiredValueOf(TITLE)), 43 | Tag.from(frontMatter.requiredValueOf(TAGS)), 44 | LocalDate.parse(frontMatter.requiredValueOf(DATE)), 45 | new Description(frontMatter.requiredValueOf(DESCRIPTION)), 46 | new Slug(frontMatter.requiredValueOf(SLUG)), 47 | new URI(frontMatter.requiredValueOf(SLIDES)), 48 | frontMatter.valueOf(VIDEO).map(VideoSlug::new)); 49 | } catch (URISyntaxException ex) { 50 | throw new IllegalArgumentException(ex); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/post/factories/VideoFactory.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post.factories; 2 | 3 | import org.codefx.java_after_eight.post.Description; 4 | import org.codefx.java_after_eight.post.Repository; 5 | import org.codefx.java_after_eight.post.Slug; 6 | import org.codefx.java_after_eight.post.Tag; 7 | import org.codefx.java_after_eight.post.Title; 8 | import org.codefx.java_after_eight.post.Video; 9 | import org.codefx.java_after_eight.post.VideoSlug; 10 | 11 | import java.nio.file.Path; 12 | import java.time.LocalDate; 13 | 14 | import static org.codefx.java_after_eight.post.factories.PostFactory.DATE; 15 | import static org.codefx.java_after_eight.post.factories.PostFactory.DESCRIPTION; 16 | import static org.codefx.java_after_eight.post.factories.PostFactory.REPOSITORY; 17 | import static org.codefx.java_after_eight.post.factories.PostFactory.SLUG; 18 | import static org.codefx.java_after_eight.post.factories.PostFactory.TAGS; 19 | import static org.codefx.java_after_eight.post.factories.PostFactory.TITLE; 20 | import static org.codefx.java_after_eight.post.factories.PostFactory.VIDEO; 21 | 22 | public final class VideoFactory { 23 | 24 | private VideoFactory() { 25 | // private constructor to prevent accidental instantiation of utility class 26 | } 27 | 28 | public static Video createVideo(Path file) { 29 | try { 30 | RawPost post = PostFactory.readPost(file); 31 | return createVideo(post); 32 | } catch (RuntimeException ex) { 33 | throw new RuntimeException("Creating video failed: " + file, ex); 34 | } 35 | } 36 | 37 | private static Video createVideo(RawPost post) { 38 | RawFrontMatter frontMatter = post.frontMatter(); 39 | return new Video( 40 | new Title(frontMatter.requiredValueOf(TITLE)), 41 | Tag.from(frontMatter.requiredValueOf(TAGS)), 42 | LocalDate.parse(frontMatter.requiredValueOf(DATE)), 43 | new Description(frontMatter.requiredValueOf(DESCRIPTION)), 44 | new Slug(frontMatter.requiredValueOf(SLUG)), 45 | new VideoSlug(frontMatter.requiredValueOf(VIDEO)), 46 | frontMatter.valueOf(REPOSITORY).map(Repository::new)); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/recommendation/Recommendation.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.recommendation; 2 | 3 | import org.codefx.java_after_eight.post.Post; 4 | import org.codefx.java_after_eight.post.Slug; 5 | 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.stream.Stream; 9 | 10 | import static java.util.Objects.requireNonNull; 11 | import static java.util.stream.Collectors.joining; 12 | import static java.util.stream.Collectors.toList; 13 | 14 | public class Recommendation { 15 | 16 | private final Post post; 17 | private final List recommendedPosts; 18 | 19 | Recommendation(Post post, List recommendedPosts) { 20 | this.post = requireNonNull(post); 21 | this.recommendedPosts = requireNonNull(recommendedPosts); 22 | } 23 | 24 | static Recommendation from(Post post, Stream sortedRecommendations, int perPost) { 25 | List recommendations = sortedRecommendations.limit(perPost).collect(toList()); 26 | return new Recommendation(requireNonNull(post), recommendations); 27 | } 28 | 29 | public Post post() { 30 | return post; 31 | } 32 | 33 | public Stream recommendedPosts() { 34 | return recommendedPosts.stream(); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object o) { 39 | if (this == o) 40 | return true; 41 | if (o == null || getClass() != o.getClass()) 42 | return false; 43 | Recommendation that = (Recommendation) o; 44 | return post.equals(that.post) && 45 | recommendedPosts.equals(that.recommendedPosts); 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hash(post); 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "Recommendation{" + 56 | "post=" + post.slug().value() + 57 | ", recommendedPosts=" + recommendedPosts.stream() 58 | .map(Post::slug) 59 | .map(Slug::value) 60 | .collect(joining(", ")) + 61 | '}'; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /genealogy/src/main/java/org/codefx/java_after_eight/recommendation/Recommender.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.recommendation; 2 | 3 | import org.codefx.java_after_eight.genealogy.Relation; 4 | import org.codefx.java_after_eight.post.Post; 5 | 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.stream.Stream; 10 | 11 | import static java.util.Comparator.comparing; 12 | import static java.util.stream.Collectors.groupingBy; 13 | 14 | // Don't judge me for the name - recommend a better one (see what I did there?) 15 | public class Recommender { 16 | 17 | public Stream recommend(Stream relations, int perPost) { 18 | if (perPost < 1) 19 | throw new IllegalArgumentException( 20 | "Number of recommendations per post must be greater zero: " + perPost); 21 | 22 | Comparator byPostThenByDecreasingScore = 23 | comparing((Relation relation) -> relation.post1().slug()) 24 | .thenComparing(Relation::score) 25 | .reversed(); 26 | Map> byPost = relations 27 | .sorted(byPostThenByDecreasingScore) 28 | .collect(groupingBy(Relation::post1)); 29 | return byPost 30 | .entrySet().stream() 31 | .map(postWithRelations -> Recommendation.from( 32 | postWithRelations.getKey(), 33 | postWithRelations.getValue().stream().map(Relation::post2), 34 | perPost)); 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/TextParserTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | public class TextParserTests { 8 | 9 | public interface QuotationTests { 10 | 11 | String parseCreateExtract(String text); 12 | 13 | @Test 14 | default void createFromStringWithoutQuotationMarks_noChange() { 15 | String text = "A cool blog post"; 16 | String expected = text; 17 | 18 | String actual = parseCreateExtract(text); 19 | 20 | assertThat(actual).isEqualTo(expected); 21 | } 22 | 23 | @Test 24 | default void createFromStringWithQuotationMarks_quotationMarksRemoved() { 25 | String text = "\"A cool blog post\""; 26 | String expected = "A cool blog post"; 27 | 28 | String actual = parseCreateExtract(text); 29 | 30 | assertThat(actual).isEqualTo(expected); 31 | } 32 | 33 | @Test 34 | default void createFromStringWithInnerQuotationMarks_onlyOuterQuotationMarksRemoved() { 35 | String text = "\"\"A cool blog post\" he said\""; 36 | String expected = "\"A cool blog post\" he said"; 37 | 38 | String actual = parseCreateExtract(text); 39 | 40 | assertThat(actual).isEqualTo(expected); 41 | } 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/UtilsTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight; 2 | 3 | import org.junit.jupiter.api.Nested; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Optional; 7 | import java.util.stream.Stream; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 11 | import static org.codefx.java_after_eight.Utils.collectEqualElement; 12 | 13 | class UtilsTests { 14 | 15 | @Nested 16 | class QuotationTests implements TextParserTests.QuotationTests { 17 | 18 | @Override 19 | public String parseCreateExtract(String text) { 20 | return Utils.removeOuterQuotationMarks(text); 21 | } 22 | 23 | } 24 | 25 | @Nested 26 | class CollectEqualElement { 27 | 28 | @Test 29 | void emptyStream_emptyOptional() { 30 | Optional element = Stream 31 | .of() 32 | .collect(collectEqualElement()); 33 | 34 | assertThat(element).isEmpty(); 35 | } 36 | 37 | @Test 38 | void singleElementStream_optionalWithThatElement() { 39 | Optional element = Stream 40 | .of("element") 41 | .collect(collectEqualElement()); 42 | 43 | assertThat(element).contains("element"); 44 | } 45 | 46 | @Test 47 | void equalElementStream_optionalWithThatElement() { 48 | Optional element = Stream 49 | .of("element", "element", "element") 50 | .collect(collectEqualElement()); 51 | 52 | assertThat(element).contains("element"); 53 | } 54 | 55 | @Test 56 | void nonEqualElementStream_throwsException() { 57 | Stream stream = Stream.of("element", "other element"); 58 | 59 | assertThatThrownBy(() -> stream.collect(collectEqualElement())) 60 | .isInstanceOf(IllegalArgumentException.class); 61 | } 62 | 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/genealogy/GenealogyTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogy; 2 | 3 | import org.codefx.java_after_eight.post.Post; 4 | import org.codefx.java_after_eight.post.PostTestHelper; 5 | import org.codefx.java_after_eight.genealogist.Genealogist; 6 | import org.codefx.java_after_eight.genealogist.RelationType; 7 | import org.codefx.java_after_eight.genealogist.TypedRelation; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.util.Arrays; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.stream.Stream; 14 | 15 | import static java.lang.Math.round; 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | 18 | class GenealogyTests { 19 | 20 | private static final int TAG_SCORE_A_B = 80; 21 | private static final int TAG_SCORE_A_C = 60; 22 | private static final int TAG_SCORE_B_A = 70; 23 | private static final int TAG_SCORE_B_C = 50; 24 | private static final int TAG_SCORE_C_A = 50; 25 | private static final int TAG_SCORE_C_B = 40; 26 | 27 | private static final int LINK_SCORE_A_B = 60; 28 | private static final int LINK_SCORE_A_C = 40; 29 | private static final int LINK_SCORE_B_A = 50; 30 | private static final int LINK_SCORE_B_C = 30; 31 | private static final int LINK_SCORE_C_A = 30; 32 | private static final int LINK_SCORE_C_B = 20; 33 | 34 | private static final double TAG_WEIGHT = 1.0; 35 | private static final double LINK_WEIGHT = 0.75; 36 | 37 | private final Post postA = PostTestHelper.createWithSlug("a"); 38 | private final Post postB = PostTestHelper.createWithSlug("b"); 39 | private final Post postC = PostTestHelper.createWithSlug("c"); 40 | 41 | private final RelationType tagRelation = new RelationType("tag"); 42 | private final RelationType linkRelation = new RelationType("link"); 43 | 44 | private final Genealogist tagGenealogist = (Post1, Post2) -> 45 | new TypedRelation(Post1, Post2, tagRelation, tagScore(Post1, Post2)); 46 | private final Genealogist linkGenealogist = (Post1, Post2) -> 47 | new TypedRelation(Post1, Post2, linkRelation, linkScore(Post1, Post2)); 48 | 49 | private final Weights weights; 50 | 51 | GenealogyTests() { 52 | Map weights = new HashMap<>(); 53 | weights.put(tagRelation, TAG_WEIGHT); 54 | weights.put(linkRelation, LINK_WEIGHT); 55 | this.weights = new Weights(weights, 0.5); 56 | } 57 | 58 | private int tagScore(Post post1, Post post2) { 59 | if (post1 == post2) 60 | return 100; 61 | if (post1 == postA && post2 == postB) 62 | return TAG_SCORE_A_B; 63 | if (post1 == postA && post2 == postC) 64 | return TAG_SCORE_A_C; 65 | if (post1 == postB && post2 == postA) 66 | return TAG_SCORE_B_A; 67 | if (post1 == postB && post2 == postC) 68 | return TAG_SCORE_B_C; 69 | if (post1 == postC && post2 == postA) 70 | return TAG_SCORE_C_A; 71 | if (post1 == postC && post2 == postB) 72 | return TAG_SCORE_C_B; 73 | return 0; 74 | } 75 | 76 | private int linkScore(Post post1, Post post2) { 77 | if (post1 == post2) 78 | return 100; 79 | if (post1 == postA && post2 == postB) 80 | return LINK_SCORE_A_B; 81 | if (post1 == postA && post2 == postC) 82 | return LINK_SCORE_A_C; 83 | if (post1 == postB && post2 == postA) 84 | return LINK_SCORE_B_A; 85 | if (post1 == postB && post2 == postC) 86 | return LINK_SCORE_B_C; 87 | if (post1 == postC && post2 == postA) 88 | return LINK_SCORE_C_A; 89 | if (post1 == postC && post2 == postB) 90 | return LINK_SCORE_C_B; 91 | return 0; 92 | } 93 | 94 | @Test 95 | void oneGenealogist_twoPosts() { 96 | Genealogy genealogy = new Genealogy( 97 | Arrays.asList(postA, postB), 98 | Arrays.asList(tagGenealogist), 99 | weights); 100 | 101 | Stream relations = genealogy.inferRelations(); 102 | 103 | assertThat(relations).containsExactlyInAnyOrder( 104 | new Relation(postA, postB, round(TAG_SCORE_A_B * TAG_WEIGHT)), 105 | new Relation(postB, postA, round(TAG_SCORE_B_A * TAG_WEIGHT)) 106 | ); 107 | } 108 | 109 | @Test 110 | void otherGenealogist_twoPosts() { 111 | Genealogy genealogy = new Genealogy( 112 | Arrays.asList(postA, postB), 113 | Arrays.asList(linkGenealogist), 114 | weights); 115 | 116 | Stream relations = genealogy.inferRelations(); 117 | 118 | assertThat(relations).containsExactlyInAnyOrder( 119 | new Relation(postA, postB, round(LINK_SCORE_A_B * LINK_WEIGHT)), 120 | new Relation(postB, postA, round(LINK_SCORE_B_A * LINK_WEIGHT)) 121 | ); 122 | } 123 | 124 | @Test 125 | void oneGenealogist_threePosts() { 126 | Genealogy genealogy = new Genealogy( 127 | Arrays.asList(postA, postB, postC), 128 | Arrays.asList(tagGenealogist), 129 | weights); 130 | 131 | Stream relations = genealogy.inferRelations(); 132 | 133 | assertThat(relations).containsExactlyInAnyOrder( 134 | new Relation(postA, postB, round(TAG_SCORE_A_B * TAG_WEIGHT)), 135 | new Relation(postA, postC, round(TAG_SCORE_A_C * TAG_WEIGHT)), 136 | new Relation(postB, postA, round(TAG_SCORE_B_A * TAG_WEIGHT)), 137 | new Relation(postB, postC, round(TAG_SCORE_B_C * TAG_WEIGHT)), 138 | new Relation(postC, postA, round(TAG_SCORE_C_A * TAG_WEIGHT)), 139 | new Relation(postC, postB, round(TAG_SCORE_C_B * TAG_WEIGHT)) 140 | ); 141 | } 142 | 143 | @Test 144 | void twoGenealogists_threePosts() { 145 | Genealogy genealogy = new Genealogy( 146 | Arrays.asList(postA, postB, postC), 147 | Arrays.asList(tagGenealogist, linkGenealogist), 148 | weights); 149 | 150 | Stream relations = genealogy.inferRelations(); 151 | 152 | assertThat(relations).containsExactlyInAnyOrder( 153 | new Relation(postA, postB, round((TAG_SCORE_A_B * TAG_WEIGHT + LINK_SCORE_A_B * LINK_WEIGHT) / 2)), 154 | new Relation(postA, postC, round((TAG_SCORE_A_C * TAG_WEIGHT + LINK_SCORE_A_C * LINK_WEIGHT) / 2)), 155 | new Relation(postB, postA, round((TAG_SCORE_B_A * TAG_WEIGHT + LINK_SCORE_B_A * LINK_WEIGHT) / 2)), 156 | new Relation(postB, postC, round((TAG_SCORE_B_C * TAG_WEIGHT + LINK_SCORE_B_C * LINK_WEIGHT) / 2)), 157 | new Relation(postC, postA, round((TAG_SCORE_C_A * TAG_WEIGHT + LINK_SCORE_C_A * LINK_WEIGHT) / 2)), 158 | new Relation(postC, postB, round((TAG_SCORE_C_B * TAG_WEIGHT + LINK_SCORE_C_B * LINK_WEIGHT) / 2)) 159 | ); 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/genealogy/RelationTestHelper.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogy; 2 | 3 | import org.codefx.java_after_eight.post.Post; 4 | 5 | public class RelationTestHelper { 6 | 7 | public static Relation create(Post post1, Post post2, long score) { 8 | return new Relation(post1, post2, score); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/genealogy/RelationTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogy; 2 | 3 | import org.codefx.java_after_eight.post.Post; 4 | import org.codefx.java_after_eight.post.PostTestHelper; 5 | import org.codefx.java_after_eight.genealogist.RelationType; 6 | import org.codefx.java_after_eight.genealogist.TypedRelation; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.stream.Stream; 12 | 13 | import static java.lang.Math.round; 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | 16 | class RelationTests { 17 | 18 | private static final double TAG_WEIGHT = 1.0; 19 | private static final double LINK_WEIGHT = 0.25; 20 | 21 | private final Post postA = PostTestHelper.createWithSlug("a"); 22 | private final Post postB = PostTestHelper.createWithSlug("b"); 23 | 24 | private final RelationType tagRelation = new RelationType("tag"); 25 | private final RelationType linkRelation = new RelationType("link"); 26 | 27 | private final Weights weights; 28 | 29 | RelationTests() { 30 | Map weights = new HashMap<>(); 31 | weights.put(tagRelation, TAG_WEIGHT); 32 | weights.put(linkRelation, LINK_WEIGHT); 33 | this.weights = new Weights(weights, 0.5); 34 | } 35 | 36 | /* 37 | * TODO: various failure states 38 | */ 39 | 40 | @Test 41 | void singleTypedRelation_weightOne_samePostsAndScore() { 42 | int score = 60; 43 | Stream typedRelations = Stream.of( 44 | new TypedRelation(postA, postB, tagRelation, score) 45 | ); 46 | 47 | Relation relation = Relation.aggregate(typedRelations, weights); 48 | 49 | assertThat(relation.post1()).isEqualTo(postA); 50 | assertThat(relation.post2()).isEqualTo(postB); 51 | assertThat(relation.score()).isEqualTo(score); 52 | } 53 | 54 | @Test 55 | void twoTypedRelation_weightOne_averagedScore() { 56 | Stream typedRelations = Stream.of( 57 | new TypedRelation(postA, postB, tagRelation, 40), 58 | new TypedRelation(postA, postB, tagRelation, 80) 59 | ); 60 | 61 | Relation relation = Relation.aggregate(typedRelations, weights); 62 | 63 | assertThat(relation.score()).isEqualTo((40 + 80) / 2); 64 | } 65 | 66 | @Test 67 | void twoTypedRelation_differingWeight_weightedScore() { 68 | Stream typedRelations = Stream.of( 69 | new TypedRelation(postA, postB, tagRelation, 40), 70 | new TypedRelation(postA, postB, linkRelation, 80) 71 | ); 72 | 73 | Relation relation = Relation.aggregate(typedRelations, weights); 74 | 75 | double expectedScore = (40 * TAG_WEIGHT + 80 * LINK_WEIGHT) / 2; 76 | assertThat(relation.score()).isEqualTo(round(expectedScore)); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/genealogy/WeightsTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.genealogy; 2 | 3 | import org.codefx.java_after_eight.genealogist.RelationType; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 11 | 12 | class WeightsTests { 13 | 14 | public static final RelationType TAG_TYPE = new RelationType("tag"); 15 | public static final RelationType LIST_TYPE = new RelationType("list"); 16 | 17 | @Test 18 | void nullRelationType_throwsException() { 19 | Map weightMap = new HashMap<>(); 20 | weightMap.put(null, 1.0); 21 | assertThatThrownBy(() -> new Weights(weightMap, 0.5)).isInstanceOf(NullPointerException.class); 22 | } 23 | 24 | @Test 25 | void nullWeight_throwsException() { 26 | Map weightMap = new HashMap<>(); 27 | weightMap.put(TAG_TYPE, null); 28 | assertThatThrownBy(() -> new Weights(weightMap, 0.5)).isInstanceOf(NullPointerException.class); 29 | } 30 | 31 | @Test 32 | void knownRelationType_returnsWeight() { 33 | Map weightMap = new HashMap<>(); 34 | weightMap.put(TAG_TYPE, 0.42); 35 | Weights weights = new Weights(weightMap, 0.5); 36 | 37 | assertThat(weights.weightOf(TAG_TYPE)).isEqualTo(0.42); 38 | } 39 | 40 | @Test 41 | void unknownRelationType_returnsDefaultWeight() { 42 | Map weightMap = new HashMap<>(); 43 | weightMap.put(TAG_TYPE, 0.42); 44 | Weights weights = new Weights(weightMap, 0.5); 45 | 46 | assertThat(weights.weightOf(LIST_TYPE)).isEqualTo(0.5); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/post/DescriptionTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import org.codefx.java_after_eight.TextParserTests; 4 | import org.junit.jupiter.api.Nested; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 8 | 9 | class DescriptionTests { 10 | 11 | @Test 12 | void emptyText_exception() { 13 | assertThatThrownBy(() -> new Description("")).isInstanceOf(IllegalArgumentException.class); 14 | } 15 | 16 | @Nested 17 | class QuotationTests implements TextParserTests.QuotationTests { 18 | 19 | @Override 20 | public String parseCreateExtract(String text) { 21 | return new Description(text).text(); 22 | } 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/post/PostTestHelper.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Optional; 5 | import java.util.stream.Stream; 6 | 7 | public class PostTestHelper { 8 | 9 | public static Post createWithSlug(String slug) { 10 | return new Article( 11 | new Title("Title"), 12 | Tag.from("[Tag]"), 13 | LocalDate.now(), 14 | new Description("description"), 15 | new Slug(slug), 16 | Optional.empty(), 17 | () -> Stream.of("")); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/post/SlugTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 6 | 7 | class SlugTests { 8 | 9 | @Test 10 | void emptyText_exception() { 11 | assertThatThrownBy(() -> new Slug("")).isInstanceOf(IllegalArgumentException.class); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/post/TagTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import org.assertj.core.api.Assertions; 4 | import org.codefx.java_after_eight.post.Tag; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Set; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | class TagTests { 12 | 13 | @Test 14 | void emptyElementArray_emptyTag() { 15 | String tagsText = "[ ]"; 16 | String[] expectedTags = { }; 17 | 18 | Set tags = Tag.from(tagsText); 19 | 20 | Assertions.assertThat(tags) 21 | .extracting(Tag::text) 22 | .containsExactlyInAnyOrder(expectedTags); 23 | } 24 | 25 | @Test 26 | void singleElementArray_singleTag() { 27 | String tagsText = "[$TAG]"; 28 | String[] expectedTags = { "$TAG" }; 29 | 30 | Set tags = Tag.from(tagsText); 31 | 32 | Assertions.assertThat(tags) 33 | .extracting(Tag::text) 34 | .containsExactlyInAnyOrder(expectedTags); 35 | } 36 | 37 | @Test 38 | void multipleElementsArray_multipleTags() { 39 | String tagsText = "[$TAG,$TOG,$TUG]"; 40 | String[] expectedTags = { "$TAG", "$TOG", "$TUG" }; 41 | 42 | Set tags = Tag.from(tagsText); 43 | 44 | Assertions.assertThat(tags) 45 | .extracting(Tag::text) 46 | .containsExactlyInAnyOrder(expectedTags); 47 | } 48 | 49 | @Test 50 | void multipleElementsArrayWithSpaces_multipleTagsWithoutSpaces() { 51 | String tagsText = "[$TAG , $TOG , $TUG ]"; 52 | String[] expectedTags = { "$TAG", "$TOG", "$TUG" }; 53 | 54 | Set tags = Tag.from(tagsText); 55 | 56 | Assertions.assertThat(tags) 57 | .extracting(Tag::text) 58 | .containsExactlyInAnyOrder(expectedTags); 59 | } 60 | 61 | @Test 62 | void multipleElementsArrayWithJustSpacesTag_emptyTagIsIgnored() { 63 | String tagsText = "[$TAG , , $TUG ]"; 64 | String[] expectedTags = { "$TAG", "$TUG" }; 65 | 66 | Set tags = Tag.from(tagsText); 67 | 68 | Assertions.assertThat(tags) 69 | .extracting(Tag::text) 70 | .containsExactlyInAnyOrder(expectedTags); 71 | } 72 | 73 | @Test 74 | void multipleElementsArrayWithEmptyTag_emptyTagIsIgnored() { 75 | String tagsText = "[$TAG ,, $TUG ]"; 76 | String[] expectedTags = { "$TAG", "$TUG" }; 77 | 78 | Set tags = Tag.from(tagsText); 79 | 80 | Assertions.assertThat(tags) 81 | .extracting(Tag::text) 82 | .containsExactlyInAnyOrder(expectedTags); 83 | } 84 | 85 | @Test 86 | void multipleElementsArrayDuplicateTags_duplicateTagIsIgnored() { 87 | String tagsText = "[$TAG, $TAG]"; 88 | String[] expectedTags = { "$TAG" }; 89 | 90 | Set tags = Tag.from(tagsText); 91 | 92 | Assertions.assertThat(tags) 93 | .extracting(Tag::text) 94 | .containsExactlyInAnyOrder(expectedTags); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/post/TitleTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post; 2 | 3 | import org.codefx.java_after_eight.TextParserTests; 4 | import org.junit.jupiter.api.Nested; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 8 | 9 | class TitleTests { 10 | 11 | @Test 12 | void emptyText_exception() { 13 | assertThatThrownBy(() -> new Title("")).isInstanceOf(IllegalArgumentException.class); 14 | } 15 | 16 | 17 | @Nested 18 | class QuotationTests implements TextParserTests.QuotationTests { 19 | 20 | @Override 21 | public String parseCreateExtract(String text) { 22 | return new Title(text).text(); 23 | } 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/post/factories/ArticleFactoryTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.post.factories; 2 | 3 | import org.codefx.java_after_eight.post.Article; 4 | import org.codefx.java_after_eight.post.Post; 5 | import org.codefx.java_after_eight.post.Tag; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.time.LocalDate; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | class ArticleFactoryTests { 15 | 16 | @Test 17 | void createFromFrontMatter_multipleColons_getValidArticle() { 18 | List file = Arrays.asList( 19 | "---", 20 | "title: Cool: A blog post", 21 | "tags: [$TAG, $TOG]", 22 | "date: 2020-01-23", 23 | "description: \"Very blog, much post, so wow\"", 24 | "slug: cool-blog-post", 25 | "---", 26 | "" 27 | ); 28 | 29 | Post post = ArticleFactory.createArticle(file); 30 | 31 | assertThat(post.title().text()).isEqualTo("Cool: A blog post"); 32 | assertThat(post.tags()).extracting(Tag::text).containsExactlyInAnyOrder("$TAG", "$TOG"); 33 | assertThat(post.date()).isEqualTo(LocalDate.of(2020, 1, 23)); 34 | assertThat(post.description().text()).isEqualTo("Very blog, much post, so wow"); 35 | assertThat(post.slug().value()).isEqualTo("cool-blog-post"); 36 | } 37 | 38 | @Test 39 | void createFromFrontMatter_allTagsCorrect_getValidArticle() { 40 | List file = Arrays.asList( 41 | "---", 42 | "title: A cool blog post", 43 | "tags: [$TAG, $TOG]", 44 | "date: 2020-01-23", 45 | "description: \"Very blog, much post, so wow\"", 46 | "slug: cool-blog-post", 47 | "---", 48 | "" 49 | ); 50 | 51 | Post article = ArticleFactory.createArticle(file); 52 | 53 | assertThat(article.title().text()).isEqualTo("A cool blog post"); 54 | assertThat(article.tags()).extracting(Tag::text).containsExactlyInAnyOrder("$TAG", "$TOG"); 55 | assertThat(article.date()).isEqualTo(LocalDate.of(2020, 1, 23)); 56 | assertThat(article.description().text()).isEqualTo("Very blog, much post, so wow"); 57 | assertThat(article.slug().value()).isEqualTo("cool-blog-post"); 58 | } 59 | 60 | @Test 61 | void createFromFile_allTagsCorrect_getValidArticle() { 62 | List file = Arrays.asList( 63 | "---", 64 | "title: A cool blog post", 65 | "tags: [$TAG, $TOG]", 66 | "date: 2020-01-23", 67 | "description: \"Very blog, much post, so wow\"", 68 | "slug: cool-blog-post", 69 | "---", 70 | "", 71 | "Lorem ipsum dolor sit amet.", 72 | "Ut enim ad minim veniam.", 73 | "Duis aute irure dolor in reprehenderit.", 74 | "Excepteur sint occaecat cupidatat non proident."); 75 | 76 | Article article = ArticleFactory.createArticle(file); 77 | 78 | assertThat(article.title().text()).isEqualTo("A cool blog post"); 79 | assertThat(article.tags()).extracting(Tag::text).containsExactlyInAnyOrder("$TAG", "$TOG"); 80 | assertThat(article.date()).isEqualTo(LocalDate.of(2020, 1, 23)); 81 | assertThat(article.description().text()).isEqualTo("Very blog, much post, so wow"); 82 | assertThat(article.slug().value()).isEqualTo("cool-blog-post"); 83 | assertThat(article.content().get()).containsExactly( 84 | "", 85 | "Lorem ipsum dolor sit amet.", 86 | "Ut enim ad minim veniam.", 87 | "Duis aute irure dolor in reprehenderit.", 88 | "Excepteur sint occaecat cupidatat non proident."); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /genealogy/src/test/java/org/codefx/java_after_eight/recommendation/RecommenderTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.java_after_eight.recommendation; 2 | 3 | import org.codefx.java_after_eight.post.Post; 4 | import org.codefx.java_after_eight.post.PostTestHelper; 5 | import org.codefx.java_after_eight.genealogy.Relation; 6 | import org.codefx.java_after_eight.genealogy.RelationTestHelper; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.util.Arrays; 10 | import java.util.stream.Stream; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | class RecommenderTests { 15 | 16 | private final Post postA = PostTestHelper.createWithSlug("a"); 17 | private final Post postB = PostTestHelper.createWithSlug("b"); 18 | private final Post postC = PostTestHelper.createWithSlug("c"); 19 | 20 | private final Relation relation_AB = RelationTestHelper.create(postA, postB, 60L); 21 | private final Relation relation_AC = RelationTestHelper.create(postA, postC, 40L); 22 | private final Relation relation_BA = RelationTestHelper.create(postB, postA, 50L); 23 | private final Relation relation_BC = RelationTestHelper.create(postB, postC, 70L); 24 | private final Relation relation_CA = RelationTestHelper.create(postC, postA, 80L); 25 | private final Relation relation_CB = RelationTestHelper.create(postC, postB, 60L); 26 | 27 | private final Recommender recommender = new Recommender(); 28 | 29 | @Test 30 | void forOnePost_oneRelation() { 31 | Stream recommendations = recommender.recommend( 32 | Stream.of(relation_AC), 33 | 1); 34 | 35 | assertThat(recommendations).containsExactlyInAnyOrder( 36 | new Recommendation(postA, Arrays.asList(postC))); 37 | } 38 | 39 | @Test 40 | void forOnePost_twoRelations() { 41 | Stream recommendations = recommender.recommend( 42 | Stream.of(relation_AB, relation_AC), 43 | 1); 44 | 45 | assertThat(recommendations).containsExactlyInAnyOrder( 46 | new Recommendation(postA, Arrays.asList(postB))); 47 | } 48 | 49 | @Test 50 | void forManyPosts_oneRelationEach() { 51 | Stream recommendations = recommender.recommend( 52 | Stream.of(relation_AC, relation_BC, relation_CB), 53 | 1); 54 | 55 | assertThat(recommendations).containsExactlyInAnyOrder( 56 | new Recommendation(postA, Arrays.asList(postC)), 57 | new Recommendation(postB, Arrays.asList(postC)), 58 | new Recommendation(postC, Arrays.asList(postB)) 59 | ); 60 | } 61 | 62 | @Test 63 | void forManyPosts_twoRelationsEach() { 64 | Stream recommendations = recommender.recommend( 65 | Stream.of(relation_AB, relation_AC, relation_BA, relation_BC, relation_CA, relation_CB), 66 | 1); 67 | 68 | assertThat(recommendations).containsExactlyInAnyOrder( 69 | new Recommendation(postA, Arrays.asList(postB)), 70 | new Recommendation(postB, Arrays.asList(postC)), 71 | new Recommendation(postC, Arrays.asList(postA)) 72 | ); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | pom 8 | 9 | genealogy 10 | genealogists 11 | 12 | 13 | 14 | UTF-8 15 | 8 16 | 8 17 | 5.7.0 18 | 3.7.7 19 | 20 | 21 | org.codefx.java-after-eight 22 | parent 23 | 1.0-SNAPSHOT 24 | 25 | 26 | 27 | 28 | 29 | maven-compiler-plugin 30 | 3.8.1 31 | 32 | 33 | maven-surefire-plugin 34 | 3.0.0-M5 35 | 36 | 37 | maven-jar-plugin 38 | 3.2.0 39 | 40 | 41 | maven-javadoc-plugin 42 | 3.4.1 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.junit.jupiter 53 | junit-jupiter 54 | ${junit-jupiter-version} 55 | test 56 | 57 | 58 | org.junit.jupiter 59 | junit-jupiter-params 60 | ${junit-jupiter-version} 61 | test 62 | 63 | 64 | org.mockito 65 | mockito-core 66 | ${mockito-version} 67 | test 68 | 69 | 70 | org.mockito 71 | mockito-junit-jupiter 72 | ${mockito-version} 73 | test 74 | 75 | 76 | org.assertj 77 | assertj-core 78 | 3.14.0 79 | test 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /recommendations.config: -------------------------------------------------------------------------------- 1 | content/articles/ 2 | content/talks/ 3 | content/videos/ 4 | recommendations.json -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | java -cp jars/genealogy.jar:jars/genealogists.jar org.codefx.java_after_eight.Main 2 | -------------------------------------------------------------------------------- /stats-code.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | find . -type f -name '*.java' -exec cat {} \; | sed '/^\s*#/d;/^\s*$/d;/^\s*\/\//d' | wc > /tmp/wc.$$ $@ 5 | awk '{ printf "Code stats: %s lines, %s words, %s characters\n", $1, $2, $3 }' /tmp/wc.$$ 6 | -------------------------------------------------------------------------------- /stats-time.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "running..." 5 | 6 | rm -f /tmp/mtime.$$ 7 | for x in {1..10} 8 | do 9 | /usr/bin/time -f "real %e user %U sys %S" -a -o /tmp/mtime.$$ $@ 10 | # tail -1 /tmp/mtime.$$ 11 | done 12 | 13 | echo "" 14 | awk '{ et += $2; ut += $4; st += $6; count++ } END { printf "Average runtime (user time): %.3fs\n", ut/count }' /tmp/mtime.$$ 15 | -------------------------------------------------------------------------------- /stats.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | java -version 5 | echo "" 6 | 7 | ./build.sh 8 | ./stats-time.sh ./run.sh 9 | ./stats-code.sh 10 | --------------------------------------------------------------------------------