├── .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 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 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 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 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 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 | 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 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 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 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 | --------------------------------------------------------------------------------