From d01f6fffd3d5f21bad5bef44effcd0f3aabee209 Mon Sep 17 00:00:00 2001
From: arnaudlabourel <arnaud.labourel@univ-amu.fr>
Date: Thu, 8 Sep 2022 10:49:00 +0200
Subject: [PATCH] Correction composition

---
 settings.gradle                               |   2 +-
 src/main/java/agency/AbstractVehicle.java     |   2 +-
 src/main/java/agency/CarSpecifics.java        |  43 ++++++
 src/main/java/agency/Client.java              |   1 -
 src/main/java/agency/ConcreteVehicle.java     |  73 ++++++++++
 src/main/java/agency/MotorbikeSpecifics.java  |  30 ++++
 src/main/java/agency/RentalAgency.java        |  12 +-
 .../java/agency/VehicleTypeSpecifics.java     |   7 +
 src/test/java/agency/TestRentalAgency.java    | 130 +++++++++++-------
 9 files changed, 244 insertions(+), 56 deletions(-)
 create mode 100644 src/main/java/agency/CarSpecifics.java
 create mode 100644 src/main/java/agency/ConcreteVehicle.java
 create mode 100644 src/main/java/agency/MotorbikeSpecifics.java
 create mode 100644 src/main/java/agency/VehicleTypeSpecifics.java

diff --git a/settings.gradle b/settings.gradle
index b96841a..0ab41f9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,2 @@
-rootProject.name = 'formula'
+rootProject.name = 'agency'
 
diff --git a/src/main/java/agency/AbstractVehicle.java b/src/main/java/agency/AbstractVehicle.java
index 03f016f..2ad1427 100644
--- a/src/main/java/agency/AbstractVehicle.java
+++ b/src/main/java/agency/AbstractVehicle.java
@@ -33,8 +33,8 @@ public abstract class AbstractVehicle implements Vehicle {
     if(productionYear < MINIMAL_YEAR_VALUE || productionYear > currentYearValue)
       throw new IllegalArgumentException(productionYear + " is not a correct year (comprised between "
               + MINIMAL_YEAR_VALUE + " and " + currentYearValue + ")");
-    this.brand = brand;
     this.productionYear = productionYear;
+    this.brand = brand;
     this.model = model;
   }
 
