Dans ce TP, nous allons un peu jouer avec les nouveautés du langage Java :

  • records

  • streams & lambdas

  • multi-line strings

Nous avons bien besoin de Java 17 (minimum) pour ce TP !

1. Initialisation du projet

Initialisez un pom.xml, ainsi que les répertoires src/main/java et src/test/java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.miage.alom.tp</groupId>
    <artifactId>w02-modern-java</artifactId>
    <version>0.1.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source> (1)
        <maven.compiler.target>17</maven.compiler.target> (2)
    </properties>

    <dependencies>
    </dependencies>

</project>
1 On indique à maven quelle version de Java utiliser pour les sources !
2 On indique à maven quelle version de JVM on cible !
Maven considère par défaut que le code est écrit en Java 7 !

Ajoutez les dépendances JUnit :

pom.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.24.2</version>
    <scope>test</scope>
</dependency>

Il vous faut également surcharger la version du maven-surefire-plugin (qui est le plugin maven qui implémente la phase d’exécution des tests).

pom.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<build>
  <pluginManagement>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.1.2</version> (1)
        </plugin>
    </plugins>
  </pluginManagement>
</build>
1 On a besoin de la version 2.22.0 minimum pour JUnit 5 comme indiqué dans la documentation junit

Pour que votre TP soit évalué automatiquement, ajoutez également le fichier .gitlab-ci.yml suivant à la racine de votre projet :

.gitlab-ci.yml
1
2
3
include:
  project: gitlab-classrooms/autograding
  file: maven-junit.yml

2. Records

Dans un premier temps, nous allons manipuler des records.

Créez les records suivants :

  • PokemonType

    • id: int

    • name: String

    • types: List<String>

  • Pokemon

    • type: PokemonType

    • nickname: String

    • level: int

Le record PokemonType représente les types de Pokemon (Pikachu, Rattata, …​).

Le record Pokemon représente un Pokemon particulier (Le Pikachu de Sasha, le Rattata de Julien…​).

Importez dans votre code les tests unitaires suivants :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

public class RecordsTest {

    @Test
    public void testPokemonTypeCreation(){
        var pikachu = new PokemonType(25, "Pikachu", List.of("electric"));

        assertThat(pikachu.id()).isEqualTo(25);
        assertThat(pikachu.name()).isEqualTo("Pikachu");
        assertThat(pikachu.types()).containsOnly("electric");
    }

    @Test
    public void testPokemonCreation(){
        var geodude = new PokemonType(74, "Racaillou", List.of("rock", "ground"));
        var petersGeodude = new Pokemon(geodude, "Racaillou de Pierre", 12);

        assertThat(petersGeodude.nickname()).isEqualTo("Racaillou de Pierre");
        assertThat(petersGeodude.type()).isEqualTo(geodude);
        assertThat(petersGeodude.level()).isEqualTo(12);
    }

}

3. Streams et Lambdas

Nous allons maintenant charger une liste de types de Pokemons, et la manipuler avec des Streams.

Récupérez le fichier pokemons.json, et placez-le dans le répertoire src/main/resources de votre projet.

Pour charger le fichier JSON, nous allons devoir utiliser la librairie jackson-databind:

pom.xml
1
2
3
4
5
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>

Importez et complétez la classe suivante :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class PokemonStreams {

    private Collection<PokemonType> pokemonsTypes;

    public void loadPokemonTypes() throws IOException {
        var objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        this.pokemonsTypes = objectMapper.readValue(new FileInputStream("src/main/resources/pokemons.json"), new TypeReference<Collection<PokemonType>>() {});
    }

    public List<PokemonType> sortById(){
        // TODO
    }

    public Set<PokemonType> findByType(String type) {
        // TODO
    }

    public Optional<PokemonType> findFirstByTypes(String... types) {
        // TODO
    }
}

Vous pouvez valider vos développements avec la classe de test suivante :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class PokemonStreamsTest {

    PokemonStreams pokemonStreams;

    @BeforeEach
    void setUp() throws IOException {
        pokemonStreams = new PokemonStreams();
        pokemonStreams.loadPokemonTypes();
    }

    @Test
    public void testSortById(){
        assertThat(pokemonStreams.sortById())
                .extracting(it -> it.id())
                .isSorted();
    }

    @Test
    public void testFindElectricPokemons(){
        assertThat(pokemonStreams.findByType("electric"))
                .hasSize(9);
    }

    @Test
    public void testFindFirePokemons(){
        assertThat(pokemonStreams.findByType("fire"))
                .hasSize(12);
    }

    @Test
    public void testFindFirstPsychicPokemon(){
        assertThat(pokemonStreams.findFirstByTypes("psychic"))
                .isNotEmpty()
                .get()
                .extracting("name")
                .isEqualTo("abra");
    }

    @Test
    public void testFindFirstUnknownPokemon(){
        assertThat(pokemonStreams.findFirstByTypes("unknown"))
                .isEmpty();
    }
}

4. Multi-Line Strings

Importez le test suivant, et faites ce qu’il faut pour qu’il passe !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class TextBlockTest {

    ObjectMapper objectMapper;

    @BeforeEach
    void setUp() {
        objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    @Test
    void pokemonAsJsonString() throws JsonProcessingException {
        var jsonStringOldFashioned = "{\n" +
                "    \"id\": 47,\n" +
                "    \"name\": \"parasect\",\n" +
                "    \"baseExperience\": 142,\n" +
                "    \"weight\": 295,\n" +
                "    \"height\": 10,\n" +
                "    \"types\": [\n" +
                "      \"grass\",\n" +
                "      \"bug\"\n" +
                "    ],\n" +
                "    \"stats\": {\n" +
                "      \"speed\": 30,\n" +
                "      \"attack\": 95,\n" +
                "      \"defense\": 80,\n" +
                "      \"hp\": 60\n" +
                "    },\n" +
                "    \"sprites\": {\n" +
                "      \"front_default\": \"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/47.png\",\n" +
                "      \"back_default\": \"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/47.png\"\n" +
                "    }\n" +
                "  }";
        var pokemonTypeFromJsonString = objectMapper.readValue(jsonStringOldFashioned, PokemonType.class);
        System.out.println("pokemonType.toString() = " + pokemonTypeFromJsonString.toString());


        // TODO : écrivez un text block ici !
        String textBlockString = null;
        var pokemonTypeFromTextBlock = objectMapper.readValue(textBlockString, PokemonType.class);

        assertThat(pokemonTypeFromJsonString).isEqualTo(pokemonTypeFromTextBlock);
    }
}