├── .mise.toml
├── slides
├── images
│ ├── jta.png
│ ├── llm.png
│ ├── anatomy.png
│ ├── avatar.png
│ ├── catalog.png
│ ├── github.png
│ ├── jta_1.png
│ ├── regexp.png
│ ├── title.webp
│ ├── writing.png
│ ├── concepts.jpg
│ ├── feedback.png
│ ├── running.avif
│ ├── click_droit.png
│ ├── conclusion.jpg
│ ├── declarative.jpg
│ ├── easter-egg.png
│ ├── magic
│ │ ├── nim.webp
│ │ ├── feedback.png
│ │ ├── jafar_3.webp
│ │ ├── kirikou.png
│ │ ├── mickey.webp
│ │ ├── jackjack.webp
│ │ └── malefincent.webp
│ ├── upside_down.png
│ ├── logo_onepoint.png
│ ├── qrcode_github.png
│ ├── sound
│ │ └── punition.mp3
│ ├── chercher_remplacer.png
│ ├── permis_de_refactoring.png
│ ├── openrewrite.svg
│ └── under_logo.svg
├── dev.sh
├── 05-coding-recipes.adoc
├── 04-catalogue.adoc
├── index.adoc
├── outro.adoc
├── favicon.svg
├── 02-running-recipes.adoc
├── intro.adoc
├── 01-openrewrite.adoc
├── 03-declarative-recipes.adoc
├── css
│ └── meetup.css
└── index-docinfo-footer.html
├── toxic-library
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── github
│ │ └── jtama
│ │ └── toxic
│ │ ├── LearnToFly.java
│ │ ├── BigDecimalUtils.java
│ │ ├── Timer.java
│ │ └── FooBarUtils.java
└── pom.xml
├── toxic-library-remover
├── src
│ ├── test
│ │ └── java
│ │ │ ├── .editorconfig
│ │ │ └── io
│ │ │ └── github
│ │ │ └── jtama
│ │ │ └── openrewrite
│ │ │ ├── RemoveFooBarUtilsStringFormattedTest.java
│ │ │ ├── RemoveLogStartInvocationTest.java
│ │ │ ├── RemoveTryCatchConnectionTest.java
│ │ │ ├── RemoveFooBarIsEmptyTest.java
│ │ │ ├── RemoveFooBarCompareTest.java
│ │ │ └── ExtractInterfaceTest.java
│ └── main
│ │ ├── java
│ │ └── io
│ │ │ └── github
│ │ │ └── jtama
│ │ │ └── openrewrite
│ │ │ ├── model
│ │ │ ├── Node.java
│ │ │ └── Link.java
│ │ │ ├── RemoveFooBarUtilsIsEmpty.java
│ │ │ ├── RemoveFooBarUtilsStringFormatted.java
│ │ │ ├── RemoveLogStartInvocations.java
│ │ │ ├── RemoveTryCatchConnection.java
│ │ │ ├── UseObjectsCompare.java
│ │ │ └── ExtractInterface.java
│ │ └── resources
│ │ ├── logback.xml
│ │ └── META-INF
│ │ └── rewrite
│ │ └── rewrite.yml
├── rewrite.yml
├── licenseHeader.txt
└── pom.xml
├── .gitignore
├── toxic-library-user
├── src
│ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── github
│ │ │ └── jtama
│ │ │ └── app
│ │ │ ├── exception
│ │ │ ├── InvalidNameException.java
│ │ │ ├── UnavailableException.java
│ │ │ ├── UnknownEntityException.java
│ │ │ ├── DuplicateEntityException.java
│ │ │ ├── InvalidBookingException.java
│ │ │ └── OnerentExceptionHandler.java
│ │ │ ├── util
│ │ │ └── MonthValidator.java
│ │ │ ├── rocket
│ │ │ ├── RocketType.java
│ │ │ ├── Rocket.java
│ │ │ ├── RocketController.java
│ │ │ └── RocketReservationService.java
│ │ │ ├── security
│ │ │ ├── HeaderAuthenticationRequest.java
│ │ │ ├── NaiveSecurityProvider.java
│ │ │ └── NaiveAuth.java
│ │ │ ├── hostels
│ │ │ ├── HostelReservationService.java
│ │ │ ├── HostelController.java
│ │ │ └── Hostel.java
│ │ │ └── reservation
│ │ │ └── Reservation.java
│ └── test
│ │ └── java
│ │ └── com
│ │ └── github
│ │ └── jtama
│ │ └── app
│ │ ├── hostels
│ │ └── HostelReservationServiceTest.java
│ │ └── rocket
│ │ └── RocketReservationServiceTest.java
├── README.adoc
└── pom.xml
├── setup.sh
├── .github
└── workflows
│ └── deploy.yaml
├── README.adoc
└── LICENSE
/.mise.toml:
--------------------------------------------------------------------------------
1 | [tools]
2 | java = "temurin-25.0.0+36.0.LTS"
3 |
--------------------------------------------------------------------------------
/slides/images/jta.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/jta.png
--------------------------------------------------------------------------------
/slides/images/llm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/llm.png
--------------------------------------------------------------------------------
/slides/images/anatomy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/anatomy.png
--------------------------------------------------------------------------------
/slides/images/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/avatar.png
--------------------------------------------------------------------------------
/slides/images/catalog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/catalog.png
--------------------------------------------------------------------------------
/slides/images/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/github.png
--------------------------------------------------------------------------------
/slides/images/jta_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/jta_1.png
--------------------------------------------------------------------------------
/slides/images/regexp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/regexp.png
--------------------------------------------------------------------------------
/slides/images/title.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/title.webp
--------------------------------------------------------------------------------
/slides/images/writing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/writing.png
--------------------------------------------------------------------------------
/slides/images/concepts.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/concepts.jpg
--------------------------------------------------------------------------------
/slides/images/feedback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/feedback.png
--------------------------------------------------------------------------------
/slides/images/running.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/running.avif
--------------------------------------------------------------------------------
/slides/images/click_droit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/click_droit.png
--------------------------------------------------------------------------------
/slides/images/conclusion.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/conclusion.jpg
--------------------------------------------------------------------------------
/slides/images/declarative.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/declarative.jpg
--------------------------------------------------------------------------------
/slides/images/easter-egg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/easter-egg.png
--------------------------------------------------------------------------------
/slides/images/magic/nim.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/magic/nim.webp
--------------------------------------------------------------------------------
/slides/images/upside_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/upside_down.png
--------------------------------------------------------------------------------
/slides/images/logo_onepoint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/logo_onepoint.png
--------------------------------------------------------------------------------
/slides/images/magic/feedback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/magic/feedback.png
--------------------------------------------------------------------------------
/slides/images/magic/jafar_3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/magic/jafar_3.webp
--------------------------------------------------------------------------------
/slides/images/magic/kirikou.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/magic/kirikou.png
--------------------------------------------------------------------------------
/slides/images/magic/mickey.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/magic/mickey.webp
--------------------------------------------------------------------------------
/slides/images/qrcode_github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/qrcode_github.png
--------------------------------------------------------------------------------
/slides/images/sound/punition.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/sound/punition.mp3
--------------------------------------------------------------------------------
/slides/images/magic/jackjack.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/magic/jackjack.webp
--------------------------------------------------------------------------------
/slides/images/chercher_remplacer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/chercher_remplacer.png
--------------------------------------------------------------------------------
/slides/images/magic/malefincent.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/magic/malefincent.webp
--------------------------------------------------------------------------------
/toxic-library/src/main/java/com/github/jtama/toxic/LearnToFly.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.toxic;
2 |
3 | public @interface LearnToFly {
4 | }
5 |
--------------------------------------------------------------------------------
/slides/images/permis_de_refactoring.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtama/openrewrite-refactoring-as-code/HEAD/slides/images/permis_de_refactoring.png
--------------------------------------------------------------------------------
/slides/dev.sh:
--------------------------------------------------------------------------------
1 | fswatch -or . -e *.adoc -I --no-defer | xargs -n1 -I{} podman container run --rm -v $(pwd):/documents -w /documents asciidoctor/docker-asciidoctor:1.80 asciidoctor-revealjs -r asciidoctor-diagram index.adoc
--------------------------------------------------------------------------------
/toxic-library-remover/src/test/java/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | # Limit continuation indent to 2 spaces for Java files, as we heavily use continuations around our text blocks.
4 | [*.java]
5 | indent_size = 4
6 | ij_continuation_indent_size = 2
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea
3 | target
4 | .DS_Store
5 | slides/reveal.js
6 | slides/highlight.js
7 | *.html
8 | .asciidoctor
9 | !index-docinfo-footer.html
10 | openrewrite-sample-recipes/target/
11 | toxic-library/target/
12 | /slides/reveal.js/
13 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/exception/InvalidNameException.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.exception;
2 |
3 | public class InvalidNameException extends RuntimeException {
4 | public InvalidNameException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/exception/UnavailableException.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.exception;
2 |
3 | public class UnavailableException extends RuntimeException {
4 | public UnavailableException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/exception/UnknownEntityException.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.exception;
2 |
3 | public class UnknownEntityException extends RuntimeException {
4 | public UnknownEntityException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/exception/DuplicateEntityException.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.exception;
2 |
3 | public class DuplicateEntityException extends RuntimeException {
4 | public DuplicateEntityException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/exception/InvalidBookingException.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.exception;
2 |
3 | public class InvalidBookingException extends RuntimeException {
4 | public InvalidBookingException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/toxic-library/src/main/java/com/github/jtama/toxic/BigDecimalUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.toxic;
2 |
3 | import java.math.BigDecimal;
4 |
5 | public class BigDecimalUtils {
6 |
7 | public static BigDecimal valueOf(Long value) {
8 | return new BigDecimal(value);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/toxic-library/src/main/java/com/github/jtama/toxic/Timer.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.toxic;
2 |
3 | public class Timer {
4 | public static void logStart() {
5 | System.out.println("Start");
6 | }
7 |
8 | public static void logEnd() {
9 | System.out.println("End");
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/toxic-library-remover/rewrite.yml:
--------------------------------------------------------------------------------
1 | ---
2 | type: specs.openrewrite.org/v1beta/recipe
3 | name: com.yourorg.ApplyRecipeBestPractices
4 | displayName: Apply Recipe best practices
5 | description: Apply best practices to your recipes, through OpenRewrite's best practices recipe & IntelliJ IDEA plugin.
6 | recipeList:
7 | - org.openrewrite.recipes.OpenRewriteBestPractices
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/util/MonthValidator.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.util;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 |
5 | import java.time.LocalDate;
6 |
7 | @ApplicationScoped
8 | public class MonthValidator {
9 | /**
10 | * Will throw {@link java.time.DateTimeException} if the month is invalid
11 | * @param month
12 | */
13 | public void validateMonth(int month){
14 | LocalDate.of(1970, month, 1);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/toxic-library-user/README.adoc:
--------------------------------------------------------------------------------
1 | [source%linenums, console]
2 | ----
3 | mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
4 | -Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-testing-frameworks:RELEASE \
5 | -Drewrite.activeRecipes=org.openrewrite.java.testing.junit5.JUnit4to5Migration
6 | ----
7 |
8 | [source%linenums, console]
9 | ----
10 | mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
11 | -Drewrite.recipeArtifactCoordinates=com.github.jtama:toxic-library-remover:1.0.1-SNAPSHOT \
12 | -Drewrite.activeRecipes=com.github.jtama.openrewrite.RemovesThatToxicDependency
13 | ----
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd slides
3 | git clone https://github.com/hakimel/reveal.js --depth 1 --branch 5.1.0
4 | rm -rf reveal.js/.git
5 | git clone https://github.com/denehyg/reveal.js-menu --depth 1 --branch 2.1.0 reveal.js/plugin/menu
6 | rm -rf reveal.js/plugin/menu/.git
7 | cd reveal.js && npm i && npm run build && cd ../
8 | mkdir highlight.js
9 | curl -sLo highlight.js/highlight.min.js https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.3/highlight.min.js
10 | curl -sLo highlight.js/atom-one-light.min.css https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.3/styles/atom-one-light.min.css
--------------------------------------------------------------------------------
/slides/05-coding-recipes.adoc:
--------------------------------------------------------------------------------
1 | [.no-transition.transparency.blur-background]
2 | == Ecrire sa propre recette
3 |
4 | image::O.png[background, size=cover]
5 | Demo time !
6 |
7 | [%notitle.demo,background-iframe="http://localhost:8443"]
8 | === GO
9 |
10 | [.columns.transparency.blur-background]
11 | === !
12 |
13 | image::O.png[background, size=cover]
14 |
15 | [.column.is-one-third]
16 | --
17 | image::magic/jackjack.webp[]
18 | --
19 |
20 | [.column]
21 | --
22 | - ✅ Connaître les ingrédients
23 | - ✅ Savoir où les acheter
24 | - ✅ Savoir lire le grimoire
25 | - ✅ Savoir jeter un sort
26 | - ✅ Créer vos propres sorts
27 | --
28 |
29 |
--------------------------------------------------------------------------------
/toxic-library-remover/licenseHeader.txt:
--------------------------------------------------------------------------------
1 | Copyright 2024 Jérôme TAMA.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/rocket/RocketType.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.rocket;
2 |
3 | import com.github.jtama.toxic.FooBarUtils;
4 |
5 | public enum RocketType {
6 | EXPLOSIVE("explosive"),
7 | FIRST_CLASS("first class"),
8 | LUXURY("luxury");
9 |
10 | private String luxury;
11 |
12 | RocketType(String luxury) {
13 | this.luxury = luxury;
14 | }
15 |
16 | public String getLuxury() {
17 | return luxury;
18 | }
19 |
20 | public int is(RocketType type) {
21 | return new FooBarUtils().compare(this, type, (o1, o2) -> o1.getLuxury().compareTo(o2.getLuxury()));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/toxic-library/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.github.jtama
8 | toxic-library
9 | 19.666.45-RC18-FINAL
10 |
11 |
12 | 25
13 | 25
14 | UTF-8
15 |
16 |
17 |
--------------------------------------------------------------------------------
/toxic-library/src/main/java/com/github/jtama/toxic/FooBarUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.toxic;
2 |
3 | import java.util.Comparator;
4 | import java.util.List;
5 |
6 | public class FooBarUtils {
7 |
8 | public String stringFormatted(String template, Object... args) {
9 | return String.format(template, args);
10 | }
11 |
12 | public static boolean isEmpty(String value) {
13 | if (value == null) return true;
14 | return value.isEmpty();
15 | }
16 |
17 | public static boolean isEmpty(List value) {
18 | if (value == null) return true;
19 | return value.isEmpty();
20 | }
21 |
22 | public int compare(T o1, T o2, Comparator comparator) {
23 | return comparator.compare(o1, o2);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/security/HeaderAuthenticationRequest.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.security;
2 |
3 | import io.quarkus.security.identity.request.AuthenticationRequest;
4 | import io.quarkus.security.identity.request.BaseAuthenticationRequest;
5 |
6 | import java.security.Principal;
7 | import java.util.Set;
8 |
9 | public class HeaderAuthenticationRequest extends BaseAuthenticationRequest {
10 | private String userName;
11 | private Set roles;
12 |
13 | public HeaderAuthenticationRequest(String userName, Set roles) {
14 | this.userName = userName;
15 | this.roles = roles;
16 | }
17 |
18 | public Principal getPrincipal() {
19 | return () -> userName;
20 | }
21 |
22 | public Set getRoles() {
23 | return roles;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/slides/04-catalogue.adoc:
--------------------------------------------------------------------------------
1 | [.transparency.no-transition]
2 | == Catalogue de recettes
3 |
4 | image::catalog.png[background, size=cover]
5 |
6 | [.notes]
7 | --
8 | C'est en tout cas vrai pour toutes les recettes proposées par OpenRewrite, mais il peut y en avoir d'autres.
9 | --
10 |
11 | [%notitle.demo,background-iframe="https://docs.openrewrite.org/recipes/java/testing/junit5/junit4to5migration"]
12 | === Partons à la découverte du catalogue
13 |
14 | [.notes]
15 | --
16 | Plus de 500 recettes la dernière fois que j'ai compté.
17 | --
18 |
19 |
20 | [.transparency.columns.blur-background]
21 | === !
22 |
23 | image::catalog.png[background, size=cover]
24 |
25 | [.column.is-one-third]
26 | --
27 | image::magic/malefincent.webp[]
28 | --
29 |
30 | [.column]
31 | --
32 | - ✅ Connaître les ingrédients
33 | - ✅ Savoir où les acheter
34 | - ✅ Savoir lire le grimoire
35 | --
--------------------------------------------------------------------------------
/slides/index.adoc:
--------------------------------------------------------------------------------
1 | = Openrewrite - Refactoring as code
2 | :docinfo: private
3 | :source-highlighter: highlight.js
4 | :highlightjsdir: highlight.js
5 | :highlightjs-theme: highlight.js/atom-one-light.min.css
6 | :autofit-option:
7 | :icons: font
8 | :favicon: favicon.svg
9 | :revealjs_slideNumber: true
10 | :revealjs_theme: white
11 | :revealjs_history: true
12 | :revealjs_pdfseparatefragments: false
13 | :revealjs_width: 1200
14 | :title-slide-background-image: title.webp
15 | :title-slide-background-size: cover
16 | :title-slide-background-repeat: no-repeat
17 | :title-slide-background-opacity: 0.5
18 | :customcss: css/meetup.css
19 | :imagesdir: images
20 |
21 | include::intro.adoc[]
22 | include::01-openrewrite.adoc[]
23 | include::02-running-recipes.adoc[]
24 | include::04-catalogue.adoc[]
25 | include::03-declarative-recipes.adoc[]
26 | include::05-coding-recipes.adoc[]
27 | include::outro.adoc[]
28 |
29 |
--------------------------------------------------------------------------------
/slides/outro.adoc:
--------------------------------------------------------------------------------
1 | [.transparency.no-transition.blur-background]
2 | == Points d'attention
3 |
4 |
5 | image::magic/jafar_3.webp[background, size=cover]
6 |
7 | [%notitle.transparency.no-transition.blur-background]
8 | === Points d'attention
9 |
10 |
11 | image::magic/jafar_3.webp[background, size=cover]
12 |
13 | - Quand on n'a qu'un marteau tout ressemble à un clou.
14 |
15 | [.fragment]
16 | Composition de recettes complexes.
17 |
18 | [.fragment]
19 | _java preview feature_
20 |
21 |
22 | [.transparency.columns.no-transition]
23 | === Merci !
24 |
25 | [.column]
26 | --
27 | [.important-text.has-text-left.vertical-align-middle]
28 |
29 | image:qrcode_github.png[alt="https://github.com/jtama/openrewrite-refactor-as-code"]
30 | --
31 |
32 | [.column]
33 | --
34 |
35 | [.important-text.has-text-left.vertical-align-middle]
36 |
37 | image:feedback.png[alt="https://openfeedback.io/devfestnantes2025/2025-10-16/openrewriterefactorascode"]
38 | --
--------------------------------------------------------------------------------
/toxic-library-remover/src/main/java/io/github/jtama/openrewrite/model/Node.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite.model;
2 |
3 | /**
4 | * Represents a node in the D3.js graph, corresponding to a Java class.
5 | */
6 | public class Node {
7 | private final String id;
8 | private final String group;
9 | private int size;
10 |
11 | /**
12 | * Constructs a new Node.
13 | * @param id The fully qualified name of the class.
14 | * @param group The package name of the class.
15 | */
16 | public Node(String id, String group) {
17 | this.id = id;
18 | this.group = group;
19 | this.size = 1; // Start with a base size
20 | }
21 |
22 | public String getId() {
23 | return id;
24 | }
25 |
26 | public String getGroup() {
27 | return group;
28 | }
29 |
30 | public int getSize() {
31 | return size;
32 | }
33 |
34 | /**
35 | * Increments the size of the node, typically representing an additional connection.
36 | */
37 | public void incrementSize() {
38 | this.size++;
39 | }
40 | }
--------------------------------------------------------------------------------
/toxic-library-remover/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
23 | %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/main/java/io/github/jtama/openrewrite/model/Link.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite.model;
2 |
3 | /**
4 | * Represents a link between two nodes in the D3.js graph, corresponding to an interaction between two Java classes.
5 | */
6 | public class Link {
7 | private final String source;
8 | private final String target;
9 | private int weight;
10 |
11 | /**
12 | * Constructs a new Link.
13 | * @param source The fully qualified name of the source class.
14 | * @param target The fully qualified name of the target class.
15 | */
16 | public Link(String source, String target) {
17 | this.source = source;
18 | this.target = target;
19 | this.weight = 1; // Start with a base weight
20 | }
21 |
22 | public String getSource() {
23 | return source;
24 | }
25 |
26 | public String getTarget() {
27 | return target;
28 | }
29 |
30 | public int getWeight() {
31 | return weight;
32 | }
33 |
34 | /**
35 | * Increments the weight of the link, representing an additional interaction between the two classes.
36 | */
37 | public void incrementWeight() {
38 | this.weight++;
39 | }
40 | }
--------------------------------------------------------------------------------
/.github/workflows/deploy.yaml:
--------------------------------------------------------------------------------
1 | name: Build and deploy latest version
2 |
3 | on:
4 | push:
5 | branches:
6 | - '*'
7 |
8 | permissions:
9 | contents: write
10 |
11 | concurrency:
12 | group: ci-${{ github.ref }}
13 | cancel-in-progress: true
14 |
15 | jobs:
16 | build-and-deploy:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: Checkout 🛎️
20 | uses: actions/checkout@v4
21 |
22 | - name: Install required dependencies
23 | run: ./setup.sh
24 |
25 | - name: Install and Build 🔧
26 | uses: addnab/docker-run-action@v3
27 | with:
28 | image: asciidoctor/docker-asciidoctor:1.80.0
29 | options: -v ${{ github.workspace }}/slides:/documents
30 | run: asciidoctor-revealjs -r asciidoctor-diagram /documents/index.adoc
31 |
32 | - name: Copy build to folder 📦
33 | run: |
34 | cd slides && \
35 | mkdir -p build/${{ github.ref_name }}/reveal.js/dist && \
36 | cp -r favicon.svg *.html images css highlight.js build/${{ github.ref_name }}/ && \
37 | cp -r reveal.js/dist/* build/${{ github.ref_name }}/reveal.js/dist/ && \
38 | cp -r reveal.js/plugin build/${{ github.ref_name }}/reveal.js/
39 |
40 | - name: Deploy 🚀
41 | uses: JamesIves/github-pages-deploy-action@v4.5.0
42 | with:
43 | folder: slides/build
44 | branch: gh-pages
45 | clean: false
--------------------------------------------------------------------------------
/slides/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/README.adoc:
--------------------------------------------------------------------------------
1 | = Openrewrite: Refactoring as Code
2 |
3 | Readable version available on https://jtama.github.io/openrewrite-refactoring-as-code/[Github Pages -> ici]
4 |
5 | == Slides
6 | === Generate
7 |
8 | [source%linenums,bash]
9 | ----
10 | cd slides
11 | jbang qrcode@maxandersen https://pocket.e-tech.dev/ -o images/easter-egg.png
12 | jbang qrcode@maxandersen -i image/github.png https://github.com/jtama/openrewrite-refactoring-as-code --qr-colo="linear-gradient(90deg, rgba(36,14,0,1) 0%, rgba(9,121,105,1) 35%, rgba(0,212,255,1) 100%);"
13 | podman container run --rm -v $(pwd)/slides:/documents -w /documents asciidoctor/docker-asciidoctor:1.80.0 asciidoctor-revealjs -r asciidoctor-diagram index.adoc
14 | ----
15 |
16 | === Run locally
17 |
18 | [source%linenums,bash]
19 | ----
20 | podman container run --name prez --rm -d -p 8080:80 -v $(pwd)/slides:/usr/share/nginx/html nginx
21 | podman container run --name coder --rm -d -p 8443:8443 -v $(pwd):/config/workspace -v ~/.m2:/config/.m2 -e MAVEN_CONFIG=/config/.m2 ghcr.io/jtama/java_jbang_codeserver:latest
22 | ----
23 |
24 | == `toxic-library`
25 |
26 | Contains a small dumb maven module mocking a toxic library.
27 |
28 | Install locally with `mvn clean install`.
29 |
30 | == `toxic-library-remover`
31 |
32 | Contains sample code demonstrating OpenRewrite capabilities.
33 |
34 | Install locally with `mvn clean install`.
35 |
36 | == `toxic-library-user`
37 |
38 | Contains a small java application using the `toxic-library`.
39 |
40 | Clean using :
41 |
42 | include::toxic-library-user/README.adoc[]
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/hostels/HostelReservationService.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.hostels;
2 |
3 | import com.github.jtama.app.exception.UnavailableException;
4 | import com.github.jtama.app.exception.UnknownEntityException;
5 | import com.github.jtama.app.reservation.Reservation;
6 | import com.github.jtama.app.util.MonthValidator;
7 | import com.github.jtama.toxic.FooBarUtils;
8 | import jakarta.enterprise.context.ApplicationScoped;
9 | import jakarta.inject.Inject;
10 | import jakarta.transaction.Transactional;
11 |
12 | import java.util.Optional;
13 |
14 | @ApplicationScoped
15 | public class HostelReservationService {
16 |
17 | @Inject
18 | MonthValidator monthValidator;
19 |
20 | @Transactional
21 | public Reservation book(String name, int month, String userName) {
22 | monthValidator.validateMonth(month);
23 | System.out.println(FooBarUtils.isEmpty(userName));
24 | if(Reservation.existsByUserNameAndMonthAndHostelName(userName, month, name)){
25 | throw new UnavailableException("Hostel %s is already booked for month %s".formatted(name, month));
26 | }
27 |
28 | Reservation reservation = new Reservation(userName,month);
29 |
30 | Optional hostel = Hostel.findByName(name);
31 | if (hostel.isEmpty()){
32 | throw new UnknownEntityException("The hostel %s doesn't exist".formatted(name));
33 | }
34 | reservation.setHostel(hostel.get());
35 | return reservation;
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/slides/02-running-recipes.adoc:
--------------------------------------------------------------------------------
1 |
2 | [.transparency.no-transition]
3 | == Exécution d'une recette
4 |
5 | image::running.avif[background, size=cover]
6 |
7 | [%notitle.transparency.blur-background]
8 | === Via la ligne de commande
9 |
10 | image::running.avif[background, size=cover]
11 | [.notes]
12 | --
13 | Pareil avec gradle
14 | --
15 |
16 | image::running.avif[background, size=cover]
17 | [.fragment]
18 | [source%linenums,console,highlight="1|2..3|4..5",step=0]
19 | ----
20 | $ mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
21 | -Drewrite.recipeArtifactCoordinates=
22 | org.openrewrite.recipe:rewrite-testing-frameworks:RELEASE\
23 | -Drewrite.activeRecipes=
24 | org.openrewrite.java.testing.junit5.JUnit4to5Migration
25 | ----
26 | [.fragment, data-fragment-index=0]
27 | Déclaration du plugin
28 | [.fragment, data-fragment-index=1]
29 | Ajout de la dépendance de la recette
30 | [.fragment, data-fragment-index=2]
31 | Activation des recettes
32 |
33 | [.notes]
34 | --
35 | Il existe aussi une intégration avec IntelliJ qui facilite beaucoup la vie.
36 | --
37 |
38 | [.columns.transparency.blur-background]
39 | === !
40 |
41 | image::running.avif[background, size=cover]
42 |
43 | [.column.is-one-third]
44 | --
45 | image::magic/nim.webp[]
46 | --
47 |
48 | [.column]
49 | --
50 | - ✅ Connaître les ingrédients
51 | - ✅ Savoir où les acheter
52 | --
53 |
54 | [.notes]
55 | --
56 | Les façons de faire décrites ci-dessus ne sont valables que si les recettes ne prennent pas de paramètres. Si telle n'est pas le cas, il va falloir passer à l'étape suivante
57 | --
58 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/main/resources/META-INF/rewrite/rewrite.yml:
--------------------------------------------------------------------------------
1 | ---
2 | type: specs.openrewrite.org/v1beta/recipe
3 | name: com.github.jtama.openrewrite.RemovesThatToxicDependency
4 | displayName: Removes that toxic dependency
5 | description: |
6 | Migrate from AcmeToxic ☠️ to AcmeHealthy 😇,
7 | removes dependencies and migrates code.
8 | tags:
9 | - acme
10 | - toxic
11 | recipeList:
12 | - org.openrewrite.java.ChangeMethodTargetToStatic:
13 | methodPattern: com.github.jtama.toxic.BigDecimalUtils valueOf(..)
14 | fullyQualifiedTargetTypeName: java.math.BigDecimal
15 | - com.github.jtama.openrewrite.VousAllezVoirCeQueVousAllezVoir
16 | - org.openrewrite.java.RemoveUnusedImports
17 | - org.openrewrite.staticanalysis.CommonStaticAnalysis
18 | - org.openrewrite.staticanalysis.RemoveUnusedPrivateFields
19 | - org.openrewrite.maven.RemoveDependency:
20 | groupId: com.github.jtama
21 | artifactId: toxic-library
22 | - org.openrewrite.maven.RemoveProperty:
23 | propertyName: toxic.version
24 | - com.github.jtama.openrewrite.ExtractInterface:
25 | targetAnnotation: com.github.jtama.toxic.LearnToFly
26 | ---
27 | type: specs.openrewrite.org/v1beta/recipe
28 | name: com.github.jtama.openrewrite.VousAllezVoirCeQueVousAllezVoir
29 | displayName: Ça va vous épater
30 | description: |
31 | Rech. proj. pr proj. priv. Self Dem. Brt. Poss. S’adr. à l’hô. Mart
32 | tags:
33 | - acme
34 | recipeList:
35 | - com.github.jtama.openrewrite.RemoveFooBarUtilsIsEmptyRecipes
36 | - com.github.jtama.openrewrite.RemoveFooBarUtilsStringFormatted
37 | - com.github.jtama.openrewrite.RemoveLogStartInvocations
38 | - com.github.jtama.openrewrite.UseObjectsCompare
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/rocket/Rocket.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.rocket;
2 |
3 |
4 | import com.github.jtama.app.exception.DuplicateEntityException;
5 | import com.github.jtama.toxic.FooBarUtils;
6 | import io.quarkus.hibernate.orm.panache.PanacheEntity;
7 | import io.quarkus.runtime.annotations.RegisterForReflection;
8 | import jakarta.persistence.Entity;
9 | import jakarta.persistence.EnumType;
10 | import jakarta.persistence.Enumerated;
11 | import jakarta.validation.constraints.NotBlank;
12 | import jakarta.validation.constraints.Size;
13 |
14 | import java.util.Optional;
15 |
16 | @Entity
17 | @RegisterForReflection
18 | public class Rocket extends PanacheEntity {
19 |
20 | @NotBlank
21 | @Size(min = 1, max = 50)
22 | private String name;
23 |
24 | @Enumerated(EnumType.STRING)
25 | private RocketType type;
26 |
27 | public static Optional findByName(String name) {
28 | return find("name", name).firstResultOptional();
29 | }
30 |
31 | public static Rocket persistIfNotExists(Rocket rocket) {
32 | if (find("name", rocket.name).count() > 0) {
33 | throw new DuplicateEntityException(new FooBarUtils().stringFormatted("Rocket named %s already exists", rocket.name));
34 | }
35 | rocket.persist();
36 | return rocket;
37 | }
38 |
39 | public void setName(String name) {
40 | this.name = name;
41 | }
42 |
43 | public void setType(RocketType type) {
44 | this.type = type;
45 | }
46 |
47 | public String getName() {
48 | return name;
49 | }
50 |
51 | public RocketType getType() {
52 | return type;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/rocket/RocketController.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.rocket;
2 |
3 | import com.github.jtama.app.reservation.Reservation;
4 | import jakarta.annotation.security.RolesAllowed;
5 | import jakarta.inject.Inject;
6 | import jakarta.transaction.Transactional;
7 | import jakarta.validation.Valid;
8 | import jakarta.ws.rs.Consumes;
9 | import jakarta.ws.rs.GET;
10 | import jakarta.ws.rs.POST;
11 | import jakarta.ws.rs.Path;
12 | import jakarta.ws.rs.PathParam;
13 | import jakarta.ws.rs.Produces;
14 | import jakarta.ws.rs.QueryParam;
15 | import jakarta.ws.rs.core.Context;
16 | import jakarta.ws.rs.core.MediaType;
17 | import jakarta.ws.rs.core.Response;
18 | import jakarta.ws.rs.core.SecurityContext;
19 | import org.jboss.resteasy.annotations.Query;
20 |
21 | import java.util.List;
22 |
23 | @Path("/api/rockets")
24 | @Consumes(MediaType.APPLICATION_JSON)
25 | @Produces(MediaType.APPLICATION_JSON)
26 | public class RocketController {
27 |
28 | @Inject
29 | RocketReservationService rocketReservationService;
30 |
31 | @GET
32 | public List getAll() {
33 | return Rocket.listAll();
34 | }
35 |
36 | @POST
37 | @RolesAllowed("ADMIN")
38 | @Transactional
39 | public Response create(@Valid Rocket rocket) {
40 | return Response.status(Response.Status.CREATED).entity(Rocket.persistIfNotExists(rocket)).build();
41 |
42 | }
43 |
44 | @POST
45 | @Path("/{name}/book")
46 | @RolesAllowed("USER")
47 | public Reservation book(@PathParam("name") String name, @QueryParam("month") Integer month, @Context SecurityContext securityContext) {
48 | return rocketReservationService.book(name, month, securityContext.getUserPrincipal().getName());
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/hostels/HostelController.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.hostels;
2 |
3 | import com.github.jtama.app.reservation.Reservation;
4 | import com.github.jtama.toxic.BigDecimalUtils;
5 | import com.github.jtama.toxic.FooBarUtils;
6 | import com.github.jtama.toxic.LearnToFly;
7 | import jakarta.annotation.security.RolesAllowed;
8 | import jakarta.inject.Inject;
9 | import jakarta.transaction.Transactional;
10 | import jakarta.ws.rs.Consumes;
11 | import jakarta.ws.rs.GET;
12 | import jakarta.ws.rs.POST;
13 | import jakarta.ws.rs.Path;
14 | import jakarta.ws.rs.PathParam;
15 | import jakarta.ws.rs.Produces;
16 | import jakarta.ws.rs.QueryParam;
17 | import jakarta.ws.rs.core.Context;
18 | import jakarta.ws.rs.core.MediaType;
19 | import jakarta.ws.rs.core.SecurityContext;
20 |
21 | import java.security.Security;
22 | import java.util.List;
23 |
24 | @Path("/api/hostels")
25 | @Produces(MediaType.APPLICATION_JSON)
26 | @LearnToFly
27 | public class HostelController {
28 |
29 | @Inject
30 | HostelReservationService hostelReservationService;
31 |
32 | @GET
33 | public List getAll() {
34 | return Hostel.listAll();
35 | }
36 |
37 | @POST
38 | @Consumes(MediaType.APPLICATION_JSON)
39 | @RolesAllowed("ADMIN")
40 | @Transactional
41 | public Hostel create(Hostel hostel) {
42 | return Hostel.persistIfNotExists(hostel);
43 | }
44 |
45 | @POST
46 | @Path("/{name}/book")
47 | @RolesAllowed("USER")
48 | public Reservation book(@PathParam("name")String name, @QueryParam("month") Integer month, @Context SecurityContext security) {
49 | BigDecimalUtils.valueOf(month.longValue());// Because I can !
50 | return hostelReservationService.book(name, month, security.getUserPrincipal().getName());
51 | }
52 |
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/main/java/io/github/jtama/openrewrite/RemoveFooBarUtilsIsEmpty.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite;
2 |
3 | import com.github.jtama.toxic.FooBarUtils;
4 | import com.google.errorprone.refaster.annotation.AfterTemplate;
5 | import com.google.errorprone.refaster.annotation.BeforeTemplate;
6 | import org.openrewrite.java.template.RecipeDescriptor;
7 |
8 | import java.util.List;
9 |
10 | @RecipeDescriptor(
11 | name = "Remove `FooBarUtils.isEmpty` methodes usages",
12 | description = "Replace any usage of `FooBarUtils.isEMpty` method by standards equivalent.")
13 | public class RemoveFooBarUtilsIsEmpty {
14 |
15 | @RecipeDescriptor(
16 | name = "Replace `FooBarUtils.isEmptyString(String)` with standard equivalent",
17 | description = "Replace `FooBarUtils.isEmptyString(String)` with ternary 'value == null || value.isEmpty()'."
18 | )
19 | public static class RemoveStringIsEmpty {
20 |
21 | @BeforeTemplate
22 | boolean before(String value) {
23 | return FooBarUtils.isEmpty(value);
24 | }
25 |
26 | @AfterTemplate
27 | boolean after(String value) {
28 | return value == null || value.isEmpty();
29 | }
30 |
31 | }
32 |
33 |
34 | @RecipeDescriptor(
35 | name = "Replace `FooBarUtils.isEmptyList(List)` with standard equivalent",
36 | description = "Replace `FooBarUtils.isEmptyList(List)` with ternary 'value == null || value.isEmpty()'."
37 | )
38 | public static class RemoveListIsEmpty {
39 |
40 | @BeforeTemplate
41 | public boolean before(List value) {
42 | return FooBarUtils.isEmpty(value);
43 | }
44 |
45 | @AfterTemplate
46 | public boolean after(List value) {
47 | return value == null || value.isEmpty();
48 | }
49 |
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/security/NaiveSecurityProvider.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.security;
2 |
3 | import io.quarkus.security.AuthenticationFailedException;
4 | import io.quarkus.security.identity.AuthenticationRequestContext;
5 | import io.quarkus.security.identity.IdentityProvider;
6 | import io.quarkus.security.identity.SecurityIdentity;
7 | import io.quarkus.security.runtime.QuarkusSecurityIdentity;
8 | import io.smallrye.mutiny.Uni;
9 | import jakarta.enterprise.context.ApplicationScoped;
10 | import jakarta.inject.Inject;
11 | import org.jboss.logging.Logger;
12 |
13 | @ApplicationScoped
14 | public class NaiveSecurityProvider implements IdentityProvider {
15 |
16 | @Inject
17 | Logger logger;
18 |
19 | @Override
20 | public Class getRequestType() {
21 | return HeaderAuthenticationRequest.class;
22 | }
23 |
24 | @Override
25 | public Uni authenticate(HeaderAuthenticationRequest request, AuthenticationRequestContext context) {
26 | return Uni.createFrom().item(() -> {
27 | try {
28 | QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
29 | if (request.getPrincipal().getName() == null) {
30 | builder.setPrincipal(() -> "");
31 | } else {
32 | builder.setPrincipal(request.getPrincipal());
33 | for (String i : request.getRoles()) {
34 | builder.addRole(i);
35 | }
36 | }
37 | return builder.build();
38 | } catch (SecurityException e) {
39 | logger.debug("Authentication failed", e);
40 | throw new AuthenticationFailedException(e);
41 | }
42 | });
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/exception/OnerentExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.exception;
2 |
3 | import jakarta.ws.rs.NotAuthorizedException;
4 | import jakarta.ws.rs.core.Response;
5 | import jakarta.ws.rs.ext.ExceptionMapper;
6 | import jakarta.ws.rs.ext.Provider;
7 | import java.util.HashMap;
8 | import java.util.Map;
9 | import java.util.function.Function;
10 |
11 | @Provider
12 | public class OnerentExceptionHandler implements ExceptionMapper {
13 |
14 | private static Map, Function> mappers;
15 | private static Function functionalError = e -> Response.status(Response.Status.OK).entity(e.getMessage()).build();
16 | private static Function defaultHandler = e -> Response.status(Response.Status.INTERNAL_SERVER_ERROR)
17 | .entity(e.getMessage())
18 | .build();
19 |
20 | static {
21 | mappers = new HashMap<>();
22 | mappers.put(DuplicateEntityException.class, e -> Response.status(Response.Status.CONFLICT)
23 | .entity(e.getMessage())
24 | .build());
25 | mappers.put(UnknownEntityException.class, e -> Response.status(Response.Status.NOT_FOUND)
26 | .entity(e.getMessage())
27 | .build());
28 | mappers.put(NotAuthorizedException.class, e -> Response.status(Response.Status.UNAUTHORIZED)
29 | .entity("He non du con")
30 | .build());
31 | mappers.put(InvalidBookingException.class,functionalError);
32 | mappers.put(InvalidNameException.class,functionalError);
33 | mappers.put(UnavailableException.class,functionalError);
34 |
35 | }
36 |
37 | @Override
38 | public Response toResponse(Exception exception) {
39 | return mappers.getOrDefault(exception.getClass(), defaultHandler).apply(exception);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/test/java/io/github/jtama/openrewrite/RemoveFooBarUtilsStringFormattedTest.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.openrewrite.DocumentExample;
5 | import org.openrewrite.java.JavaParser;
6 | import org.openrewrite.test.RecipeSpec;
7 | import org.openrewrite.test.RewriteTest;
8 |
9 | import static org.openrewrite.java.Assertions.java;
10 |
11 | class RemoveFooBarUtilsStringFormattedTest implements RewriteTest {
12 |
13 | @Override
14 | public void defaults(RecipeSpec spec) {
15 | spec.recipe(new RemoveFooBarUtilsStringFormatted())
16 | .parser(JavaParser.fromJavaVersion()
17 | .logCompilationWarningsAndErrors(true)
18 | .classpath("toxic-library")
19 | );
20 | }
21 |
22 | @DocumentExample
23 | @Test
24 | void removeStringFormattedInvocation() {
25 | rewriteRun(
26 | //language=java
27 | java(
28 | """
29 | import com.github.jtama.toxic.FooBarUtils;
30 |
31 | public class FullDriftCar {
32 |
33 | public String foo() {
34 | return new FooBarUtils().stringFormatted("Hello %s %s %s", 2L,
35 | "tutu" +
36 | "tata",
37 | this.getClass()
38 | .getName());
39 | }
40 | }
41 | """,
42 | """
43 | public class FullDriftCar {
44 |
45 | public String foo() {
46 | return "Hello %s %s %s".formatted(2L,
47 | "tutu" +
48 | "tata",
49 | this.getClass()
50 | .getName());
51 | }
52 | }
53 | """));
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/hostels/Hostel.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.hostels;
2 |
3 | import com.github.jtama.app.exception.DuplicateEntityException;
4 | import com.github.jtama.app.exception.InvalidNameException;
5 | import com.github.jtama.toxic.FooBarUtils;
6 | import io.quarkus.hibernate.orm.panache.PanacheEntity;
7 | import io.quarkus.runtime.annotations.RegisterForReflection;
8 | import jakarta.persistence.Entity;
9 |
10 | import java.util.Optional;
11 | import java.util.regex.Matcher;
12 | import java.util.regex.Pattern;
13 |
14 | @Entity
15 | @RegisterForReflection
16 | public class Hostel extends PanacheEntity {
17 |
18 | private static final String PATTERN = "[a-zA-Z]([-a-z0-9]*[a-z0-9])";
19 | private static final Pattern NAME_PATTERN = Pattern.compile(PATTERN);
20 |
21 | private String name;
22 |
23 | public static Hostel persistIfNotExists(Hostel hostel) {
24 | System.out.println(FooBarUtils.isEmpty(hostel.getName()));
25 | if (!validateName(hostel.getName())) {
26 | throw new InvalidNameException(new FooBarUtils().stringFormatted("Name %s is invalid for a hostel. Name should comply the following pattern %s", hostel.getName(), PATTERN));
27 | }
28 | if (Hostel.count("name", hostel.name) > 0) {
29 | throw new DuplicateEntityException(new FooBarUtils().stringFormatted("Hostel %s already exists", hostel.name));
30 | }
31 | hostel.persist();
32 | return hostel;
33 | }
34 |
35 | private static boolean validateName(String name) {
36 | Matcher matcher = NAME_PATTERN.matcher(name);
37 | return matcher.matches();
38 | }
39 |
40 | public static Optional findByName(String name) {
41 | return find("name", name).firstResultOptional();
42 | }
43 |
44 |
45 | public String getName() {
46 | return name;
47 | }
48 |
49 | public void setName(String name) {
50 | this.name = name;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/slides/intro.adoc:
--------------------------------------------------------------------------------
1 | [%notitle]
2 | == Openrewrite
3 |
4 | image::upside_down.png[background, size=contain]
5 |
6 | [%notitle,transition=none]
7 | === Chercher / Remplacer
8 |
9 | image::upside_down.png[background, size=contain]
10 | image::chercher_remplacer.png[role=chercher_remplacer]
11 |
12 | [%notitle,transition=none]
13 | === Regexp
14 |
15 | image::upside_down.png[background, size=contain]
16 | image::chercher_remplacer.png[role=chercher_remplacer]
17 | image::regexp.png[role=regexp]
18 |
19 | [%notitle,transition=none]
20 | === Click droit
21 |
22 | image::upside_down.png[background, size=contain]
23 | image::chercher_remplacer.png[role=chercher_remplacer]
24 | image::regexp.png[role=regexp]
25 | image::click_droit.png[role=click_droit]
26 |
27 | [%notitle,transition=none]
28 | === LLM/RAG
29 |
30 | image::upside_down.png[background, size=contain]
31 | image::chercher_remplacer.png[role=chercher_remplacer]
32 | image::regexp.png[role=regexp]
33 | image::click_droit.png[role=click_droit]
34 | image::llm.png[role=llm]
35 |
36 | [%notitle,transition=none,%notitle]
37 | === Punition
38 |
39 | image::upside_down.png[background, size=contain]
40 | image::chercher_remplacer.png[role=chercher_remplacer]
41 | image::regexp.png[role=regexp]
42 | image::click_droit.png[role=click_droit]
43 | image::llm.png[role=llm]
44 | audio::sound/punition.mp3[data-autoplay]
45 |
46 | [%notitle.columns.is-vcentered.transparency]
47 | === Présentation
48 |
49 | [.easter-egg]
50 | --
51 | image::easter-egg.png[width=400]
52 | --
53 |
54 | [.column.is-one-third]
55 | --
56 | image::jta_1.png[]
57 | --
58 |
59 | [.column.is-3.has-text-left.medium]
60 | --
61 | Jérôme
62 |
63 | Tech Lead/Architecte, Professeur de réécriture à BeauxBâtons
64 | --
65 |
66 | [.column]
67 | --
68 | [.vertical-align-middle]
69 | image:logo_onepoint.png[]
70 |
71 | [.vertical-align-middle]
72 | icon:github[] @jtama image:avatar.png[width=100]
73 | --
74 |
75 | [%notitle.transparency]
76 | === Le permis
77 |
78 | image::permis_de_refactoring.png[width=600]
79 |
80 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/rocket/RocketReservationService.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.rocket;
2 |
3 | import com.github.jtama.app.exception.InvalidBookingException;
4 | import com.github.jtama.app.exception.UnavailableException;
5 | import com.github.jtama.app.exception.UnknownEntityException;
6 | import com.github.jtama.app.reservation.Reservation;
7 | import com.github.jtama.app.util.MonthValidator;
8 | import com.github.jtama.toxic.BigDecimalUtils;
9 | import com.github.jtama.toxic.FooBarUtils;
10 | import jakarta.enterprise.context.ApplicationScoped;
11 | import jakarta.inject.Inject;
12 | import jakarta.transaction.Transactional;
13 |
14 | import java.util.List;
15 | import java.util.Optional;
16 |
17 | @ApplicationScoped
18 | public class RocketReservationService {
19 |
20 | @Inject
21 | MonthValidator monthValidator;
22 |
23 | @Transactional
24 | public Reservation book(String name, int month, String user) {
25 | monthValidator.validateMonth(month);
26 | BigDecimalUtils.valueOf(Long.parseLong(month + ""));
27 | Optional reservation = Reservation.findByUserNameAndMonthAndHostelIsNotNull(user, month);
28 | if (reservation.isEmpty()) {
29 | throw new InvalidBookingException(new FooBarUtils().stringFormatted("No hostel is booked for user %S on month %s", user, month));
30 | }
31 | reservation.map(resa -> FooBarUtils.isEmpty(resa.getUserName()));
32 | Boolean booked = Reservation.existsByMonthAndRocketName(month, name);
33 | if (booked) {
34 | throw new UnavailableException(new FooBarUtils().stringFormatted("Rocket %s has already been booked for month %s", name, month));
35 | }
36 | Optional rocket = Rocket.findByName(name);
37 | if (rocket.isEmpty()) {
38 | throw new UnknownEntityException(new FooBarUtils().stringFormatted("Rocket %s doesn't exists", name));
39 | }
40 | rocket.map(rock -> FooBarUtils.isEmpty(List.of(rock)));
41 | reservation.get().setRocket(rocket.get());
42 | return reservation.get();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/slides/01-openrewrite.adoc:
--------------------------------------------------------------------------------
1 |
2 | [%notitle.transparency]
3 | == Openrewrite
4 |
5 | image::openrewrite.svg[width=30%]
6 | image::under_logo.svg[]
7 |
8 | [.notes]
9 | --
10 | C'est un outil java open-source - Apache 2
11 |
12 | . Debezium
13 | . SdkMan
14 | . Feign
15 | . Hibernate
16 | . Jackson
17 | . JBang
18 | . JReleaser
19 | . Quarkus
20 | --
21 |
22 | [.transparency.no-transition]
23 | === Les concepts
24 |
25 | image::concepts.jpg[background, size=cover]
26 |
27 | [%notitle.transparency.blur-background]
28 | === Les concepts (2/2)
29 |
30 | image::concepts.jpg[background, size=cover]
31 |
32 | Recipe
33 | [.fragment]
34 | **L**ossless **S**yntax **T**ree
35 |
36 | [.notes]
37 | --
38 | * Programming languages
39 | ** Java
40 | ** Kotlin
41 | ** Groovy
42 | * Data formats
43 | ** XML
44 | ** Properties
45 | ** YAML
46 | ** JSON
47 | ** Protobuf
48 | * Build tools
49 | ** Maven
50 | ** Gradle
51 |
52 | Par exemple 73 points d'extension pour Java...
53 | --
54 |
55 | [.transparency.no-transition]
56 | === Anatomie d'une recette
57 |
58 | image::anatomy.png[background, size=cover]
59 |
60 | [%notitle.transparency]
61 | === Anatomie d'une recette (2/2)
62 |
63 | image::anatomy.png[background, size=cover]
64 |
65 | [source,java,highlight="3|5|7|9|11|13"]
66 | ----
67 | public abstract class Recipe implements Cloneable {
68 |
69 | public abstract String getDisplayName();
70 |
71 | public abstract String getDescription();
72 |
73 | public Set getTags() { ... }
74 |
75 | public List getRecipeList() { ... }
76 |
77 | public TreeVisitor, ExecutionContext> getVisitor() { ... }
78 |
79 | public void addDataTable(DataTable> dataTable) { ... }
80 | }
81 | ----
82 |
83 |
84 | [.notes]
85 | --
86 | Des résultats sous forme de _data tables_, et le concept de préconditions.
87 | --
88 |
89 | [.columns.transparency.blur-background]
90 | === !
91 |
92 | image::anatomy.png[background, size=cover]
93 |
94 | [.column.is-one-third]
95 | --
96 | image::magic/mickey.webp[]
97 | --
98 |
99 | [.column]
100 | --
101 | - ✅ Connaître les ingrédients
102 | --
103 |
104 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/reservation/Reservation.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.reservation;
2 |
3 |
4 | import com.github.jtama.app.hostels.Hostel;
5 | import com.github.jtama.app.rocket.Rocket;
6 | import io.quarkus.hibernate.orm.panache.PanacheEntity;
7 | import io.quarkus.runtime.annotations.RegisterForReflection;
8 | import jakarta.persistence.Entity;
9 | import jakarta.persistence.JoinColumn;
10 | import jakarta.persistence.OneToOne;
11 |
12 | import java.util.Optional;
13 |
14 | @Entity
15 | @RegisterForReflection
16 | public class Reservation extends PanacheEntity {
17 |
18 | private String userName;
19 |
20 | private int month;
21 |
22 | @OneToOne
23 | @JoinColumn(name = "hostel_id")
24 | private Hostel hostel;
25 |
26 | @OneToOne
27 | @JoinColumn(name = "rocket_id")
28 | private Rocket rocket;
29 |
30 | public static Boolean existsByUserNameAndMonthAndHostelName(String user, int month, String name) {
31 | return count("userName = ?1 and month = ?2 and hostel.name = ?3", user, month, name) > 0;
32 | }
33 |
34 | public static Optional findByUserNameAndMonthAndHostelIsNotNull(String user, int month) {
35 | return find("userName = ?1 and month = ?2 and hostel is not null", user, month).firstResultOptional();
36 | }
37 |
38 | public static Boolean existsByMonthAndRocketName(int month, String name) {
39 | return count("month = ?1 and rocket.name = ?2", month, name) > 0;
40 | }
41 |
42 | public Reservation() {
43 | }
44 |
45 | public Reservation(String userName, int month) {
46 | this.userName = userName;
47 | this.month = month;
48 | }
49 |
50 | public String getUserName() {
51 | return userName;
52 | }
53 |
54 | public void setUserName(String userName) {
55 | this.userName = userName;
56 | }
57 |
58 | public int getMonth() {
59 | return month;
60 | }
61 |
62 | public void setMonth(int month) {
63 | this.month = month;
64 | }
65 |
66 | public Hostel getHostel() {
67 | return hostel;
68 | }
69 |
70 | public void setHostel(Hostel house) {
71 | this.hostel = house;
72 | }
73 |
74 | public Rocket getRocket() {
75 | return rocket;
76 | }
77 |
78 | public void setRocket(Rocket rocket) {
79 | this.rocket = rocket;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/test/java/io/github/jtama/openrewrite/RemoveLogStartInvocationTest.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.openrewrite.DocumentExample;
5 | import org.openrewrite.java.JavaParser;
6 | import org.openrewrite.test.RecipeSpec;
7 | import org.openrewrite.test.RewriteTest;
8 |
9 | import static org.openrewrite.java.Assertions.java;
10 |
11 | class RemoveLogStartInvocationTest implements RewriteTest {
12 |
13 | @Override
14 | public void defaults(RecipeSpec spec) {
15 | spec.recipe(new RemoveLogStartInvocations())
16 | .parser(JavaParser.fromJavaVersion()
17 | .logCompilationWarningsAndErrors(true)
18 | .classpath("toxic-library"))
19 | .cycles(3);
20 |
21 | }
22 |
23 | @DocumentExample
24 | @Test
25 | void removeLogStartInvocations() {
26 | rewriteRun(
27 | //language=java
28 | java(
29 | """
30 | import com.github.jtama.toxic.Timer;
31 |
32 | public class ManualGearCar {
33 |
34 | @Deprecated
35 | public void drift(String param) {
36 | Timer.logStart();
37 | System.out.println("A very long process");
38 | Timer.logEnd();
39 | }
40 |
41 | public void hardBreak(Boolean param) {
42 | // do nothing
43 | }
44 | }
45 | """,
46 | """
47 | import io.micrometer.core.annotation.Timed;
48 |
49 | public class ManualGearCar {
50 |
51 | @Deprecated
52 | @Timed
53 | public void drift(String param) {
54 | System.out.println("A very long process");
55 | }
56 |
57 | public void hardBreak(Boolean param) {
58 | // do nothing
59 | }
60 | }
61 | """));
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/toxic-library-user/src/main/java/com/github/jtama/app/security/NaiveAuth.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.security;
2 |
3 | import com.github.jtama.toxic.Timer;
4 | import io.netty.handler.codec.http.HttpHeaderNames;
5 | import io.netty.handler.codec.http.HttpResponseStatus;
6 | import io.quarkus.security.identity.IdentityProviderManager;
7 | import io.quarkus.security.identity.SecurityIdentity;
8 | import io.quarkus.security.identity.request.AuthenticationRequest;
9 | import io.quarkus.vertx.http.runtime.security.ChallengeData;
10 | import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
11 | import io.quarkus.vertx.http.runtime.security.HttpCredentialTransport;
12 | import io.smallrye.mutiny.Uni;
13 | import io.vertx.ext.web.RoutingContext;
14 | import jakarta.enterprise.context.ApplicationScoped;
15 | import jakarta.inject.Inject;
16 | import org.jboss.logging.Logger;
17 |
18 | import java.util.Collections;
19 | import java.util.Locale;
20 | import java.util.Optional;
21 | import java.util.Set;
22 |
23 | @ApplicationScoped
24 | public class NaiveAuth implements HttpAuthenticationMechanism {
25 |
26 | private static final String ONERENT_PREFIX = "onerent";
27 | private static final String LOWERCASE_BASIC_PREFIX = ONERENT_PREFIX.toLowerCase(Locale.ENGLISH);
28 | protected static final ChallengeData CHALLENGE_DATA = new ChallengeData(
29 | HttpResponseStatus.UNAUTHORIZED.code(),
30 | HttpHeaderNames.WWW_AUTHENTICATE,
31 | "onerent realm=one-rent");
32 |
33 | @Override
34 | public Uni authenticate(RoutingContext context,
35 | IdentityProviderManager identityProviderManager) {
36 | Timer.logStart();
37 | var userName = context.request().headers().get("X-user-name");
38 | var userRoles = Set.of(Optional.ofNullable(context.request().getHeader("X-user-roles")).orElse("").split(","));
39 | HeaderAuthenticationRequest credential = new HeaderAuthenticationRequest(userName,
40 | userRoles);
41 | return identityProviderManager.authenticate(credential);
42 | }
43 |
44 |
45 | @Override
46 | public Uni getChallenge(RoutingContext context) {
47 | return Uni.createFrom().item(CHALLENGE_DATA);
48 | }
49 |
50 | @Override
51 | public Set> getCredentialTypes() {
52 | return Collections.singleton(HeaderAuthenticationRequest.class);
53 | }
54 |
55 | @Override
56 | public Uni getCredentialTransport(RoutingContext context) {
57 | return Uni.createFrom().
58 | item(new HttpCredentialTransport(HttpCredentialTransport.Type.AUTHORIZATION, "onerent"));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/slides/images/openrewrite.svg:
--------------------------------------------------------------------------------
1 |
2 | image/svg+xml
3 |
4 |
5 |
--------------------------------------------------------------------------------
/slides/03-declarative-recipes.adoc:
--------------------------------------------------------------------------------
1 |
2 | [.transparency.no-transition]
3 | == Declarative recipes
4 |
5 | image::declarative.jpg[background, size=cover]
6 |
7 | [.notes]
8 | --
9 | Une recette déclarative est en fait juste un format de déclaration de recette sans avoir à écrire du code.
10 | --
11 |
12 |
13 | [%notitle.transparency]
14 | === Et ça ressemble à ?
15 |
16 | image::declarative.jpg[background, size=cover]
17 |
18 | [source%linenums,yaml,highlight="3|4..10|13..17|12|20|26..28|29..32"]
19 | ----
20 | ---
21 | type: specs.openrewrite.org/v1beta/recipe
22 | name: com.github.jtama.openrewrite.RedIsDead
23 | displayName: Removes that toxic dependency
24 | description: |
25 | Migrate from AcmeToxic ☠️ to AcmeHealthy 😇,
26 | removes dependencies and migrates code.
27 | tags:
28 | - acme
29 | - toxic
30 | recipeList:
31 | - com.github.jtama.openrewrite.CaVaCouperCherie
32 | - org.openrewrite.maven.RemoveDependency:
33 | groupId: com.github.jtama
34 | artifactId: toxic-library
35 | - org.openrewrite.maven.RemoveUnusedProperties:
36 | propertyPattern: .*toxic\.version
37 | ---
38 | type: specs.openrewrite.org/v1beta/recipe
39 | name: com.github.jtama.openrewrite.CaVaCouperCherie
40 | displayName: Ça va vous épater
41 | description: |
42 | Rech. proj. pr proj. priv. Self Dem. Brt. Poss. S’adr. à l’hô. Mart
43 | tags:
44 | - acme
45 | preconditions:
46 | - org.openrewrite.java.search.FindTypes:
47 | fullyQualifiedTypeName: com.github.jtama.toxic.FooBarUtils
48 | recipeList:
49 | - org.openrewrite.java.ChangeMethodTargetToStatic:
50 | methodPattern: com.github.jtama.toxic.BigDecimalUtils valueOf(..)
51 | fullyQualifiedTargetTypeName: java.math.BigDecimal
52 | ----
53 |
54 | [.notes]
55 | --
56 | On va créer une recette declarative qui permet de supprimer une dépendance toxique.
57 | Et on imagine ici que la dernière recette est accessible ailleurs.
58 |
59 | À la racine d'un projet.
60 | Dans le répertoire `META-INF/rewrite`
61 | Attention, pas `yaml`
62 | Et puis un coup de mvn run
63 | --
64 |
65 | [%notitle.transparency]
66 | === Et on l'exécute comme ça
67 |
68 | image::declarative.jpg[background, size=cover]
69 |
70 | [.fragment]
71 | [source%linenums,console,highlight="1|2..3"]
72 | ----
73 | $ mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
74 | -Drewrite.activeRecipes=\
75 | com.github.jtama.openrewrite.RemovesThatToxicDependency
76 | ----
77 |
78 | [.no-transition.transparency.blur-background]
79 | === Distribution
80 |
81 | image::declarative.jpg[background, size=cover]
82 | Inclure ce fichier dans un module maven...
83 |
84 |
85 | [.columns.transparency.blur-background]
86 | === !
87 |
88 | image::declarative.jpg[background, size=cover]
89 |
90 | [.column.is-one-third]
91 | --
92 | image::magic/kirikou.png[]
93 | --
94 |
95 | [.column]
96 | --
97 | - ✅ Connaître les ingrédients
98 | - ✅ Savoir où les acheter
99 | - ✅ Savoir lire le grimoire
100 | - ✅ Savoir jeter un sort
101 | --
--------------------------------------------------------------------------------
/toxic-library-remover/src/test/java/io/github/jtama/openrewrite/RemoveTryCatchConnectionTest.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.openrewrite.DocumentExample;
5 | import org.openrewrite.java.JavaParser;
6 | import org.openrewrite.test.RecipeSpec;
7 | import org.openrewrite.test.RewriteTest;
8 |
9 | import static org.openrewrite.java.Assertions.java;
10 |
11 | class RemoveTryCatchConnectionTest implements RewriteTest {
12 |
13 | @Override
14 | public void defaults(RecipeSpec spec) {
15 | spec.recipe(new RemoveTryCatchConnection())
16 | .parser(JavaParser.fromJavaVersion()
17 | .logCompilationWarningsAndErrors(true)
18 | .classpath("toxic-library"));
19 |
20 | }
21 |
22 | @DocumentExample
23 | @Test
24 | void removeLogStartInvocations() {
25 | rewriteRun(
26 | //language=java
27 | java(
28 | """
29 | import com.github.jtama.toxic.FooBarUtils;
30 | import java.io.File;
31 | import java.io.FileReader;
32 | import java.nio.file.Files;
33 |
34 | public class ManualGearCar {
35 |
36 | @Deprecated
37 | public void drift(String param) {
38 | try (File file = new File("Tutu.md")) {
39 | String test = FooBarUtils.isEmpty(Files.readString(file.toPath()));
40 | } catch (Exception e) {
41 | throw new RuntimeException(e);
42 | }
43 | }
44 |
45 | public void doSomething(String param) {
46 | // To something
47 | }
48 | }
49 | """,
50 | """
51 | import com.github.jtama.toxic.FooBarUtils;
52 | import java.io.FileReader;
53 | import java.nio.file.Files;
54 | import java.nio.file.Path;
55 |
56 | public class ManualGearCar {
57 |
58 | @Deprecated
59 | public void drift(String param) {
60 | Files.readString(Path.of("Tutu.md"));
61 | }
62 |
63 | public void doSomething(String param) {
64 | // To something
65 | }
66 | }
67 | """));
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/main/java/io/github/jtama/openrewrite/RemoveFooBarUtilsStringFormatted.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package io.github.jtama.openrewrite;
17 |
18 | import org.openrewrite.ExecutionContext;
19 | import org.openrewrite.Preconditions;
20 | import org.openrewrite.Recipe;
21 | import org.openrewrite.TreeVisitor;
22 | import org.openrewrite.internal.ListUtils;
23 | import org.openrewrite.java.JavaIsoVisitor;
24 | import org.openrewrite.java.JavaTemplate;
25 | import org.openrewrite.java.MethodMatcher;
26 | import org.openrewrite.java.search.UsesType;
27 | import org.openrewrite.java.tree.Expression;
28 | import org.openrewrite.java.tree.J;
29 | import org.openrewrite.java.tree.Space;
30 |
31 | import java.util.List;
32 |
33 | public class RemoveFooBarUtilsStringFormatted extends Recipe {
34 |
35 | @Override
36 | public String getDisplayName() {
37 | return "Remove `FooBarUtils.stringFormatted`";
38 | }
39 |
40 | @Override
41 | public String getDescription() {
42 | return "Replace any usage of `FooBarUtils.stringFormatted` with `String.formatted` method.";
43 | }
44 |
45 | @Override
46 | public TreeVisitor, ExecutionContext> getVisitor() {
47 | return new Preconditions.Check(
48 | new UsesType<>("com.github.jtama.toxic.FooBarUtils", true),
49 | new ToStringFormattedVisitor());
50 | }
51 |
52 |
53 | private static class ToStringFormattedVisitor extends JavaIsoVisitor {
54 |
55 | private final MethodMatcher toxicStringFormatted = new MethodMatcher("com.github.jtama.toxic.FooBarUtils stringFormatted(String,..)");
56 | private JavaTemplate stringFormatted = JavaTemplate.builder("#{any(java.lang.String)}.formatted()").build();
57 |
58 | @Override
59 | public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
60 | J.MethodInvocation methodInvocation = super.visitMethodInvocation(method, ctx);
61 | if (!toxicStringFormatted.matches(methodInvocation)) {
62 | return methodInvocation;
63 | }
64 | maybeRemoveImport("com.github.jtama.toxic.FooBarUtils");
65 | List arguments = methodInvocation.getArguments();
66 | J.MethodInvocation mi = stringFormatted.apply(
67 | getCursor(),
68 | methodInvocation.getCoordinates().replace(),
69 | arguments.getFirst());
70 | mi = mi.withArguments(ListUtils.mapFirst(
71 | arguments.subList(1, arguments.size()),
72 | expression -> expression.withPrefix(Space.EMPTY)));
73 | return mi;
74 | }
75 | }
76 |
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/main/java/io/github/jtama/openrewrite/RemoveLogStartInvocations.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite;
2 |
3 | import org.openrewrite.Cursor;
4 | import org.openrewrite.ExecutionContext;
5 | import org.openrewrite.Preconditions;
6 | import org.openrewrite.Recipe;
7 | import org.openrewrite.TreeVisitor;
8 | import org.openrewrite.java.AnnotationMatcher;
9 | import org.openrewrite.java.JavaIsoVisitor;
10 | import org.openrewrite.java.JavaParser;
11 | import org.openrewrite.java.JavaTemplate;
12 | import org.openrewrite.java.MethodMatcher;
13 | import org.openrewrite.java.search.UsesType;
14 | import org.openrewrite.java.tree.J;
15 |
16 | import java.util.Comparator;
17 |
18 | public class RemoveLogStartInvocations extends Recipe {
19 |
20 |
21 | @Override
22 | public String getDisplayName() {
23 | return "Remove `Timer.logStart()` usages";
24 | }
25 |
26 | @Override
27 | public String getDescription() {
28 | return "Replace any usage of `Timer.logStart()` and `Timer.logEnd()` methods by `@Timed` annotation.";
29 | }
30 |
31 | @Override
32 | public boolean causesAnotherCycle() {
33 | return true;
34 | }
35 |
36 | @Override
37 | public TreeVisitor, ExecutionContext> getVisitor() {
38 | return new Preconditions.Check(new UsesType<>("com.github.jtama.toxic.Timer", true),
39 | new ReplaceCompareVisitor());
40 | }
41 |
42 | private static class ReplaceCompareVisitor extends JavaIsoVisitor {
43 |
44 | private final MethodMatcher logStartInvocaMatcher = new MethodMatcher("com.github.jtama.toxic.Timer logStart()");
45 | private final MethodMatcher logEndInvocaMatcher = new MethodMatcher("com.github.jtama.toxic.Timer logEnd()");
46 | private final AnnotationMatcher timedMatcher = new AnnotationMatcher("@io.micrometer.core.annotation.Timed");
47 | private final JavaTemplate annotationTemplate = JavaTemplate.builder("@Timed")
48 | .imports("io.micrometer.core.annotation.Timed")
49 | .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()))
50 | .build();
51 |
52 | public ReplaceCompareVisitor() {
53 | maybeRemoveImport("com.github.jtama.toxic.Timer");
54 | }
55 |
56 | @Override
57 | public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
58 | J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx);
59 | Cursor cursor = getCursor();
60 | if (cursor.getMessage("appendAnnotation", false)) {
61 | if (md.getLeadingAnnotations().stream()
62 | .noneMatch(timedMatcher::matches)) {
63 | maybeAddImport("io.micrometer.core.annotation.Timed");
64 | md = annotationTemplate.apply(cursor, method.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)));
65 | }
66 | }
67 | return md;
68 | }
69 |
70 | @Override
71 | public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
72 | J.MethodInvocation mi = super.visitMethodInvocation(method, ctx);
73 | if (logStartInvocaMatcher.matches(mi) || logEndInvocaMatcher.matches(mi)) {
74 | getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, "appendAnnotation", true);
75 | return null;
76 | }
77 | return mi;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/test/java/io/github/jtama/openrewrite/RemoveFooBarIsEmptyTest.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.openrewrite.DocumentExample;
5 | import org.openrewrite.java.JavaParser;
6 | import org.openrewrite.test.RecipeSpec;
7 | import org.openrewrite.test.RewriteTest;
8 |
9 | import static org.openrewrite.java.Assertions.java;
10 |
11 | class RemoveFooBarIsEmptyTest implements RewriteTest {
12 |
13 | @Override
14 | public void defaults(RecipeSpec spec) {
15 | spec.recipe(new RemoveFooBarUtilsIsEmptyRecipes())
16 | .parser(JavaParser.fromJavaVersion()
17 | .logCompilationWarningsAndErrors(true)
18 | .classpath("toxic-library"));
19 | }
20 |
21 | @DocumentExample
22 | @Test
23 | void removeAllIsEmptyInvocation() {
24 | rewriteRun(
25 | //language=java
26 | java(
27 | """
28 | import static com.github.jtama.toxic.FooBarUtils.isEmpty;
29 |
30 | import java.util.List;
31 |
32 | public class ForTestingPurpose {
33 |
34 | public void virage(String param) {
35 | System.out.printf("On le prend ce virage ? %s", isEmpty(param));
36 | }
37 |
38 | public void auFreinAMain(List values) {
39 | System.out.printf("Au frein à main ? %s", isEmpty(values));
40 | }
41 | }
42 | """,
43 | """
44 | import java.util.List;
45 |
46 | public class ForTestingPurpose {
47 |
48 | public void virage(String param) {
49 | System.out.printf("On le prend ce virage ? %s", param == null || param.isEmpty());
50 | }
51 |
52 | public void auFreinAMain(List values) {
53 | System.out.printf("Au frein à main ? %s", values == null || values.isEmpty());
54 | }
55 | }
56 | """));
57 | }
58 |
59 | @Test
60 | void removeIEmptyInvocation() {
61 | rewriteRun(
62 | spec -> spec.recipe(new RemoveFooBarUtilsIsEmptyRecipes.RemoveListIsEmptyRecipe()),
63 | //language=java
64 | java(
65 | """
66 | import static com.github.jtama.toxic.FooBarUtils.isEmpty;
67 |
68 | import java.util.List;
69 |
70 | public class AutomaticCar {
71 |
72 | public void virage(String param) {
73 | System.out.printf("On le prend ce virage ? %s", isEmpty(param));
74 | }
75 |
76 | public void auFreinAMain(List values) {
77 | System.out.printf("Au frein à main ? %s", isEmpty(values));
78 | }
79 | }
80 | """,
81 | """
82 | import static com.github.jtama.toxic.FooBarUtils.isEmpty;
83 |
84 | import java.util.List;
85 |
86 | public class AutomaticCar {
87 |
88 | public void virage(String param) {
89 | System.out.printf("On le prend ce virage ? %s", isEmpty(param));
90 | }
91 |
92 | public void auFreinAMain(List values) {
93 | System.out.printf("Au frein à main ? %s", values == null || values.isEmpty());
94 | }
95 | }
96 | """));
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/main/java/io/github/jtama/openrewrite/RemoveTryCatchConnection.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite;
2 |
3 | import org.openrewrite.ExecutionContext;
4 | import org.openrewrite.Recipe;
5 | import org.openrewrite.TreeVisitor;
6 | import org.openrewrite.java.JavaTemplate;
7 | import org.openrewrite.java.JavaVisitor;
8 | import org.openrewrite.java.MethodMatcher;
9 | import org.openrewrite.java.tree.Expression;
10 | import org.openrewrite.java.tree.J;
11 | import org.openrewrite.java.tree.Space;
12 |
13 | import java.util.List;
14 |
15 | public class RemoveTryCatchConnection extends Recipe {
16 |
17 | @Override
18 | public String getDisplayName() {
19 | return "Remove `FooBarUtils.compare()` usages";
20 | }
21 |
22 | @Override
23 | public String getDescription() {
24 | return "Replace any usage of `FooBarUtils.compare()` method by `Objects.compare()` invocations.";
25 | }
26 |
27 | @Override
28 | public TreeVisitor, ExecutionContext> getVisitor() {
29 | return new RemoveTryCatchConnection.RemoveTryCatchConnectionVisitor();
30 | }
31 |
32 | public class RemoveTryCatchConnectionVisitor extends JavaVisitor {
33 |
34 | public static final String PATH_PARAM = "pathParam";
35 | public static final String IS_EMPTY_PARAM = "isEmptyParam";
36 | private final MethodMatcher isEmptyInvocaMatcher = new MethodMatcher("com.github.jtama.toxic.FooBarUtils isEmpty(java.lang.String)");
37 | private final MethodMatcher newFileMatcher = new MethodMatcher("java.io.File (..)");
38 | private final JavaTemplate template = JavaTemplate.builder("Path.of(#{any()})")
39 | .imports("java.nio.file.Path").build();
40 |
41 | @Override
42 | public J visitTry(J.Try tr, ExecutionContext executionContext) {
43 | J result = super.visitTry(tr, executionContext);
44 |
45 | if (getCursor().getMessage(PATH_PARAM) != null) {
46 | maybeRemoveImport("java.io.File");
47 | maybeAddImport("java.nio.file.Path");
48 | J.MethodInvocation mi = getCursor().getMessage(IS_EMPTY_PARAM);
49 | List pathParam = getCursor().getMessage(PATH_PARAM);
50 | J.MethodInvocation newMi = template.apply(getCursor(), tr.getCoordinates().replace(), pathParam.getFirst());
51 | newMi = newMi.withPrefix(Space.EMPTY);
52 | mi = mi.withPrefix(tr.getPrefix());
53 | return mi.withArguments(List.of(newMi));
54 | }
55 | return result;
56 | }
57 |
58 | @Override
59 | public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
60 | super.visitMethodInvocation(method, executionContext);
61 | if (isEmptyInvocaMatcher.matches(method)) {
62 | getCursor().putMessageOnFirstEnclosing(J.Try.class, IS_EMPTY_PARAM, method.getArguments().getFirst());
63 | }
64 | return method;
65 | }
66 |
67 | @Override
68 | public J visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext executionContext) {
69 | multiVariable = (J.VariableDeclarations) super.visitVariableDeclarations(multiVariable, executionContext);
70 | multiVariable.getVariables().stream()
71 | .map(J.VariableDeclarations.NamedVariable::getInitializer)
72 | .filter(exp -> newFileMatcher.matches(exp))
73 | .map(exp -> (J.NewClass) exp)
74 | .findFirst()
75 | .ifPresent(newClass -> getCursor().putMessageOnFirstEnclosing(J.Try.class, PATH_PARAM, newClass.getArguments()));
76 | return multiVariable;
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/toxic-library-user/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.github.jtama
8 | toxic-library-user
9 | 1.0.1-SNAPSHOT
10 |
11 |
12 | 25
13 | 25
14 | UTF-8
15 | 3.8.1
16 | 3.17.3
17 | 3.5.2
18 | 19.666.45-RC18-FINAL
19 |
20 |
21 |
22 |
23 | io.quarkus
24 | quarkus-bom
25 | ${quarkus.version}
26 | pom
27 | import
28 |
29 |
30 |
31 |
32 |
33 | com.github.jtama
34 | toxic-library
35 | ${toxic.version}
36 |
37 |
38 | io.quarkus
39 | quarkus-resteasy-jsonb
40 |
41 |
42 | io.quarkus
43 | quarkus-micrometer-registry-prometheus
44 |
45 |
46 | io.quarkus
47 | quarkus-security
48 |
49 |
50 | io.quarkus
51 | quarkus-hibernate-orm-panache
52 |
53 |
54 | io.quarkus
55 | quarkus-hibernate-validator
56 |
57 |
58 | io.quarkus
59 | quarkus-jdbc-postgresql
60 |
61 |
62 | junit
63 | junit
64 | 4.13.2
65 | test
66 |
67 |
68 | org.mockito
69 | mockito-inline
70 | 4.0.0
71 | test
72 |
73 |
74 |
75 |
76 |
77 | io.quarkus
78 | quarkus-maven-plugin
79 | ${quarkus.version}
80 | true
81 |
82 |
83 |
84 | build
85 | generate-code
86 | generate-code-tests
87 |
88 |
89 |
90 |
91 |
92 | maven-compiler-plugin
93 | ${compiler-plugin.version}
94 |
95 |
96 | maven-surefire-plugin
97 | ${surefire-plugin.version}
98 |
99 |
100 | org.jboss.logmanager.LogManager
101 | ${maven.home}
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/slides/css/meetup.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Poppins");
2 |
3 |
4 | :root {
5 | --r-background-color: #EEEEEE;
6 | --r-main-color: #111012;
7 | --r-main-font: 'Poppins';
8 | --r-heading-color: #8FAADC;
9 | --r-link-color: #8FAADC;
10 | }
11 |
12 | body {
13 | /* Footer styles for devfest-logo if body has .title */
14 |
15 | &.title footer:has(.devfest-logo) {
16 | z-index: 10;
17 | position: absolute;
18 | left: 35%;
19 | bottom: 0;
20 | min-height: 80px;
21 | text-align: center;
22 | width: 600px;
23 | }
24 |
25 | /* Footer styles for onepoint-logo if body does not have .present.demo or .title */
26 |
27 | &:not(:has(.present.demo)):not(.title) footer:has(.onepoint-logo) {
28 | z-index: 10;
29 | position: absolute;
30 | left: 0;
31 | width: 100%;
32 | bottom: 0;
33 | min-height: 80px;
34 | text-align: center;
35 | }
36 |
37 | .reveal {
38 | h1 {
39 | --r-heading1-size: 82px;
40 | color: #000000;
41 | position: fixed;
42 | left: 28px;
43 | width: 100%;
44 | text-align: center;
45 | /*text-shadow: -1px 0 #8FAADC, 0 1px #8FAADC, 1px 0 #8FAADC, 0 -1px #8FAADC;*/
46 | }
47 |
48 | .title.present h2 {
49 | position: fixed;
50 | top: 208px;
51 | left: 630px;
52 | width: 100%;
53 | font-family: var(--r-heading-font);
54 | text-shadow: -1px 0 #000000, 0 1px #000000, 1px 0 #000000, 0 -1px #000000;
55 | /*color: #8FAADC;*/
56 | text-transform: none;
57 | }
58 |
59 | .to-the-bottom {
60 | position: absolute;
61 | left: 50%;
62 | bottom: 20px;
63 | transform: translate(-50%, -50%);
64 | margin: 0 auto;
65 | }
66 |
67 | h2 {
68 | --r-heading-color: #000000;
69 | /*text-shadow: -1px 0 #8FAADC, 0 1px #8FAADC, 1px 0 #8FAADC, 0 -1px #8FAADC;*/
70 | position: fixed;
71 | left: 50%;
72 | top: 6px;
73 | transform: translateX(-50%);
74 | width: 100%;
75 | }
76 |
77 | ul {
78 | list-style-type: none;
79 | }
80 |
81 | .progress {
82 | color: #241F35;
83 | height: 7px;
84 | }
85 |
86 | .blur-background {
87 | border-radius: 10px;
88 | background-color: rgba(255, 255, 255, 0.5);
89 | }
90 | }
91 |
92 | .transparency.slide-background.present {
93 | opacity: 0.40;
94 | }
95 |
96 | .big {
97 | font-size: 60px;
98 | color: #012d3f;
99 | }
100 |
101 | img {
102 | border-radius: 10px;
103 | }
104 |
105 | .vertical-align-middle > p,
106 | .vertical-align-middle > p > span,
107 | .vertical-align-middle > p > span > img {
108 | vertical-align: middle;
109 | margin: 0;
110 | }
111 |
112 | .important-text > * {
113 | font-size: xxx-large;
114 | font-weight: bold;
115 | color: #012d3f;
116 | }
117 |
118 | .small {
119 | font-size: small;
120 | }
121 |
122 | .medium {
123 | font-size: 38px;
124 | }
125 |
126 | .column.has-text-right > div > div.hdlist.small > table {
127 | margin-right: 0;
128 | }
129 |
130 | .column.has-text-left > div > div.hdlist.small > table {
131 | margin-left: 0;
132 | }
133 |
134 | .blur {
135 | filter: blur(4px);
136 | }
137 |
138 | .chercher_remplacer {
139 | position: fixed;
140 | top: 125px;
141 | left: 821px;
142 | }
143 |
144 | .regexp {
145 | position: fixed;
146 | top: 125px;
147 | left: 197px;
148 | }
149 |
150 | .llm {
151 | position: fixed;
152 | top: 355px;
153 | left: 197px;
154 | }
155 |
156 | .click_droit {
157 | position: fixed;
158 | top: 355px;
159 | left: 821px;
160 | overflow: hidden;
161 | }
162 |
163 | li {
164 | list-style-type: none;
165 | }
166 |
167 | audio {
168 | display: none;
169 | }
170 |
171 | .down {
172 | position: fixed;
173 | bottom: 0;
174 | right: 0;
175 | }
176 |
177 | .easter-egg {
178 | position: absolute;
179 | right: 380px;
180 | bottom: 110px;
181 | animation: easteregg 5s ease 0s 1 normal forwards;
182 | }
183 |
184 | .easter-egg::after {
185 | content: "#dobbycestlemeilleur";
186 | }
187 | }
188 |
189 | @keyframes easteregg {
190 | 0% {
191 | animation-timing-function: ease-in;
192 | opacity: 0;
193 | transform: scale(0) translateY(-45px) translateX(-80px) rotate(-75deg);
194 | }
195 |
196 | 100% {
197 | animation-timing-function: ease-out;
198 | opacity: 1;
199 | transform: scale(0.5) rotate(-25deg) translateY(0px) translateX(600px);
200 | }
201 | }
--------------------------------------------------------------------------------
/toxic-library-remover/src/main/java/io/github/jtama/openrewrite/UseObjectsCompare.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.openrewrite.ExecutionContext;
5 | import org.openrewrite.Preconditions;
6 | import org.openrewrite.Recipe;
7 | import org.openrewrite.TreeVisitor;
8 | import org.openrewrite.java.JavaIsoVisitor;
9 | import org.openrewrite.java.JavaParser;
10 | import org.openrewrite.java.JavaTemplate;
11 | import org.openrewrite.java.MethodMatcher;
12 | import org.openrewrite.java.search.UsesType;
13 | import org.openrewrite.java.tree.Comment;
14 | import org.openrewrite.java.tree.J;
15 | import org.openrewrite.java.tree.TextComment;
16 | import org.openrewrite.marker.Markers;
17 |
18 | import java.util.List;
19 |
20 | public class UseObjectsCompare extends Recipe {
21 |
22 |
23 | @Override
24 | public String getDisplayName() {
25 | return "Remove `FooBarUtils.compare()` usages";
26 | }
27 |
28 | @Override
29 | public String getDescription() {
30 | return "Replace any usage of `FooBarUtils.compare()` method by `Objects.compare()` invocations.";
31 | }
32 |
33 | @Override
34 | public TreeVisitor, ExecutionContext> getVisitor() {
35 | return new Preconditions.Check(new UsesType<>("com.github.jtama.toxic.FooBarUtils", true),
36 | new ReplaceCompareVisitor());
37 | }
38 |
39 | private static class ReplaceCompareVisitor extends JavaIsoVisitor {
40 |
41 | private final MethodMatcher compareMethodMatcher = new MethodMatcher("com.github.jtama.toxic.FooBarUtils compare(..)");
42 | private final JavaTemplate objectsCompareTemplate = JavaTemplate.builder("Objects.compare(#{any(java.lang.Object)}, #{any(java.lang.Object)}, #{any(java.util.Comparator)})")
43 | .imports("java.util.Objects")
44 | .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()))
45 | .build();
46 |
47 | public ReplaceCompareVisitor() {
48 | maybeRemoveImport("com.github.jtama.toxic.FooBarUtils");
49 | }
50 |
51 | @Override
52 | public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
53 | J.MethodInvocation mi = super.visitMethodInvocation(method, ctx);
54 | if (compareMethodMatcher.matches(mi)) {
55 | maybeAddImport("java.util.Objects");
56 | mi = objectsCompareTemplate.apply(getCursor(), mi.getCoordinates().replace(),
57 | mi.getArguments().get(0), mi.getArguments().get(1), mi.getArguments().get(2));
58 | String comment = "Comparing %s using %s".formatted(
59 | mi.getArguments().get(0).getType().toString(),
60 | mi.getArguments().get(2).getType().toString());
61 | if (getCursor().getParent().getParent().getValue() instanceof J.Block) {
62 | mi = mi.withComments(getComment(comment, mi));
63 | } else {
64 | getCursor().dropParentUntil(this::isAcceptable).putMessage("comment", comment);
65 | }
66 | }
67 | return mi;
68 | }
69 |
70 | @Override
71 | public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext executionContext) {
72 | J.Assignment visitedAssignment = super.visitAssignment(assignment, executionContext);
73 | String comment = getCursor().getMessage("comment", "");
74 | if (!comment.isEmpty() && visitedAssignment.getComments().isEmpty()) {
75 | visitedAssignment = visitedAssignment.withComments(getComment(comment, visitedAssignment));
76 | getCursor().clearMessages();
77 | }
78 | return visitedAssignment;
79 | }
80 |
81 | @Override
82 | public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext executionContext) {
83 | J.VariableDeclarations visitVariableDeclarations = super.visitVariableDeclarations(multiVariable, executionContext);
84 | String comment = getCursor().getMessage("comment", "");
85 | if (!comment.isEmpty() && visitVariableDeclarations.getComments().isEmpty()) {
86 | visitVariableDeclarations = visitVariableDeclarations.withComments(getComment(comment, visitVariableDeclarations));
87 | getCursor().clearMessages();
88 | }
89 | return visitVariableDeclarations;
90 | }
91 |
92 | @Override
93 | public J.Return visitReturn(J.Return _return, ExecutionContext executionContext) {
94 | J.Return visitedReturn = super.visitReturn(_return, executionContext);
95 | String comment = getCursor().getMessage("comment", "");
96 | if (!comment.isEmpty() && visitedReturn.getComments().isEmpty()) {
97 | visitedReturn = visitedReturn.withComments(getComment(comment, visitedReturn));
98 | getCursor().clearMessages();
99 | }
100 | return visitedReturn;
101 | }
102 |
103 | private @NotNull List getComment(String commment, J j) {
104 | return List.of(new TextComment(false, commment, j.getPrefix().getWhitespace(), Markers.EMPTY));
105 | }
106 |
107 | private boolean isAcceptable(Object j) {
108 | return switch (j) {
109 | case J.VariableDeclarations v -> true;
110 | case J.Return r -> true;
111 | case J.Assignment a -> true;
112 | default -> false;
113 | };
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/toxic-library-remover/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 | com.github.jtama
7 | toxic-library-remover
8 | 1.0.1-SNAPSHOT
9 |
10 |
11 | UTF-8
12 | UTF-8
13 | 21
14 | 21
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | org.openrewrite.recipe
23 | rewrite-recipe-bom
24 | 3.16.0
25 | pom
26 | import
27 |
28 |
29 | org.junit
30 | junit-bom
31 | 5.13.0-M2
32 | pom
33 | import
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | org.openrewrite
43 | rewrite-templating
44 |
45 |
46 | javax.annotation
47 | javax.annotation-api
48 | 1.3.2
49 |
50 |
51 |
52 |
53 | org.openrewrite.meta
54 | rewrite-analysis
55 |
56 |
57 | org.openrewrite
58 | rewrite-java
59 |
60 |
61 | org.projectlombok
62 | lombok
63 |
64 |
65 |
66 |
67 | org.openrewrite.recipe
68 | rewrite-migrate-java
69 |
70 |
71 | org.openrewrite.recipe
72 | rewrite-static-analysis
73 |
74 |
75 | io.quarkus
76 | quarkus-micrometer-registry-prometheus
77 | 3.26.3
78 |
79 |
80 |
81 |
82 |
83 | com.google.errorprone
84 | error_prone_core
85 | 2.42.0
86 | provided
87 | true
88 |
89 |
90 | com.google.auto.service
91 | auto-service-annotations
92 |
93 |
94 |
95 |
96 |
97 | com.github.jtama
98 | toxic-library
99 | 19.666.45-RC18-FINAL
100 |
101 |
102 |
103 | org.openrewrite
104 | rewrite-test
105 | test
106 |
107 |
108 |
109 | org.junit.jupiter
110 | junit-jupiter
111 | test
112 |
113 |
114 |
115 | org.assertj
116 | assertj-core
117 | 4.0.0-M1
118 | test
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | org.apache.maven.plugins
127 | maven-compiler-plugin
128 | 3.13.0
129 |
130 | 21
131 |
132 | -parameters
133 |
134 |
135 |
136 | org.openrewrite
137 | rewrite-templating
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/test/java/io/github/jtama/openrewrite/RemoveFooBarCompareTest.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.openrewrite.DocumentExample;
5 | import org.openrewrite.java.JavaParser;
6 | import org.openrewrite.test.RecipeSpec;
7 | import org.openrewrite.test.RewriteTest;
8 |
9 | import static org.openrewrite.java.Assertions.java;
10 |
11 | class RemoveFooBarCompareTest implements RewriteTest {
12 |
13 | @Override
14 | public void defaults(RecipeSpec spec) {
15 | spec.recipe(new UseObjectsCompare())
16 | .parser(JavaParser.fromJavaVersion()
17 | .logCompilationWarningsAndErrors(true)
18 | .classpath("toxic-library"));
19 | }
20 |
21 | @DocumentExample
22 | @Test
23 | void replaceAndCommentDeclaration() {
24 | rewriteRun(
25 | //language=java
26 | java(
27 | """
28 | import com.github.jtama.toxic.FooBarUtils;
29 |
30 | public class ManualGearCar {
31 | private static final FooBarUtils UTILS = new FooBarUtils();
32 |
33 | public void virage(String param) {
34 | int val = UTILS.compare(param,
35 | "au frein à main ?",
36 | (o1,o2) -> o1.compareTo(o2));
37 | }
38 | }
39 | """,
40 | """
41 | import com.github.jtama.toxic.FooBarUtils;
42 |
43 | import java.util.Objects;
44 |
45 | public class ManualGearCar {
46 | private static final FooBarUtils UTILS = new FooBarUtils();
47 |
48 | public void virage(String param) {
49 | //Comparing java.lang.String using java.util.Comparator
50 | int val = Objects.compare(param, "au frein à main ?", (o1, o2) -> o1.compareTo(o2));
51 | }
52 | }
53 | """));
54 | }
55 |
56 | @Test
57 | void replaceAndCommentAssignment() {
58 | rewriteRun(
59 | //language=java
60 | java(
61 | """
62 | import com.github.jtama.toxic.FooBarUtils;
63 |
64 | public class ManualGearCar {
65 | private static final FooBarUtils UTILS = new FooBarUtils();
66 |
67 | public void virage(String param) {
68 | int val;
69 | val = UTILS.compare(param,
70 | "au frein à main ?",
71 | (o1,o2) -> o1.compareTo(o2));
72 | }
73 | }
74 | """,
75 | """
76 | import com.github.jtama.toxic.FooBarUtils;
77 |
78 | import java.util.Objects;
79 |
80 | public class ManualGearCar {
81 | private static final FooBarUtils UTILS = new FooBarUtils();
82 |
83 | public void virage(String param) {
84 | int val;
85 | //Comparing java.lang.String using java.util.Comparator
86 | val = Objects.compare(param, "au frein à main ?", (o1, o2) -> o1.compareTo(o2));
87 | }
88 | }
89 | """));
90 | }
91 |
92 | @Test
93 | void replaceAndCommentReturn() {
94 | rewriteRun(
95 | //language=java
96 | java(
97 | """
98 | import com.github.jtama.toxic.FooBarUtils;
99 |
100 | public class ManualGearCar {
101 | private static final FooBarUtils UTILS = new FooBarUtils();
102 |
103 | public int virage(String param) {
104 | return UTILS.compare(param,
105 | "au frein à main ?",
106 | (o1,o2) -> o1.compareTo(o2));
107 | }
108 | }
109 | """,
110 | """
111 | import com.github.jtama.toxic.FooBarUtils;
112 |
113 | import java.util.Objects;
114 |
115 | public class ManualGearCar {
116 | private static final FooBarUtils UTILS = new FooBarUtils();
117 |
118 | public int virage(String param) {
119 | //Comparing java.lang.String using java.util.Comparator
120 | return Objects.compare(param, "au frein à main ?", (o1, o2) -> o1.compareTo(o2));
121 | }
122 | }
123 | """));
124 | }
125 |
126 | @Test
127 | void replaceAndCommentInvocations() {
128 | rewriteRun(
129 | //language=java
130 | java(
131 | """
132 | import com.github.jtama.toxic.FooBarUtils;
133 |
134 | public class ManualGearCar {
135 | private static final FooBarUtils UTILS = new FooBarUtils();
136 |
137 | public void virage(String param) {
138 | UTILS.compare(param,
139 | "au frein à main ?",
140 | (o1,o2) -> o1.compareTo(o2));
141 | }
142 | }
143 | """,
144 | """
145 | import com.github.jtama.toxic.FooBarUtils;
146 |
147 | import java.util.Objects;
148 |
149 | public class ManualGearCar {
150 | private static final FooBarUtils UTILS = new FooBarUtils();
151 |
152 | public void virage(String param) {
153 | //Comparing java.lang.String using java.util.Comparator
154 | Objects.compare(param, "au frein à main ?", (o1, o2) -> o1.compareTo(o2));
155 | }
156 | }
157 | """));
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/test/java/io/github/jtama/openrewrite/ExtractInterfaceTest.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.openrewrite.DocumentExample;
5 | import org.openrewrite.java.JavaParser;
6 | import org.openrewrite.test.RecipeSpec;
7 | import org.openrewrite.test.RewriteTest;
8 |
9 | import static org.openrewrite.java.Assertions.java;
10 |
11 | class ExtractInterfaceTest implements RewriteTest {
12 |
13 | @Override
14 | public void defaults(RecipeSpec spec) {
15 | spec.recipe(new ExtractInterface("com.github.jtama.toxic.LearnToFly"))
16 | .parser(JavaParser.fromJavaVersion()
17 | .logCompilationWarningsAndErrors(true)
18 | .classpath("toxic-library"))
19 | .cycles(3);
20 |
21 | }
22 |
23 | @DocumentExample
24 | @Test
25 | void shouldExtractInterfaceParameterized() {
26 | rewriteRun(
27 | //language=java
28 | java(
29 | """
30 | package tutu;
31 |
32 | import com.github.jtama.toxic.FooBarUtils;
33 | import com.github.jtama.toxic.LearnToFly;
34 | import java.io.File;
35 | import java.nio.file.Files;
36 |
37 | @LearnToFly
38 | public class ManualGearCar implements Comparable {
39 |
40 | protected Integer tutu;
41 |
42 | @Deprecated
43 | public void drift(String param) {
44 | try (File file = new File("Tutu.md")) {
45 | String test = FooBarUtils.isEmpty(Files.readString(file.toPath()));
46 | } catch (Exception e) {
47 | throw new RuntimeException(e);
48 | }
49 | }
50 |
51 | public void doSomething(String param) {
52 | // To something
53 | }
54 |
55 | @Override
56 | public int compareTo(Integer o) {
57 | return 0;
58 | }
59 | }
60 | """,
61 | """
62 | package tutu;
63 |
64 | import com.github.jtama.toxic.FooBarUtils;
65 | import java.io.File;
66 | import java.nio.file.Files;
67 |
68 | public class ManualGearCar implements IManualGearCar {
69 |
70 | protected Integer tutu;
71 |
72 | @Override
73 | public void drift(String param) {
74 | try (File file = new File("Tutu.md")) {
75 | String test = FooBarUtils.isEmpty(Files.readString(file.toPath()));
76 | } catch (Exception e) {
77 | throw new RuntimeException(e);
78 | }
79 | }
80 |
81 | @Override
82 | public void doSomething(String param) {
83 | // To something
84 | }
85 |
86 | @Override
87 | public int compareTo(Integer o) {
88 | return 0;
89 | }
90 | }
91 | """),
92 | //language=java
93 | java(null, """
94 | package tutu;
95 |
96 | import com.github.jtama.toxic.LearnToFly;
97 |
98 | @LearnToFly
99 | public interface IManualGearCar extends Comparable {
100 |
101 | @Deprecated void drift(String param);
102 |
103 | void doSomething(String param);
104 |
105 | @Override int compareTo(Integer o);
106 | }
107 | """
108 | )
109 | );
110 | }
111 |
112 | @DocumentExample
113 | @Test
114 | void shouldExtractInterfaceType() {
115 | rewriteRun(
116 | //language=java
117 | java(
118 | """
119 | package tutu;
120 |
121 | import com.github.jtama.toxic.FooBarUtils;
122 | import com.github.jtama.toxic.LearnToFly;
123 | import java.io.File;
124 | import java.nio.file.Files;
125 |
126 | @LearnToFly
127 | public class ManualGearCar {
128 |
129 | @Deprecated
130 | public void drift(String param) {
131 | try (File file = new File("Tutu.md")) {
132 | String test = FooBarUtils.isEmpty(Files.readString(file.toPath()));
133 | } catch (Exception e) {
134 | throw new RuntimeException(e);
135 | }
136 | }
137 |
138 | public void doSomething(String param) {
139 | // To something
140 | }
141 | }
142 | """,
143 | """
144 | package tutu;
145 |
146 | import com.github.jtama.toxic.FooBarUtils;
147 | import java.io.File;
148 | import java.nio.file.Files;
149 |
150 | public class ManualGearCar implements IManualGearCar {
151 |
152 | @Override
153 | public void drift(String param) {
154 | try (File file = new File("Tutu.md")) {
155 | String test = FooBarUtils.isEmpty(Files.readString(file.toPath()));
156 | } catch (Exception e) {
157 | throw new RuntimeException(e);
158 | }
159 | }
160 |
161 | @Override
162 | public void doSomething(String param) {
163 | // To something
164 | }
165 | }
166 | """),
167 | //language=java
168 | java(null, """
169 | package tutu;
170 |
171 | import com.github.jtama.toxic.LearnToFly;
172 |
173 | @LearnToFly
174 | public interface IManualGearCar {
175 |
176 | @Deprecated void drift(String param);
177 |
178 | void doSomething(String param);
179 | }
180 | """
181 | )
182 | );
183 | }
184 |
185 | @DocumentExample
186 | @Test
187 | void shouldNotExtractInterface() {
188 | rewriteRun(
189 | //language=java
190 | java(
191 | """
192 | package tutu;
193 |
194 | import com.github.jtama.toxic.FooBarUtils;
195 | import java.io.File;
196 | import java.nio.file.Files;
197 |
198 | public class ManualGearCur {
199 |
200 | @Deprecated
201 | public void druft(String param) {
202 | try (File file = new File("Tutu.md")) {
203 | String test = FooBarUtils.isEmpty(Files.readString(file.toPath()));
204 | } catch (Exception e) {
205 | throw new RuntimeException(e);
206 | }
207 | }
208 |
209 | public void doSumething(String param) {
210 | // To something
211 | }
212 | }
213 | """)
214 | );
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/slides/index-docinfo-footer.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
16 |
--------------------------------------------------------------------------------
/toxic-library-remover/src/main/java/io/github/jtama/openrewrite/ExtractInterface.java:
--------------------------------------------------------------------------------
1 | package io.github.jtama.openrewrite;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import org.openrewrite.Cursor;
6 | import org.openrewrite.ExecutionContext;
7 | import org.openrewrite.Option;
8 | import org.openrewrite.Recipe;
9 | import org.openrewrite.ScanningRecipe;
10 | import org.openrewrite.SourceFile;
11 | import org.openrewrite.Tree;
12 | import org.openrewrite.TreeVisitor;
13 | import org.openrewrite.java.AnnotationMatcher;
14 | import org.openrewrite.java.JavaIsoVisitor;
15 | import org.openrewrite.java.JavaTemplate;
16 | import org.openrewrite.java.RemoveUnusedImports;
17 | import org.openrewrite.java.tree.J;
18 | import org.openrewrite.java.tree.JavaSourceFile;
19 | import org.openrewrite.java.tree.JavaType;
20 | import org.openrewrite.java.tree.Space;
21 | import org.openrewrite.java.tree.TypeTree;
22 |
23 | import java.nio.file.Path;
24 | import java.util.ArrayList;
25 | import java.util.Collection;
26 | import java.util.HashMap;
27 | import java.util.List;
28 | import java.util.Map;
29 | import java.util.UUID;
30 | import java.util.function.BiPredicate;
31 | import java.util.function.Predicate;
32 | import java.util.regex.Pattern;
33 |
34 | import static java.util.Collections.emptyList;
35 |
36 | public class ExtractInterface extends ScanningRecipe {
37 |
38 | @Option(displayName = "The targeted annotation", description = "Interface will be extracted for each class marked by this annotation", example = "jakarta.inject.Singleton")
39 | String targetAnnotation;
40 |
41 |
42 | @JsonCreator
43 | public ExtractInterface(@JsonProperty("targetAnnotation") String targetAnnotation) {
44 | this.targetAnnotation = targetAnnotation;
45 | }
46 |
47 | @Override
48 | public String getDisplayName() {
49 | return "Extract controller interface";
50 | }
51 |
52 | @Override
53 | public String getDescription() {
54 | return "Extract controller interface.";
55 | }
56 |
57 | @Override
58 | public Accumulator getInitialValue(ExecutionContext ctx) {
59 | return new Accumulator();
60 | }
61 |
62 | @Override
63 | public List getRecipeList() {
64 | return List.of(new RemoveUnusedImports());
65 | }
66 |
67 | @Override
68 | public TreeVisitor, ExecutionContext> getScanner(Accumulator acc) {
69 | return new TreeVisitor<>() {
70 | final Predicate isClass =
71 | aClass -> aClass.getKind().equals(J.ClassDeclaration.Kind.Type.Class);
72 | final BiPredicate, Accumulator.ToExtract> alreadyImplementsInterface = (implementList, toExtract) -> implementList != null && implementList.stream().anyMatch(tt ->
73 | switch (tt) {
74 | case J.Identifier identifier ->
75 | identifier.getSimpleName().equals(toExtract.extractedInterfaceName());
76 | case J.ParameterizedType parameterized ->
77 | parameterized.getType().isAssignableFrom(Pattern.compile(toExtract.extractedInterfaceName()));
78 | default -> false;
79 | }
80 | );
81 | final AnnotationMatcher matcher = new AnnotationMatcher(targetAnnotation);
82 |
83 | @Override
84 | public Tree visit(Tree tree, ExecutionContext executionContext, Cursor parent) {
85 | if (tree instanceof JavaSourceFile javaSourceFile) {
86 | javaSourceFile.getClasses().stream().filter(isClass).forEach(classDecla -> {
87 | String newFQDN = getNewFQDN(classDecla);
88 | List implementList = classDecla.getImplements();
89 | Accumulator.ToExtract toExtract = new Accumulator.ToExtract(newFQDN);
90 | toExtract.setFromSourceFile(javaSourceFile);
91 | if (!alreadyImplementsInterface.test(implementList, toExtract) && classDecla.getLeadingAnnotations().stream().anyMatch(ann -> matcher.matches(ann))) {
92 | toExtract.setClassToExtract(classDecla);
93 | acc.duplicates().put(newFQDN, toExtract);
94 | }
95 | });
96 |
97 | }
98 | return super.visit(tree, executionContext, parent);
99 | }
100 | };
101 | }
102 |
103 | public Collection extends SourceFile> generate(Accumulator acc, ExecutionContext ctx) {
104 | if (acc.duplicates.isEmpty()) {
105 | return List.of();
106 | }
107 | return acc.duplicates().values().stream()
108 | .map(item -> mapToSourceFile(item, ctx))
109 | .toList();
110 | }
111 |
112 | private J.CompilationUnit mapToSourceFile(Accumulator.ToExtract toExtract, ExecutionContext ctx) {
113 | String initPath = toExtract.fromSourceFile().getSourcePath().toString();
114 | var newPath = Path.of(initPath.substring(0, initPath.lastIndexOf("/")), toExtract.extractedInterfaceName() + ".java");
115 | toExtract.setExtractedInterfacePath(newPath);
116 | J.CompilationUnit extractedInterface = toExtract.fromSourceFile()
117 | .withClasses(List.of(getExtractedInterface(toExtract, ctx)))
118 | .withSourcePath(newPath)
119 | .withId(UUID.randomUUID());
120 | return (J.CompilationUnit) new RemoveUnusedImports().getVisitor().visit(extractedInterface, ctx);
121 | }
122 |
123 | private static String getNewFQDN(J.ClassDeclaration classDeclaration) {
124 | String packageDeclaration = classDeclaration.getType() != null ?
125 | classDeclaration.getType().getPackageName() :
126 | "";
127 | return "%s.I%s".formatted(packageDeclaration, classDeclaration.getName().getSimpleName());
128 | }
129 |
130 | private J.ClassDeclaration getExtractedInterface(Accumulator.ToExtract toExtract, ExecutionContext ctx) {
131 | J.ClassDeclaration initial = toExtract.classToExtract();
132 | J.ClassDeclaration result = initial
133 | .withKind(J.ClassDeclaration.Kind.Type.Interface)
134 | .withName(TypeTree.build(toExtract.extractedInterfaceName()).withPrefix(Space.SINGLE_SPACE))
135 | .withType(new JavaType.ShallowClass(null, 1, toExtract.extractedInterfaceFQDN(), JavaType.FullyQualified.Kind.Interface, emptyList(), null, null, emptyList(), emptyList(), emptyList(), emptyList()))
136 | .withId(UUID.randomUUID())
137 | .withLeadingAnnotations(new ArrayList<>(initial.getLeadingAnnotations()));
138 | result = (J.ClassDeclaration) result.acceptJava(new CleanerVisitor(), ctx);
139 | toExtract.setExtractedInterface(result);
140 | return result;
141 |
142 | }
143 |
144 | @Override
145 | public TreeVisitor, ExecutionContext> getVisitor(Accumulator acc) {
146 | return new JavaIsoVisitor<>() {
147 |
148 | AnnotationMatcher targetAnnotationMatcher = new AnnotationMatcher(targetAnnotation);
149 | public static final String TARGET_CLASS = "TARGET";
150 |
151 | @Override
152 | public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration cd, ExecutionContext executionContext) {
153 | Accumulator.ToExtract target = acc.duplicates.get(getNewFQDN(cd));
154 | if (target != null) {
155 | getCursor().putMessage(TARGET_CLASS, target);
156 | var annotations = cd.getLeadingAnnotations();
157 | annotations.forEach(ann -> maybeRemoveImport(ann.getSimpleName()));
158 | cd = cd.withLeadingAnnotations(List.of())
159 | .withImplements(List.of(TypeTree.build(target.extractedInterfaceName())
160 | .withType(target.extractedInterface().getType())
161 | .withPrefix(Space.SINGLE_SPACE)))
162 | .withPrefix(Space.format(System.lineSeparator()));
163 | if (cd.getPadding().getImplements().getBefore().getWhitespace().isEmpty()) {
164 | cd = cd.getPadding().withImplements(cd.getPadding().getImplements().withBefore(Space.SINGLE_SPACE));
165 | }
166 | }
167 | cd = super.visitClassDeclaration(cd, executionContext);
168 | return cd;
169 | }
170 |
171 | @Override
172 | public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) {
173 | if (getCursor().getNearestMessage(TARGET_CLASS) != null) {
174 | return JavaTemplate.builder("@Override").build().apply(getCursor(), method.getCoordinates().replaceAnnotations());
175 | }
176 | return super.visitMethodDeclaration(method, executionContext);
177 | }
178 | };
179 | }
180 |
181 | static class CleanerVisitor extends JavaIsoVisitor {
182 |
183 | @Override
184 | public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) {
185 | return method
186 | .withBody(null)
187 | .withModifiers(List.of())
188 | .withReturnTypeExpression(!method.getLeadingAnnotations().isEmpty() ? method.getReturnTypeExpression().withPrefix(Space.SINGLE_SPACE) : method.getReturnTypeExpression().withPrefix(Space.EMPTY));
189 | }
190 |
191 | @Override
192 | public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext executionContext) {
193 | return null;
194 | }
195 | }
196 |
197 | public static class Accumulator {
198 |
199 | private final Map duplicates = new HashMap<>();
200 |
201 | public Map duplicates() {
202 | return duplicates;
203 | }
204 |
205 | public static class ToExtract {
206 | // The contents of the file we want to extract
207 | JavaSourceFile fromSourceFile;
208 | J.ClassDeclaration classToExtract;
209 | J.ClassDeclaration extractedInterface;
210 | Path extractedInterfacePath;
211 | String extractedInterfaceFQDN;
212 |
213 | public ToExtract(String extractedInterfaceFQDN) {
214 | this.extractedInterfaceFQDN = extractedInterfaceFQDN;
215 | }
216 |
217 | public J.ClassDeclaration extractedInterface() {
218 | return extractedInterface;
219 | }
220 |
221 | public void setExtractedInterface(J.ClassDeclaration extractedInterface) {
222 | this.extractedInterface = extractedInterface;
223 | }
224 |
225 | public Path extractedInterfacePath() {
226 | return extractedInterfacePath;
227 | }
228 |
229 | public JavaSourceFile fromSourceFile() {
230 | return fromSourceFile;
231 | }
232 |
233 | public void setFromSourceFile(JavaSourceFile fromSourceFile) {
234 | this.fromSourceFile = fromSourceFile;
235 | }
236 |
237 | public void setExtractedInterfacePath(Path extractedInterfacePath) {
238 | this.extractedInterfacePath = extractedInterfacePath;
239 | }
240 |
241 | public String extractedInterfaceFQDN() {
242 | return extractedInterfaceFQDN;
243 | }
244 |
245 | public String extractedInterfaceName() {
246 | int lastIndexOfDot = extractedInterfaceFQDN.lastIndexOf(".") + 1;
247 | return lastIndexOfDot > 0 ? extractedInterfaceFQDN.substring(lastIndexOfDot) : extractedInterfaceFQDN;
248 | }
249 |
250 | public J.ClassDeclaration classToExtract() {
251 | return classToExtract;
252 | }
253 |
254 | public void setClassToExtract(J.ClassDeclaration sourceFileToDuplicate) {
255 | this.classToExtract = sourceFileToDuplicate;
256 | }
257 | }
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/toxic-library-user/src/test/java/com/github/jtama/app/hostels/HostelReservationServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.hostels;
2 |
3 | import com.github.jtama.app.exception.UnavailableException;
4 | import com.github.jtama.app.exception.UnknownEntityException;
5 | import com.github.jtama.app.reservation.Reservation;
6 | import com.github.jtama.app.util.MonthValidator;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.InjectMocks;
11 | import org.mockito.Mock;
12 | import org.mockito.MockedStatic;
13 | import org.mockito.junit.MockitoJUnitRunner;
14 |
15 | import java.time.DateTimeException;
16 | import java.util.Optional;
17 |
18 | import static org.junit.Assert.*;
19 | import static org.mockito.ArgumentMatchers.*;
20 | import static org.mockito.Mockito.*;
21 |
22 | @RunWith(MockitoJUnitRunner.class)
23 | public class HostelReservationServiceTest {
24 |
25 | @Mock
26 | private MonthValidator monthValidator;
27 |
28 | @InjectMocks
29 | private HostelReservationService hostelReservationService;
30 |
31 | private static final String VALID_HOSTEL_NAME = "test-hostel";
32 | private static final String INVALID_HOSTEL_NAME = "nonexistent-hostel";
33 | private static final String USER_NAME = "testuser";
34 | private static final int VALID_MONTH = 6;
35 | private static final int INVALID_MONTH = 13;
36 |
37 | private Hostel mockHostel;
38 |
39 | @Before
40 | public void setUp() {
41 | mockHostel = new Hostel();
42 | mockHostel.setName(VALID_HOSTEL_NAME);
43 | // Simuler un ID pour le mock hostel
44 | mockHostel.id = 1L;
45 | }
46 |
47 | // ========== Tests de succès ==========
48 |
49 | @Test
50 | public void testBook_Success() {
51 | // Given
52 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
53 | MockedStatic hostelMock = mockStatic(Hostel.class)) {
54 |
55 | // Configuration des mocks
56 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
57 | reservationMock.when(() -> Reservation.existsByUserNameAndMonthAndHostelName(USER_NAME, VALID_MONTH, VALID_HOSTEL_NAME))
58 | .thenReturn(false);
59 | hostelMock.when(() -> Hostel.findByName(VALID_HOSTEL_NAME))
60 | .thenReturn(Optional.of(mockHostel));
61 |
62 | // When
63 | Reservation result = hostelReservationService.book(VALID_HOSTEL_NAME, VALID_MONTH, USER_NAME);
64 |
65 | // Then
66 | assertNotNull("La réservation ne doit pas être null", result);
67 | assertEquals("Le nom d'utilisateur doit correspondre", USER_NAME, result.getUserName());
68 | assertEquals("Le mois doit correspondre", VALID_MONTH, result.getMonth());
69 | assertEquals("L'hostel doit correspondre", mockHostel, result.getHostel());
70 |
71 | // Vérification des appels
72 | verify(monthValidator).validateMonth(VALID_MONTH);
73 | reservationMock.verify(() -> Reservation.existsByUserNameAndMonthAndHostelName(USER_NAME, VALID_MONTH, VALID_HOSTEL_NAME));
74 | hostelMock.verify(() -> Hostel.findByName(VALID_HOSTEL_NAME));
75 | }
76 | }
77 |
78 | // ========== Tests d'erreur - Validation du mois ==========
79 |
80 | @Test(expected = DateTimeException.class)
81 | public void testBook_InvalidMonth_ThrowsDateTimeException() {
82 | // Given
83 | doThrow(new DateTimeException("Invalid month")).when(monthValidator).validateMonth(INVALID_MONTH);
84 |
85 | // When
86 | hostelReservationService.book(VALID_HOSTEL_NAME, INVALID_MONTH, USER_NAME);
87 |
88 | // Then - Exception attendue
89 | }
90 |
91 | @Test
92 | public void testBook_InvalidMonth_ValidatorCalled() {
93 | // Given
94 | doThrow(new DateTimeException("Invalid month")).when(monthValidator).validateMonth(INVALID_MONTH);
95 |
96 | // When
97 | try {
98 | hostelReservationService.book(VALID_HOSTEL_NAME, INVALID_MONTH, USER_NAME);
99 | fail("Une DateTimeException devrait être levée");
100 | } catch (DateTimeException e) {
101 | // Then
102 | verify(monthValidator).validateMonth(INVALID_MONTH);
103 | assertEquals("Invalid month", e.getMessage());
104 | }
105 | }
106 |
107 | // ========== Tests d'erreur - Réservation déjà existante ==========
108 |
109 | @Test(expected = UnavailableException.class)
110 | public void testBook_AlreadyBooked_ThrowsUnavailableException() {
111 | // Given
112 | try (MockedStatic reservationMock = mockStatic(Reservation.class)) {
113 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
114 | reservationMock.when(() -> Reservation.existsByUserNameAndMonthAndHostelName(USER_NAME, VALID_MONTH, VALID_HOSTEL_NAME))
115 | .thenReturn(true);
116 |
117 | // When
118 | hostelReservationService.book(VALID_HOSTEL_NAME, VALID_MONTH, USER_NAME);
119 |
120 | // Then - Exception attendue
121 | }
122 | }
123 |
124 | @Test
125 | public void testBook_AlreadyBooked_CorrectExceptionMessage() {
126 | // Given
127 | try (MockedStatic reservationMock = mockStatic(Reservation.class)) {
128 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
129 | reservationMock.when(() -> Reservation.existsByUserNameAndMonthAndHostelName(USER_NAME, VALID_MONTH, VALID_HOSTEL_NAME))
130 | .thenReturn(true);
131 |
132 | // When
133 | try {
134 | hostelReservationService.book(VALID_HOSTEL_NAME, VALID_MONTH, USER_NAME);
135 | fail("Une UnavailableException devrait être levée");
136 | } catch (UnavailableException e) {
137 | // Then
138 | String expectedMessage = "Hostel %s is already booked for month %s".formatted(VALID_HOSTEL_NAME, VALID_MONTH);
139 | assertEquals(expectedMessage, e.getMessage());
140 |
141 | // Vérification que la validation du mois a été appelée
142 | verify(monthValidator).validateMonth(VALID_MONTH);
143 | reservationMock.verify(() -> Reservation.existsByUserNameAndMonthAndHostelName(USER_NAME, VALID_MONTH, VALID_HOSTEL_NAME));
144 | }
145 | }
146 | }
147 |
148 | // ========== Tests d'erreur - Hostel inexistant ==========
149 |
150 | @Test(expected = UnknownEntityException.class)
151 | public void testBook_HostelNotFound_ThrowsUnknownEntityException() {
152 | // Given
153 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
154 | MockedStatic hostelMock = mockStatic(Hostel.class)) {
155 |
156 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
157 | reservationMock.when(() -> Reservation.existsByUserNameAndMonthAndHostelName(USER_NAME, VALID_MONTH, INVALID_HOSTEL_NAME))
158 | .thenReturn(false);
159 | hostelMock.when(() -> Hostel.findByName(INVALID_HOSTEL_NAME))
160 | .thenReturn(Optional.empty());
161 |
162 | // When
163 | hostelReservationService.book(INVALID_HOSTEL_NAME, VALID_MONTH, USER_NAME);
164 |
165 | // Then - Exception attendue
166 | }
167 | }
168 |
169 | @Test
170 | public void testBook_HostelNotFound_CorrectExceptionMessage() {
171 | // Given
172 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
173 | MockedStatic hostelMock = mockStatic(Hostel.class)) {
174 |
175 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
176 | reservationMock.when(() -> Reservation.existsByUserNameAndMonthAndHostelName(USER_NAME, VALID_MONTH, INVALID_HOSTEL_NAME))
177 | .thenReturn(false);
178 | hostelMock.when(() -> Hostel.findByName(INVALID_HOSTEL_NAME))
179 | .thenReturn(Optional.empty());
180 |
181 | // When
182 | try {
183 | hostelReservationService.book(INVALID_HOSTEL_NAME, VALID_MONTH, USER_NAME);
184 | fail("Une UnknownEntityException devrait être levée");
185 | } catch (UnknownEntityException e) {
186 | // Then
187 | String expectedMessage = "The hostel %s doesn't exist".formatted(INVALID_HOSTEL_NAME);
188 | assertEquals(expectedMessage, e.getMessage());
189 |
190 | // Vérification des appels
191 | verify(monthValidator).validateMonth(VALID_MONTH);
192 | reservationMock.verify(() -> Reservation.existsByUserNameAndMonthAndHostelName(USER_NAME, VALID_MONTH, INVALID_HOSTEL_NAME));
193 | hostelMock.verify(() -> Hostel.findByName(INVALID_HOSTEL_NAME));
194 | }
195 | }
196 | }
197 |
198 | // ========== Tests de cas limites ==========
199 |
200 | @Test
201 | public void testBook_WithNullUserName_Success() {
202 | // Given
203 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
204 | MockedStatic hostelMock = mockStatic(Hostel.class)) {
205 |
206 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
207 | reservationMock.when(() -> Reservation.existsByUserNameAndMonthAndHostelName(null, VALID_MONTH, VALID_HOSTEL_NAME))
208 | .thenReturn(false);
209 | hostelMock.when(() -> Hostel.findByName(VALID_HOSTEL_NAME))
210 | .thenReturn(Optional.of(mockHostel));
211 |
212 | // When
213 | Reservation result = hostelReservationService.book(VALID_HOSTEL_NAME, VALID_MONTH, null);
214 |
215 | // Then
216 | assertNotNull("La réservation ne doit pas être null", result);
217 | assertNull("Le nom d'utilisateur doit être null", result.getUserName());
218 | assertEquals("Le mois doit correspondre", VALID_MONTH, result.getMonth());
219 | assertEquals("L'hostel doit correspondre", mockHostel, result.getHostel());
220 | }
221 | }
222 |
223 | @Test
224 | public void testBook_WithEmptyHostelName_ThrowsUnknownEntityException() {
225 | // Given
226 | String emptyHostelName = "";
227 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
228 | MockedStatic hostelMock = mockStatic(Hostel.class)) {
229 |
230 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
231 | reservationMock.when(() -> Reservation.existsByUserNameAndMonthAndHostelName(USER_NAME, VALID_MONTH, emptyHostelName))
232 | .thenReturn(false);
233 | hostelMock.when(() -> Hostel.findByName(emptyHostelName))
234 | .thenReturn(Optional.empty());
235 |
236 | // When
237 | try {
238 | hostelReservationService.book(emptyHostelName, VALID_MONTH, USER_NAME);
239 | fail("Une UnknownEntityException devrait être levée");
240 | } catch (UnknownEntityException e) {
241 | // Then
242 | String expectedMessage = "The hostel %s doesn't exist".formatted(emptyHostelName);
243 | assertEquals(expectedMessage, e.getMessage());
244 | }
245 | }
246 | }
247 |
248 | // ========== Tests de comportement - Ordre des validations ==========
249 |
250 | @Test
251 | public void testBook_MonthValidationCalledFirst() {
252 | // Given
253 | doThrow(new DateTimeException("Invalid month")).when(monthValidator).validateMonth(INVALID_MONTH);
254 |
255 | // When
256 | try {
257 | hostelReservationService.book(VALID_HOSTEL_NAME, INVALID_MONTH, USER_NAME);
258 | fail("Une DateTimeException devrait être levée");
259 | } catch (DateTimeException e) {
260 | // Then
261 | verify(monthValidator).validateMonth(INVALID_MONTH);
262 | // Vérifier qu'aucune autre méthode statique n'a été appelée car l'exception est levée en premier
263 | }
264 | }
265 |
266 | @Test
267 | public void testBook_AllValidationsInCorrectOrder() {
268 | // Given
269 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
270 | MockedStatic hostelMock = mockStatic(Hostel.class)) {
271 |
272 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
273 | reservationMock.when(() -> Reservation.existsByUserNameAndMonthAndHostelName(USER_NAME, VALID_MONTH, VALID_HOSTEL_NAME))
274 | .thenReturn(false);
275 | hostelMock.when(() -> Hostel.findByName(VALID_HOSTEL_NAME))
276 | .thenReturn(Optional.of(mockHostel));
277 |
278 | // When
279 | Reservation result = hostelReservationService.book(VALID_HOSTEL_NAME, VALID_MONTH, USER_NAME);
280 |
281 | // Then
282 | assertNotNull(result);
283 |
284 | // Vérifier l'ordre des appels avec InOrder
285 | var inOrder = inOrder(monthValidator);
286 | inOrder.verify(monthValidator).validateMonth(VALID_MONTH);
287 | }
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/toxic-library-user/src/test/java/com/github/jtama/app/rocket/RocketReservationServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.github.jtama.app.rocket;
2 |
3 | import com.github.jtama.app.exception.InvalidBookingException;
4 | import com.github.jtama.app.exception.UnavailableException;
5 | import com.github.jtama.app.exception.UnknownEntityException;
6 | import com.github.jtama.app.hostels.Hostel;
7 | import com.github.jtama.app.reservation.Reservation;
8 | import com.github.jtama.app.util.MonthValidator;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.mockito.InjectMocks;
13 | import org.mockito.Mock;
14 | import org.mockito.MockedStatic;
15 | import org.mockito.junit.MockitoJUnitRunner;
16 |
17 | import java.time.DateTimeException;
18 | import java.util.Optional;
19 |
20 | import static org.junit.Assert.*;
21 | import static org.mockito.ArgumentMatchers.*;
22 | import static org.mockito.Mockito.*;
23 |
24 | @RunWith(MockitoJUnitRunner.class)
25 | public class RocketReservationServiceTest {
26 |
27 | @Mock
28 | private MonthValidator monthValidator;
29 |
30 | @InjectMocks
31 | private RocketReservationService rocketReservationService;
32 |
33 | private static final String VALID_ROCKET_NAME = "falcon-9";
34 | private static final String INVALID_ROCKET_NAME = "nonexistent-rocket";
35 | private static final String USER_NAME = "testuser";
36 | private static final int VALID_MONTH = 6;
37 | private static final int INVALID_MONTH = 13;
38 |
39 | private Rocket mockRocket;
40 | private Hostel mockHostel;
41 | private Reservation mockReservation;
42 |
43 | @Before
44 | public void setUp() {
45 | // Setup mock rocket
46 | mockRocket = new Rocket();
47 | mockRocket.setName(VALID_ROCKET_NAME);
48 | mockRocket.id = 1L;
49 |
50 | // Setup mock hostel
51 | mockHostel = new Hostel();
52 | mockHostel.setName("test-hostel");
53 | mockHostel.id = 2L;
54 |
55 | // Setup mock reservation with hostel
56 | mockReservation = new Reservation();
57 | mockReservation.setUserName(USER_NAME);
58 | mockReservation.setMonth(VALID_MONTH);
59 | mockReservation.setHostel(mockHostel);
60 | mockReservation.id = 3L;
61 | }
62 |
63 | // ========== Tests de succès ==========
64 |
65 | @Test
66 | public void testBook_Success() {
67 | // Given
68 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
69 | MockedStatic rocketMock = mockStatic(Rocket.class)) {
70 |
71 | // Configuration des mocks
72 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
73 |
74 | reservationMock.when(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH))
75 | .thenReturn(Optional.of(mockReservation));
76 |
77 | reservationMock.when(() -> Reservation.existsByMonthAndRocketName(VALID_MONTH, VALID_ROCKET_NAME))
78 | .thenReturn(false);
79 |
80 | rocketMock.when(() -> Rocket.findByName(VALID_ROCKET_NAME))
81 | .thenReturn(Optional.of(mockRocket));
82 |
83 | // When
84 | Reservation result = rocketReservationService.book(VALID_ROCKET_NAME, VALID_MONTH, USER_NAME);
85 |
86 | // Then
87 | assertNotNull("La réservation ne doit pas être null", result);
88 | assertEquals("Le nom d'utilisateur doit correspondre", USER_NAME, result.getUserName());
89 | assertEquals("Le mois doit correspondre", VALID_MONTH, result.getMonth());
90 | assertEquals("L'hostel doit correspondre", mockHostel, result.getHostel());
91 | assertEquals("La rocket doit être assignée", mockRocket, result.getRocket());
92 |
93 | // Vérification des appels
94 | verify(monthValidator).validateMonth(VALID_MONTH);
95 | reservationMock.verify(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH));
96 | reservationMock.verify(() -> Reservation.existsByMonthAndRocketName(VALID_MONTH, VALID_ROCKET_NAME));
97 | rocketMock.verify(() -> Rocket.findByName(VALID_ROCKET_NAME));
98 | }
99 | }
100 |
101 | // ========== Tests d'erreur - Validation du mois ==========
102 |
103 | @Test(expected = DateTimeException.class)
104 | public void testBook_InvalidMonth_ThrowsDateTimeException() {
105 | // Given
106 | doThrow(new DateTimeException("Invalid month")).when(monthValidator).validateMonth(INVALID_MONTH);
107 |
108 | // When
109 | rocketReservationService.book(VALID_ROCKET_NAME, INVALID_MONTH, USER_NAME);
110 |
111 | // Then - Exception attendue
112 | }
113 |
114 | @Test
115 | public void testBook_InvalidMonth_ValidatorCalled() {
116 | // Given
117 | doThrow(new DateTimeException("Invalid month")).when(monthValidator).validateMonth(INVALID_MONTH);
118 |
119 | // When
120 | try {
121 | rocketReservationService.book(VALID_ROCKET_NAME, INVALID_MONTH, USER_NAME);
122 | fail("Une DateTimeException devrait être levée");
123 | } catch (DateTimeException e) {
124 | // Then
125 | verify(monthValidator).validateMonth(INVALID_MONTH);
126 | assertEquals("Invalid month", e.getMessage());
127 | }
128 | }
129 |
130 | // ========== Tests d'erreur - Pas de réservation d'hostel ==========
131 |
132 | @Test(expected = InvalidBookingException.class)
133 | public void testBook_NoHostelReservation_ThrowsInvalidBookingException() {
134 | // Given
135 | try (MockedStatic reservationMock = mockStatic(Reservation.class)) {
136 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
137 | reservationMock.when(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH))
138 | .thenReturn(Optional.empty());
139 |
140 | // When
141 | rocketReservationService.book(VALID_ROCKET_NAME, VALID_MONTH, USER_NAME);
142 |
143 | // Then - Exception attendue
144 | }
145 | }
146 |
147 | @Test
148 | public void testBook_NoHostelReservation_CorrectExceptionMessage() {
149 | // Given
150 | try (MockedStatic reservationMock = mockStatic(Reservation.class)) {
151 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
152 | reservationMock.when(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH))
153 | .thenReturn(Optional.empty());
154 |
155 | // When
156 | try {
157 | rocketReservationService.book(VALID_ROCKET_NAME, VALID_MONTH, USER_NAME);
158 | fail("Une InvalidBookingException devrait être levée");
159 | } catch (InvalidBookingException e) {
160 | // Then
161 | String expectedMessage = "No hostel is booked for user %S on month %s".formatted(USER_NAME, VALID_MONTH);
162 | assertEquals(expectedMessage, e.getMessage());
163 |
164 | verify(monthValidator).validateMonth(VALID_MONTH);
165 | reservationMock.verify(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH));
166 | }
167 | }
168 | }
169 |
170 | // ========== Tests d'erreur - Rocket déjà réservée ==========
171 |
172 | @Test(expected = UnavailableException.class)
173 | public void testBook_RocketAlreadyBooked_ThrowsUnavailableException() {
174 | // Given
175 | try (MockedStatic reservationMock = mockStatic(Reservation.class)) {
176 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
177 | reservationMock.when(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH))
178 | .thenReturn(Optional.of(mockReservation));
179 | reservationMock.when(() -> Reservation.existsByMonthAndRocketName(VALID_MONTH, VALID_ROCKET_NAME))
180 | .thenReturn(true);
181 |
182 | // When
183 | rocketReservationService.book(VALID_ROCKET_NAME, VALID_MONTH, USER_NAME);
184 |
185 | // Then - Exception attendue
186 | }
187 | }
188 |
189 | @Test
190 | public void testBook_RocketAlreadyBooked_CorrectExceptionMessage() {
191 | // Given
192 | try (MockedStatic reservationMock = mockStatic(Reservation.class)) {
193 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
194 | reservationMock.when(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH))
195 | .thenReturn(Optional.of(mockReservation));
196 | reservationMock.when(() -> Reservation.existsByMonthAndRocketName(VALID_MONTH, VALID_ROCKET_NAME))
197 | .thenReturn(true);
198 |
199 | // When
200 | try {
201 | rocketReservationService.book(VALID_ROCKET_NAME, VALID_MONTH, USER_NAME);
202 | fail("Une UnavailableException devrait être levée");
203 | } catch (UnavailableException e) {
204 | // Then
205 | String expectedMessage = "Rocket %s has already been booked for month %s".formatted(VALID_ROCKET_NAME, VALID_MONTH);
206 | assertEquals(expectedMessage, e.getMessage());
207 |
208 | verify(monthValidator).validateMonth(VALID_MONTH);
209 | reservationMock.verify(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH));
210 | reservationMock.verify(() -> Reservation.existsByMonthAndRocketName(VALID_MONTH, VALID_ROCKET_NAME));
211 | }
212 | }
213 | }
214 |
215 | // ========== Tests d'erreur - Rocket inexistante ==========
216 |
217 | @Test(expected = UnknownEntityException.class)
218 | public void testBook_RocketNotFound_ThrowsUnknownEntityException() {
219 | // Given
220 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
221 | MockedStatic rocketMock = mockStatic(Rocket.class)) {
222 |
223 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
224 | reservationMock.when(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH))
225 | .thenReturn(Optional.of(mockReservation));
226 | reservationMock.when(() -> Reservation.existsByMonthAndRocketName(VALID_MONTH, INVALID_ROCKET_NAME))
227 | .thenReturn(false);
228 | rocketMock.when(() -> Rocket.findByName(INVALID_ROCKET_NAME))
229 | .thenReturn(Optional.empty());
230 |
231 | // When
232 | rocketReservationService.book(INVALID_ROCKET_NAME, VALID_MONTH, USER_NAME);
233 |
234 | // Then - Exception attendue
235 | }
236 | }
237 |
238 | @Test
239 | public void testBook_RocketNotFound_CorrectExceptionMessage() {
240 | // Given
241 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
242 | MockedStatic rocketMock = mockStatic(Rocket.class)) {
243 |
244 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
245 | reservationMock.when(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH))
246 | .thenReturn(Optional.of(mockReservation));
247 | reservationMock.when(() -> Reservation.existsByMonthAndRocketName(VALID_MONTH, INVALID_ROCKET_NAME))
248 | .thenReturn(false);
249 | rocketMock.when(() -> Rocket.findByName(INVALID_ROCKET_NAME))
250 | .thenReturn(Optional.empty());
251 |
252 | // When
253 | try {
254 | rocketReservationService.book(INVALID_ROCKET_NAME, VALID_MONTH, USER_NAME);
255 | fail("Une UnknownEntityException devrait être levée");
256 | } catch (UnknownEntityException e) {
257 | // Then
258 | String expectedMessage = "Rocket %s doesn't exists".formatted(INVALID_ROCKET_NAME);
259 | assertEquals(expectedMessage, e.getMessage());
260 |
261 | verify(monthValidator).validateMonth(VALID_MONTH);
262 | reservationMock.verify(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH));
263 | reservationMock.verify(() -> Reservation.existsByMonthAndRocketName(VALID_MONTH, INVALID_ROCKET_NAME));
264 | rocketMock.verify(() -> Rocket.findByName(INVALID_ROCKET_NAME));
265 | }
266 | }
267 | }
268 |
269 | // ========== Tests de cas limites ==========
270 |
271 | @Test
272 | public void testBook_WithNullUserName_NoHostelReservation() {
273 | // Given
274 | try (MockedStatic reservationMock = mockStatic(Reservation.class)) {
275 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
276 | reservationMock.when(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(null, VALID_MONTH))
277 | .thenReturn(Optional.empty());
278 |
279 | // When
280 | try {
281 | rocketReservationService.book(VALID_ROCKET_NAME, VALID_MONTH, null);
282 | fail("Une InvalidBookingException devrait être levée");
283 | } catch (InvalidBookingException e) {
284 | // Then
285 | String expectedMessage = "No hostel is booked for user %S on month %s".formatted(null, VALID_MONTH);
286 | assertEquals(expectedMessage, e.getMessage());
287 | }
288 | }
289 | }
290 |
291 | @Test
292 | public void testBook_WithEmptyRocketName_ThrowsUnknownEntityException() {
293 | // Given
294 | String emptyRocketName = "";
295 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
296 | MockedStatic rocketMock = mockStatic(Rocket.class)) {
297 |
298 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
299 | reservationMock.when(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH))
300 | .thenReturn(Optional.of(mockReservation));
301 | reservationMock.when(() -> Reservation.existsByMonthAndRocketName(VALID_MONTH, emptyRocketName))
302 | .thenReturn(false);
303 | rocketMock.when(() -> Rocket.findByName(emptyRocketName))
304 | .thenReturn(Optional.empty());
305 |
306 | // When
307 | try {
308 | rocketReservationService.book(emptyRocketName, VALID_MONTH, USER_NAME);
309 | fail("Une UnknownEntityException devrait être levée");
310 | } catch (UnknownEntityException e) {
311 | // Then
312 | String expectedMessage = "Rocket %s doesn't exists".formatted(emptyRocketName);
313 | assertEquals(expectedMessage, e.getMessage());
314 | }
315 | }
316 | }
317 |
318 | // ========== Tests de comportement - Ordre des validations ==========
319 |
320 | @Test
321 | public void testBook_MonthValidationCalledFirst() {
322 | // Given
323 | doThrow(new DateTimeException("Invalid month")).when(monthValidator).validateMonth(INVALID_MONTH);
324 |
325 | // When
326 | try {
327 | rocketReservationService.book(VALID_ROCKET_NAME, INVALID_MONTH, USER_NAME);
328 | fail("Une DateTimeException devrait être levée");
329 | } catch (DateTimeException e) {
330 | // Then
331 | verify(monthValidator).validateMonth(INVALID_MONTH);
332 | // Vérifier qu'aucune autre méthode statique n'a été appelée car l'exception est levée en premier
333 | }
334 | }
335 |
336 | @Test
337 | public void testBook_AllValidationsInCorrectOrder() {
338 | // Given
339 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
340 | MockedStatic rocketMock = mockStatic(Rocket.class)) {
341 |
342 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
343 | reservationMock.when(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH))
344 | .thenReturn(Optional.of(mockReservation));
345 | reservationMock.when(() -> Reservation.existsByMonthAndRocketName(VALID_MONTH, VALID_ROCKET_NAME))
346 | .thenReturn(false);
347 | rocketMock.when(() -> Rocket.findByName(VALID_ROCKET_NAME))
348 | .thenReturn(Optional.of(mockRocket));
349 |
350 | // When
351 | Reservation result = rocketReservationService.book(VALID_ROCKET_NAME, VALID_MONTH, USER_NAME);
352 |
353 | // Then
354 | assertNotNull(result);
355 |
356 | // Vérifier l'ordre des appels avec InOrder
357 | var inOrder = inOrder(monthValidator);
358 | inOrder.verify(monthValidator).validateMonth(VALID_MONTH);
359 | }
360 | }
361 |
362 | // ========== Test spécifique - Logique métier ==========
363 |
364 | @Test
365 | public void testBook_ReservationIsModifiedInPlace() {
366 | // Given
367 | try (MockedStatic reservationMock = mockStatic(Reservation.class);
368 | MockedStatic rocketMock = mockStatic(Rocket.class)) {
369 |
370 | // Créer une réservation sans rocket
371 | Reservation reservationWithoutRocket = new Reservation();
372 | reservationWithoutRocket.setUserName(USER_NAME);
373 | reservationWithoutRocket.setMonth(VALID_MONTH);
374 | reservationWithoutRocket.setHostel(mockHostel);
375 | reservationWithoutRocket.id = 4L;
376 | assertNull("La rocket doit être null initialement", reservationWithoutRocket.getRocket());
377 |
378 | doNothing().when(monthValidator).validateMonth(VALID_MONTH);
379 | reservationMock.when(() -> Reservation.findByUserNameAndMonthAndHostelIsNotNull(USER_NAME, VALID_MONTH))
380 | .thenReturn(Optional.of(reservationWithoutRocket));
381 | reservationMock.when(() -> Reservation.existsByMonthAndRocketName(VALID_MONTH, VALID_ROCKET_NAME))
382 | .thenReturn(false);
383 | rocketMock.when(() -> Rocket.findByName(VALID_ROCKET_NAME))
384 | .thenReturn(Optional.of(mockRocket));
385 |
386 | // When
387 | Reservation result = rocketReservationService.book(VALID_ROCKET_NAME, VALID_MONTH, USER_NAME);
388 |
389 | // Then
390 | assertSame("La même instance de réservation doit être retournée", reservationWithoutRocket, result);
391 | assertEquals("La rocket doit maintenant être assignée", mockRocket, result.getRocket());
392 | assertEquals("La rocket doit être assignée à la réservation originale", mockRocket, reservationWithoutRocket.getRocket());
393 | }
394 | }
395 | }
396 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Attribution-NonCommercial-ShareAlike 4.0 International
2 |
3 | =======================================================================
4 |
5 | Creative Commons Corporation ("Creative Commons") is not a law firm and
6 | does not provide legal services or legal advice. Distribution of
7 | Creative Commons public licenses does not create a lawyer-client or
8 | other relationship. Creative Commons makes its licenses and related
9 | information available on an "as-is" basis. Creative Commons gives no
10 | warranties regarding its licenses, any material licensed under their
11 | terms and conditions, or any related information. Creative Commons
12 | disclaims all liability for damages resulting from their use to the
13 | fullest extent possible.
14 |
15 | Using Creative Commons Public Licenses
16 |
17 | Creative Commons public licenses provide a standard set of terms and
18 | conditions that creators and other rights holders may use to share
19 | original works of authorship and other material subject to copyright
20 | and certain other rights specified in the public license below. The
21 | following considerations are for informational purposes only, are not
22 | exhaustive, and do not form part of our licenses.
23 |
24 | Considerations for licensors: Our public licenses are
25 | intended for use by those authorized to give the public
26 | permission to use material in ways otherwise restricted by
27 | copyright and certain other rights. Our licenses are
28 | irrevocable. Licensors should read and understand the terms
29 | and conditions of the license they choose before applying it.
30 | Licensors should also secure all rights necessary before
31 | applying our licenses so that the public can reuse the
32 | material as expected. Licensors should clearly mark any
33 | material not subject to the license. This includes other CC-
34 | licensed material, or material used under an exception or
35 | limitation to copyright. More considerations for licensors:
36 | wiki.creativecommons.org/Considerations_for_licensors
37 |
38 | Considerations for the public: By using one of our public
39 | licenses, a licensor grants the public permission to use the
40 | licensed material under specified terms and conditions. If
41 | the licensor's permission is not necessary for any reason--for
42 | example, because of any applicable exception or limitation to
43 | copyright--then that use is not regulated by the license. Our
44 | licenses grant only permissions under copyright and certain
45 | other rights that a licensor has authority to grant. Use of
46 | the licensed material may still be restricted for other
47 | reasons, including because others have copyright or other
48 | rights in the material. A licensor may make special requests,
49 | such as asking that all changes be marked or described.
50 | Although not required by our licenses, you are encouraged to
51 | respect those requests where reasonable. More considerations
52 | for the public:
53 | wiki.creativecommons.org/Considerations_for_licensees
54 |
55 | =======================================================================
56 |
57 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
58 | Public License
59 |
60 | By exercising the Licensed Rights (defined below), You accept and agree
61 | to be bound by the terms and conditions of this Creative Commons
62 | Attribution-NonCommercial-ShareAlike 4.0 International Public License
63 | ("Public License"). To the extent this Public License may be
64 | interpreted as a contract, You are granted the Licensed Rights in
65 | consideration of Your acceptance of these terms and conditions, and the
66 | Licensor grants You such rights in consideration of benefits the
67 | Licensor receives from making the Licensed Material available under
68 | these terms and conditions.
69 |
70 |
71 | Section 1 -- Definitions.
72 |
73 | a. Adapted Material means material subject to Copyright and Similar
74 | Rights that is derived from or based upon the Licensed Material
75 | and in which the Licensed Material is translated, altered,
76 | arranged, transformed, or otherwise modified in a manner requiring
77 | permission under the Copyright and Similar Rights held by the
78 | Licensor. For purposes of this Public License, where the Licensed
79 | Material is a musical work, performance, or sound recording,
80 | Adapted Material is always produced where the Licensed Material is
81 | synched in timed relation with a moving image.
82 |
83 | b. Adapter's License means the license You apply to Your Copyright
84 | and Similar Rights in Your contributions to Adapted Material in
85 | accordance with the terms and conditions of this Public License.
86 |
87 | c. BY-NC-SA Compatible License means a license listed at
88 | creativecommons.org/compatiblelicenses, approved by Creative
89 | Commons as essentially the equivalent of this Public License.
90 |
91 | d. Copyright and Similar Rights means copyright and/or similar rights
92 | closely related to copyright including, without limitation,
93 | performance, broadcast, sound recording, and Sui Generis Database
94 | Rights, without regard to how the rights are labeled or
95 | categorized. For purposes of this Public License, the rights
96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar
97 | Rights.
98 |
99 | e. Effective Technological Measures means those measures that, in the
100 | absence of proper authority, may not be circumvented under laws
101 | fulfilling obligations under Article 11 of the WIPO Copyright
102 | Treaty adopted on December 20, 1996, and/or similar international
103 | agreements.
104 |
105 | f. Exceptions and Limitations means fair use, fair dealing, and/or
106 | any other exception or limitation to Copyright and Similar Rights
107 | that applies to Your use of the Licensed Material.
108 |
109 | g. License Elements means the license attributes listed in the name
110 | of a Creative Commons Public License. The License Elements of this
111 | Public License are Attribution, NonCommercial, and ShareAlike.
112 |
113 | h. Licensed Material means the artistic or literary work, database,
114 | or other material to which the Licensor applied this Public
115 | License.
116 |
117 | i. Licensed Rights means the rights granted to You subject to the
118 | terms and conditions of this Public License, which are limited to
119 | all Copyright and Similar Rights that apply to Your use of the
120 | Licensed Material and that the Licensor has authority to license.
121 |
122 | j. Licensor means the individual(s) or entity(ies) granting rights
123 | under this Public License.
124 |
125 | k. NonCommercial means not primarily intended for or directed towards
126 | commercial advantage or monetary compensation. For purposes of
127 | this Public License, the exchange of the Licensed Material for
128 | other material subject to Copyright and Similar Rights by digital
129 | file-sharing or similar means is NonCommercial provided there is
130 | no payment of monetary compensation in connection with the
131 | exchange.
132 |
133 | l. Share means to provide material to the public by any means or
134 | process that requires permission under the Licensed Rights, such
135 | as reproduction, public display, public performance, distribution,
136 | dissemination, communication, or importation, and to make material
137 | available to the public including in ways that members of the
138 | public may access the material from a place and at a time
139 | individually chosen by them.
140 |
141 | m. Sui Generis Database Rights means rights other than copyright
142 | resulting from Directive 96/9/EC of the European Parliament and of
143 | the Council of 11 March 1996 on the legal protection of databases,
144 | as amended and/or succeeded, as well as other essentially
145 | equivalent rights anywhere in the world.
146 |
147 | n. You means the individual or entity exercising the Licensed Rights
148 | under this Public License. Your has a corresponding meaning.
149 |
150 |
151 | Section 2 -- Scope.
152 |
153 | a. License grant.
154 |
155 | 1. Subject to the terms and conditions of this Public License,
156 | the Licensor hereby grants You a worldwide, royalty-free,
157 | non-sublicensable, non-exclusive, irrevocable license to
158 | exercise the Licensed Rights in the Licensed Material to:
159 |
160 | a. reproduce and Share the Licensed Material, in whole or
161 | in part, for NonCommercial purposes only; and
162 |
163 | b. produce, reproduce, and Share Adapted Material for
164 | NonCommercial purposes only.
165 |
166 | 2. Exceptions and Limitations. For the avoidance of doubt, where
167 | Exceptions and Limitations apply to Your use, this Public
168 | License does not apply, and You do not need to comply with
169 | its terms and conditions.
170 |
171 | 3. Term. The term of this Public License is specified in Section
172 | 6(a).
173 |
174 | 4. Media and formats; technical modifications allowed. The
175 | Licensor authorizes You to exercise the Licensed Rights in
176 | all media and formats whether now known or hereafter created,
177 | and to make technical modifications necessary to do so. The
178 | Licensor waives and/or agrees not to assert any right or
179 | authority to forbid You from making technical modifications
180 | necessary to exercise the Licensed Rights, including
181 | technical modifications necessary to circumvent Effective
182 | Technological Measures. For purposes of this Public License,
183 | simply making modifications authorized by this Section 2(a)
184 | (4) never produces Adapted Material.
185 |
186 | 5. Downstream recipients.
187 |
188 | a. Offer from the Licensor -- Licensed Material. Every
189 | recipient of the Licensed Material automatically
190 | receives an offer from the Licensor to exercise the
191 | Licensed Rights under the terms and conditions of this
192 | Public License.
193 |
194 | b. Additional offer from the Licensor -- Adapted Material.
195 | Every recipient of Adapted Material from You
196 | automatically receives an offer from the Licensor to
197 | exercise the Licensed Rights in the Adapted Material
198 | under the conditions of the Adapter's License You apply.
199 |
200 | c. No downstream restrictions. You may not offer or impose
201 | any additional or different terms or conditions on, or
202 | apply any Effective Technological Measures to, the
203 | Licensed Material if doing so restricts exercise of the
204 | Licensed Rights by any recipient of the Licensed
205 | Material.
206 |
207 | 6. No endorsement. Nothing in this Public License constitutes or
208 | may be construed as permission to assert or imply that You
209 | are, or that Your use of the Licensed Material is, connected
210 | with, or sponsored, endorsed, or granted official status by,
211 | the Licensor or others designated to receive attribution as
212 | provided in Section 3(a)(1)(A)(i).
213 |
214 | b. Other rights.
215 |
216 | 1. Moral rights, such as the right of integrity, are not
217 | licensed under this Public License, nor are publicity,
218 | privacy, and/or other similar personality rights; however, to
219 | the extent possible, the Licensor waives and/or agrees not to
220 | assert any such rights held by the Licensor to the limited
221 | extent necessary to allow You to exercise the Licensed
222 | Rights, but not otherwise.
223 |
224 | 2. Patent and trademark rights are not licensed under this
225 | Public License.
226 |
227 | 3. To the extent possible, the Licensor waives any right to
228 | collect royalties from You for the exercise of the Licensed
229 | Rights, whether directly or through a collecting society
230 | under any voluntary or waivable statutory or compulsory
231 | licensing scheme. In all other cases the Licensor expressly
232 | reserves any right to collect such royalties, including when
233 | the Licensed Material is used other than for NonCommercial
234 | purposes.
235 |
236 |
237 | Section 3 -- License Conditions.
238 |
239 | Your exercise of the Licensed Rights is expressly made subject to the
240 | following conditions.
241 |
242 | a. Attribution.
243 |
244 | 1. If You Share the Licensed Material (including in modified
245 | form), You must:
246 |
247 | a. retain the following if it is supplied by the Licensor
248 | with the Licensed Material:
249 |
250 | i. identification of the creator(s) of the Licensed
251 | Material and any others designated to receive
252 | attribution, in any reasonable manner requested by
253 | the Licensor (including by pseudonym if
254 | designated);
255 |
256 | ii. a copyright notice;
257 |
258 | iii. a notice that refers to this Public License;
259 |
260 | iv. a notice that refers to the disclaimer of
261 | warranties;
262 |
263 | v. a URI or hyperlink to the Licensed Material to the
264 | extent reasonably practicable;
265 |
266 | b. indicate if You modified the Licensed Material and
267 | retain an indication of any previous modifications; and
268 |
269 | c. indicate the Licensed Material is licensed under this
270 | Public License, and include the text of, or the URI or
271 | hyperlink to, this Public License.
272 |
273 | 2. You may satisfy the conditions in Section 3(a)(1) in any
274 | reasonable manner based on the medium, means, and context in
275 | which You Share the Licensed Material. For example, it may be
276 | reasonable to satisfy the conditions by providing a URI or
277 | hyperlink to a resource that includes the required
278 | information.
279 | 3. If requested by the Licensor, You must remove any of the
280 | information required by Section 3(a)(1)(A) to the extent
281 | reasonably practicable.
282 |
283 | b. ShareAlike.
284 |
285 | In addition to the conditions in Section 3(a), if You Share
286 | Adapted Material You produce, the following conditions also apply.
287 |
288 | 1. The Adapter's License You apply must be a Creative Commons
289 | license with the same License Elements, this version or
290 | later, or a BY-NC-SA Compatible License.
291 |
292 | 2. You must include the text of, or the URI or hyperlink to, the
293 | Adapter's License You apply. You may satisfy this condition
294 | in any reasonable manner based on the medium, means, and
295 | context in which You Share Adapted Material.
296 |
297 | 3. You may not offer or impose any additional or different terms
298 | or conditions on, or apply any Effective Technological
299 | Measures to, Adapted Material that restrict exercise of the
300 | rights granted under the Adapter's License You apply.
301 |
302 |
303 | Section 4 -- Sui Generis Database Rights.
304 |
305 | Where the Licensed Rights include Sui Generis Database Rights that
306 | apply to Your use of the Licensed Material:
307 |
308 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right
309 | to extract, reuse, reproduce, and Share all or a substantial
310 | portion of the contents of the database for NonCommercial purposes
311 | only;
312 |
313 | b. if You include all or a substantial portion of the database
314 | contents in a database in which You have Sui Generis Database
315 | Rights, then the database in which You have Sui Generis Database
316 | Rights (but not its individual contents) is Adapted Material,
317 | including for purposes of Section 3(b); and
318 |
319 | c. You must comply with the conditions in Section 3(a) if You Share
320 | all or a substantial portion of the contents of the database.
321 |
322 | For the avoidance of doubt, this Section 4 supplements and does not
323 | replace Your obligations under this Public License where the Licensed
324 | Rights include other Copyright and Similar Rights.
325 |
326 |
327 | Section 5 -- Disclaimer of Warranties and Limitation of Liability.
328 |
329 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
330 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
331 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
332 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
333 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
334 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
335 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
336 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
337 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
338 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
339 |
340 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
341 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
342 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
343 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
344 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
345 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
346 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
347 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
348 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
349 |
350 | c. The disclaimer of warranties and limitation of liability provided
351 | above shall be interpreted in a manner that, to the extent
352 | possible, most closely approximates an absolute disclaimer and
353 | waiver of all liability.
354 |
355 |
356 | Section 6 -- Term and Termination.
357 |
358 | a. This Public License applies for the term of the Copyright and
359 | Similar Rights licensed here. However, if You fail to comply with
360 | this Public License, then Your rights under this Public License
361 | terminate automatically.
362 |
363 | b. Where Your right to use the Licensed Material has terminated under
364 | Section 6(a), it reinstates:
365 |
366 | 1. automatically as of the date the violation is cured, provided
367 | it is cured within 30 days of Your discovery of the
368 | violation; or
369 |
370 | 2. upon express reinstatement by the Licensor.
371 |
372 | For the avoidance of doubt, this Section 6(b) does not affect any
373 | right the Licensor may have to seek remedies for Your violations
374 | of this Public License.
375 |
376 | c. For the avoidance of doubt, the Licensor may also offer the
377 | Licensed Material under separate terms or conditions or stop
378 | distributing the Licensed Material at any time; however, doing so
379 | will not terminate this Public License.
380 |
381 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
382 | License.
383 |
384 |
385 | Section 7 -- Other Terms and Conditions.
386 |
387 | a. The Licensor shall not be bound by any additional or different
388 | terms or conditions communicated by You unless expressly agreed.
389 |
390 | b. Any arrangements, understandings, or agreements regarding the
391 | Licensed Material not stated herein are separate from and
392 | independent of the terms and conditions of this Public License.
393 |
394 |
395 | Section 8 -- Interpretation.
396 |
397 | a. For the avoidance of doubt, this Public License does not, and
398 | shall not be interpreted to, reduce, limit, restrict, or impose
399 | conditions on any use of the Licensed Material that could lawfully
400 | be made without permission under this Public License.
401 |
402 | b. To the extent possible, if any provision of this Public License is
403 | deemed unenforceable, it shall be automatically reformed to the
404 | minimum extent necessary to make it enforceable. If the provision
405 | cannot be reformed, it shall be severed from this Public License
406 | without affecting the enforceability of the remaining terms and
407 | conditions.
408 |
409 | c. No term or condition of this Public License will be waived and no
410 | failure to comply consented to unless expressly agreed to by the
411 | Licensor.
412 |
413 | d. Nothing in this Public License constitutes or may be interpreted
414 | as a limitation upon, or waiver of, any privileges and immunities
415 | that apply to the Licensor or You, including from the legal
416 | processes of any jurisdiction or authority.
417 |
418 | =======================================================================
419 |
420 | Creative Commons is not a party to its public
421 | licenses. Notwithstanding, Creative Commons may elect to apply one of
422 | its public licenses to material it publishes and in those instances
423 | will be considered the “Licensor.” The text of the Creative Commons
424 | public licenses is dedicated to the public domain under the CC0 Public
425 | Domain Dedication. Except for the limited purpose of indicating that
426 | material is shared under a Creative Commons public license or as
427 | otherwise permitted by the Creative Commons policies published at
428 | creativecommons.org/policies, Creative Commons does not authorize the
429 | use of the trademark "Creative Commons" or any other trademark or logo
430 | of Creative Commons without its prior written consent including,
431 | without limitation, in connection with any unauthorized modifications
432 | to any of its public licenses or any other arrangements,
433 | understandings, or agreements concerning use of licensed material. For
434 | the avoidance of doubt, this paragraph does not form part of the
435 | public licenses.
436 |
437 | Creative Commons may be contacted at creativecommons.org.
--------------------------------------------------------------------------------
/slides/images/under_logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------