diff --git a/src/main/java/agency/CarSpecifics.java b/src/main/java/agency/CarSpecifics.java
new file mode 100644
index 0000000..cb5fff3
--- /dev/null
+++ b/src/main/java/agency/CarSpecifics.java
@@ -0,0 +1,43 @@
+package agency;
+
+import util.TimeProvider;
+
+public class CarSpecifics implements VehicleTypeSpecifics {
+  private static final int MAXIMUM_AGE_FOR_NEW_CAR = 5;
+  private final int numberOfSeats;
+  private static final int PRICE_PER_SEAT_FOR_NEW_CAR = 40;
+  private static final int PRICE_PER_SEAT_FOR_OLD_CAR = 20;
+  private final boolean isNew;
+
+  public CarSpecifics(int numberOfSeats, int productionYear) {
+    isNew = age(productionYear) <= MAXIMUM_AGE_FOR_NEW_CAR;
+    this.numberOfSeats = numberOfSeats;
+    if (numberOfSeats < 1)
+      throw new IllegalArgumentException("Cannot construct a car with "
+              + numberOfSeats + " seats");
+  }
+
+  private static int age(int productionYear) {
+    return TimeProvider.currentYearValue() - productionYear;
+  }
+
+  @Override
+  public double dailyRentalPrice() {
+    final int priceBySeat = isNew ? PRICE_PER_SEAT_FOR_NEW_CAR : PRICE_PER_SEAT_FOR_OLD_CAR;
+    return priceBySeat * numberOfSeats;
+  }
+
+  @Override
+  public String kindOfVehicle() {
+    return "Car";
+  }
+
+  @Override
+  public String vehicleDetails() {
+    return numberOfSeats + " " + (hasSeveralSeats() ? "seats" : "seat");
+  }
+
+  private boolean hasSeveralSeats() {
+    return numberOfSeats > 1;
+  }
+}
diff --git a/src/main/java/agency/Client.java b/src/main/java/agency/Client.java
index 65fdab4..5d8b003 100644
--- a/src/main/java/agency/Client.java
+++ b/src/main/java/agency/Client.java
@@ -6,7 +6,6 @@ public class Client {
   private final String firstName;
   private final String lastName;
   private final int yearOfBirth;
-
   public Client(String firstName, String lastName, int yearOfBirth) {
     this.firstName = firstName;
     this.lastName = lastName;
diff --git a/src/main/java/agency/ConcreteVehicle.java b/src/main/java/agency/ConcreteVehicle.java
new file mode 100644
index 0000000..a637e46
--- /dev/null
+++ b/src/main/java/agency/ConcreteVehicle.java
@@ -0,0 +1,73 @@
+package agency;
+
+import util.TimeProvider;
+
+public class ConcreteVehicle implements Vehicle{
+  private static final int MINIMAL_YEAR_VALUE = 1900;
+  private final String brand;
+  private final String model;
+  private final int productionYear;
+  private final VehicleTypeSpecifics vehicleTypeSpecifics;
+
+  @Override
+  public int hashCode() {
+    int result = brand.hashCode();
+    result = 31 * result + model.hashCode();
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || this.getClass() != o.getClass()) return false;
+
+    ConcreteVehicle that = (ConcreteVehicle) o;
+    if (vehicleTypeSpecifics.getClass() != that.vehicleTypeSpecifics.getClass())
+      return false;
+    if (!brand.equals(that.brand)) return false;
+    return model.equals(that.model);
+  }
+
+  ConcreteVehicle(String brand, String model, int productionYear,
+                  VehicleTypeSpecifics vehicleTypeSpecifics) {
+    int currentYearValue =  TimeProvider.currentYearValue();
+    if(productionYear < MINIMAL_YEAR_VALUE || productionYear > currentYearValue)
+      throw new IllegalArgumentException(productionYear + " is not a correct year (comprised between "
+              + MINIMAL_YEAR_VALUE + " and " + currentYearValue + ")");
+    this.productionYear = productionYear;
+    this.brand = brand;
+    this.model = model;
+    this.vehicleTypeSpecifics = vehicleTypeSpecifics;
+  }
+
+  @Override
+  public String getBrand() {
+    return brand;
+  }
+
+  @Override
+  public String getModel() {
+    return model;
+  }
+  @Override
+  public int getProductionYear() {
+    return productionYear;
+  }
+  public String kindOfVehicle(){
+    return vehicleTypeSpecifics.kindOfVehicle();
+  }
+  public String vehicleDetails(){
+    return vehicleTypeSpecifics.vehicleDetails();
+  }
+  @Override
+  public double dailyRentalPrice() {
+    return vehicleTypeSpecifics.dailyRentalPrice();
+  }
+
+  @Override
+  public String toString() {
+    return kindOfVehicle() + " " + brand + " " + model + " " + productionYear
+            + " (" + vehicleDetails() + ")" + " : "
+            + dailyRentalPrice() + "€";
+  }
+}
diff --git a/src/main/java/agency/MotorbikeSpecifics.java b/src/main/java/agency/MotorbikeSpecifics.java
new file mode 100644
index 0000000..dbde698
--- /dev/null
+++ b/src/main/java/agency/MotorbikeSpecifics.java
@@ -0,0 +1,30 @@
+package agency;
+
+public class MotorbikeSpecifics implements VehicleTypeSpecifics{
+  private static final double PRICE_PER_UNIT_OF_CYLINDER_CAPACITY = 0.25;
+  private static final int MINIMAL_CAPACITY = 50;
+  private static final String UNIT_OF_CYLINDER_CAPACITY = "cm³";
+  private final int cylinderCapacity;
+
+
+  MotorbikeSpecifics(int cylinderCapacity) {
+    if (!(cylinderCapacity < MINIMAL_CAPACITY))
+      throw new IllegalArgumentException("Incorrect cylinder capacity "+ cylinderCapacity + "(must be at least " + MINIMAL_CAPACITY +")");
+    this.cylinderCapacity = cylinderCapacity;
+  }
+
+  @Override
+  public String kindOfVehicle() {
+    return "Motorbike";
+  }
+
+  @Override
+  public String vehicleDetails() {
+    return cylinderCapacity + UNIT_OF_CYLINDER_CAPACITY;
+  }
+
+  @Override
+  public double dailyRentalPrice() {
+    return cylinderCapacity * PRICE_PER_UNIT_OF_CYLINDER_CAPACITY;
+  }
+}
diff --git a/src/main/java/agency/RentalAgency.java b/src/main/java/agency/RentalAgency.java
index 62a52ab..e49883e 100644
--- a/src/main/java/agency/RentalAgency.java
+++ b/src/main/java/agency/RentalAgency.java
@@ -7,6 +7,13 @@ public class RentalAgency {
   private final List<Vehicle> vehicles = new ArrayList<>();
   private final Map<Client, Vehicle> rentedVehicles = new HashMap<>();
 
+  public RentalAgency() {
+  }
+
+  public RentalAgency(List<Vehicle> vehicles) {
+    this.vehicles.addAll(vehicles);
+  }
+
   void remove(Vehicle vehicle){
     requiresExisting(vehicle);
     vehicles.remove(vehicle);
@@ -81,9 +88,12 @@ public class RentalAgency {
     return vehicles;
   }
 
-  public void printVehicles(){
+  private static void print(List<Vehicle> vehicles){
     for(Vehicle vehicle : vehicles){
       System.out.println(vehicle);
     }
   }
+  public void printSelectedVehicles(Predicate<Vehicle> criterion){
+    print(select(criterion));
+  }
 }
diff --git a/src/main/java/agency/VehicleTypeSpecifics.java b/src/main/java/agency/VehicleTypeSpecifics.java
new file mode 100644
index 0000000..0da90b4
--- /dev/null
+++ b/src/main/java/agency/VehicleTypeSpecifics.java
@@ -0,0 +1,7 @@
+package agency;
+
+public interface VehicleTypeSpecifics {
+  String kindOfVehicle();
+  String vehicleDetails();
+  double dailyRentalPrice();
+}
diff --git a/src/test/java/agency/TestRentalAgency.java b/src/test/java/agency/TestRentalAgency.java
index 7c53317..227aa2f 100644
--- a/src/test/java/agency/TestRentalAgency.java
+++ b/src/test/java/agency/TestRentalAgency.java
@@ -2,92 +2,118 @@ package agency;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import util.TimeProvider;
+
+import java.time.LocalDateTime;
+import java.util.Objects;
 
 import static org.assertj.core.api.Assertions.*;
 
 class TestRentalAgency extends TestCar{
-  private RentalAgency rentalAgency;
+
+  Car catalinaCar = new Car(apple, catalina, catalinaYear, catalinaNumberOfSeats);
+  Car windows95Car = new Car(microsoft, windows95, windows95Year, windows95NumberOfSeats);
+  static final String apple = "Apple";
+  static final String catalina = "Catalina";
+  static final String microsoft = "Microsoft";
+  static final String windows95 = "Windows95";
+  static final int catalinaYear = 2019;
+  static final int windows95Year = 1995;
+  static final int catalinaNumberOfSeats = 3;
+  private static final int windows95NumberOfSeats = 1;
+  private static final LocalDateTime YEAR2000 = LocalDateTime.of(2000, 1, 1, 0, 0);
+  private static final LocalDateTime YEAR2019 = LocalDateTime.of(2019, 1, 1, 0, 0);
 
   @BeforeEach
-  void initializeRentalAgency(){
-    rentalAgency = new RentalAgency();
+  void fixClock(){
+    TimeProvider.useFixedClockAt(YEAR2019);
   }
 
   @Test
-  void testAddingCars(){
-    rentalAgency.add(catalinaCar);
-    assertThat(rentalAgency.getVehicles())
-            .containsExactly(catalinaCar);
-    rentalAgency.add(windows95Car);
-    assertThat(rentalAgency.getVehicles())
-            .containsExactly(catalinaCar, windows95Car);
+  void testConstructionOfTooOldVehicle(){
+    assertThatIllegalArgumentException()
+            .isThrownBy(()->new Car("Linux", "ubuntu",1899, 3))
+            .withMessageContaining("1899");
   }
 
   @Test
-  void testAddingSameCar() {
-    assertThat(rentalAgency.add(catalinaCar)).isTrue();
-    assertThat(rentalAgency.add(catalinaCar)).isFalse();
+  void testConstructionOfFutureVehicle() {
+    assertThatIllegalArgumentException()
+            .isThrownBy(() -> new Car("Linux", "ubuntu", 2020, 3))
+            .withMessageContaining("2020");
   }
 
   @Test
-  void testRemovingExistingVehicle(){
-    rentalAgency.add(catalinaCar);
-    rentalAgency.remove(catalinaCar);
-    assertThat(rentalAgency.getVehicles()).doesNotContain(catalinaCar);
+  void testConstructionOfVehicleWithNoSeats() {
+    assertThatIllegalArgumentException()
+            .isThrownBy(() -> new Car("Linux", "ubuntu", 2000, 0))
+            .withMessageContaining("0");
   }
 
   @Test
-  void testRemovingNonExistingVehicle(){
-    assertThatThrownBy(() -> rentalAgency.remove(catalinaCar))
-            .isInstanceOf(UnknownVehicleException.class)
-            .hasMessageContaining(catalinaCar.toString());
+  void testGetters() {
+    assertThat(catalinaCar).hasFieldOrPropertyWithValue("brand", apple)
+            .hasFieldOrPropertyWithValue("model", catalina)
+            .hasFieldOrPropertyWithValue("productionYear", catalinaYear)
+            .hasFieldOrPropertyWithValue("numberOfSeats", catalinaNumberOfSeats);
   }
 
   @Test
-  void testSelect(){
-    rentalAgency.add(catalinaCar);
-    rentalAgency.add(windows95Car);
-    assertThat(rentalAgency.select(vehicle -> vehicle.getBrand().equals(apple)))
-            .containsExactly(catalinaCar);
-    assertThat(rentalAgency.select(vehicle -> vehicle.getModel().equals(windows95)))
-            .containsExactly(windows95Car);
+  void testConstructionOfFutureCar(){
+    assertThatIllegalArgumentException().
+            isThrownBy(() -> new Car("Linux", "ubuntu",
+                    2020, 2));
   }
 
-
-
-  private final Client arnaud = new Client("Arnaud", "Labourel", 1981);
-  private final Client paul = new Client("Paul", "Calcul", 2018);
-
   @Test
-  void testRentingVehicles(){
-    rentalAgency.add(catalinaCar);
-    rentalAgency.add(windows95Car);
-    rentalAgency.rentVehicle(arnaud, catalinaCar);
-    rentalAgency.rentVehicle(paul, windows95Car);
-    assertThat(rentalAgency.allRentedVehicles()).containsExactly(catalinaCar, windows95Car);
+  void testConstructionOfTooOldCar(){
+    assertThatIllegalArgumentException().
+            isThrownBy(() -> new Car("Linux", "ubuntu",
+                    1899, 1));
   }
 
   @Test
-  void testSameClientRentingSeveralVehicles(){
-    rentalAgency.add(catalinaCar);
-    rentalAgency.add(windows95Car);
-    assertThat(rentalAgency.rentVehicle(arnaud, catalinaCar)).isEqualTo(120.);
-    assertThatIllegalStateException().isThrownBy(() -> rentalAgency.rentVehicle(arnaud, windows95Car));
+  void testThatCarImplementsVehicle(){
+    assertThat(catalinaCar).withFailMessage("%s must implement %s",
+                    Car.class, Vehicle.class)
+            .isInstanceOf(Vehicle.class);
   }
 
 
   @Test
-  void testSameVehicleRentedTwice(){
-    rentalAgency.add(catalinaCar);
-    rentalAgency.rentVehicle(arnaud, catalinaCar);
-    assertThatIllegalStateException().isThrownBy(() -> rentalAgency.rentVehicle(paul, catalinaCar));
+  void testIsNew(){
+    assertThat(windows95Car).withFailMessage("A car with production year %s should have been old",
+                    windows95Year)
+            .returns(false, Car::isNew);
+    assertThat(catalinaCar).withFailMessage("A car with production year %s should have been new",
+                    catalinaYear)
+            .returns(true, Car::isNew);
+    TimeProvider.useFixedClockAt(YEAR2000);
+    assertThat(windows95Car).withFailMessage("A car with production year %s should have been new in %s",
+                    windows95Year, YEAR2000)
+            .returns(true, Car::isNew);
+    TimeProvider.useFixedClockAt(YEAR2019);
   }
 
+  @Test
+  void testDailyRentalPrice(){
+    double expectedPriceCatalinaCar = 40.*catalinaNumberOfSeats;
+    double expectedPriceWindowsCar = 20.*windows95NumberOfSeats;
+    assertThat(catalinaCar).as("Car DailyRentalPrice")
+            .withFailMessage("A new car with %s seats must have price %s",
+                    catalinaNumberOfSeats, expectedPriceCatalinaCar )
+            .returns(expectedPriceCatalinaCar, Car::dailyRentalPrice);
+    assertThat(windows95Car).as("Car DailyRentalPrice")
+            .withFailMessage("An old car with %s seat must have price %s",
+                    windows95NumberOfSeats, expectedPriceCatalinaCar )
+            .returns(expectedPriceWindowsCar, Car::dailyRentalPrice);
+  }
 
   @Test
-  void testRentingAVehicleNotInTheAgency(){
-    assertThatThrownBy(() -> rentalAgency.rentVehicle(paul, catalinaCar))
-            .isInstanceOf(UnknownVehicleException.class)
-            .hasMessageContaining(catalinaCar.toString());
+  void testToString(){
+    String expectedStringMacOs = "Car Apple Catalina 2019 (3 seats) : 120.0€";
+    assertThat(catalinaCar).returns(expectedStringMacOs, Objects::toString);
+    String expectedStringWin = "Car Microsoft Windows95 1995 (1 seat) : 20.0€";
+    assertThat(windows95Car).returns(expectedStringWin, Objects::toString);
   }
 }
-- 
GitLab